/* Spotipharm Design System — couche de composants.
 * =====================================================================
 * Transcription fidèle des composants `core` (React) du design system
 * vers des classes CSS pures préfixées `.sp-`. AUCUN style inline. Tout passe
 * par les tokens sémantiques de tokens/{colors,typography,spacing}.css.
 *
 * États : :hover, :focus-visible (ring teal AA via --shadow-focus / --ring),
 * :disabled, [aria-invalid="true"] (bordure rouge ; ring rouge au focus via
 * --shadow-focus-danger). Lié après base.css dans base.html.twig.
 *
 * Voir lat.md/design-system.md (source de vérité visuelle).
 * ===================================================================== */

/* =====================================================================
   BUTTON — .sp-btn (action committée)
   Variants : primary (teal plein), secondary (contour), ghost, danger.
   Tailles  : sm / md (défaut) / lg.   cf. core/Button.jsx
   ===================================================================== */
.sp-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-3); /* 8px — défaut md */
  height: 38px;
  padding: 0 var(--space-5); /* 0 16px — md */
  font-family: var(--font-sans);
  font-size: var(--text-base);
  font-weight: var(--fw-semibold);
  line-height: 1;
  white-space: nowrap;
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  cursor: pointer;
  text-decoration: none; /* pour la variante lien <a> */
  transition:
    background var(--duration-fast) var(--ease-standard),
    border-color var(--duration-fast) var(--ease-standard),
    transform var(--duration-fast) var(--ease-standard);
}

.sp-btn:focus-visible {
  outline: none;
  box-shadow: var(--shadow-focus);
}

/* Tailles */
.sp-btn--sm {
  height: 32px;
  gap: var(--space-2); /* ~6px → 4px grille; cf. jsx 6 */
  padding: 0 var(--space-4); /* 0 12px */
  font-size: var(--text-sm);
}

.sp-btn--lg {
  height: 44px;
  padding: 0 var(--space-6); /* 0 20px */
  font-size: var(--text-md);
}

/* Pleine largeur */
.sp-btn--block {
  width: 100%;
}

/* Variantes */
.sp-btn--primary {
  background: var(--brand);
  color: var(--brand-on);
  border-color: var(--brand);
}
.sp-btn--primary:not(:disabled):hover {
  background: var(--brand-hover);
  border-color: var(--brand-hover);
}
.sp-btn--primary:not(:disabled):active {
  background: var(--brand-active);
  transform: translateY(0.5px);
}

.sp-btn--secondary {
  background: var(--surface-card);
  color: var(--text-body);
  border-color: var(--border-default);
}
.sp-btn--secondary:not(:disabled):hover {
  background: var(--surface-hover);
}
.sp-btn--secondary:not(:disabled):active {
  background: var(--surface-active);
  transform: translateY(0.5px);
}

.sp-btn--ghost {
  background: transparent;
  color: var(--text-body);
  border-color: transparent;
}
.sp-btn--ghost:not(:disabled):hover {
  background: var(--surface-hover);
}
.sp-btn--ghost:not(:disabled):active {
  background: var(--surface-active);
  transform: translateY(0.5px);
}

.sp-btn--danger {
  background: var(--surface-card);
  color: var(--danger);
  border-color: color-mix(in srgb, var(--danger) 35%, var(--border-default));
}
.sp-btn--danger:not(:disabled):hover {
  background: var(--danger-subtle);
}
.sp-btn--danger:not(:disabled):active {
  transform: translateY(0.5px);
}

/* États désactivés (button:disabled + <a> sans href / .is-disabled) */
.sp-btn:disabled,
.sp-btn.is-disabled,
.sp-btn[aria-disabled="true"] {
  cursor: not-allowed;
  opacity: 0.5;
  pointer-events: none;
}

/* Hover ne doit pas souligner le lien-bouton */
.sp-btn:hover {
  text-decoration: none;
}

/* Icône au sein d'un bouton : ne pas rétrécir */
.sp-btn > svg,
.sp-btn__icon {
  flex: none;
}

/* =====================================================================
   ICON BUTTON — .sp-icon-btn (carré, icône seule)
   Tailles : sm / md (défaut) / lg → 30 / 34 / 38px.
   Tons    : neutral (défaut) / danger.   cf. core/IconButton.jsx
   ===================================================================== */
.sp-icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  flex: none;
  color: var(--text-muted);
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  cursor: pointer;
  transition:
    background var(--duration-fast) var(--ease-standard),
    color var(--duration-fast) var(--ease-standard);
}

.sp-icon-btn:focus-visible {
  outline: none;
  box-shadow: var(--shadow-focus);
}

.sp-icon-btn--sm {
  width: 30px;
  height: 30px;
}
.sp-icon-btn--lg {
  width: 38px;
  height: 38px;
}

.sp-icon-btn:not(:disabled):hover {
  background: var(--surface-hover);
  color: var(--text-strong);
}
.sp-icon-btn:not(:disabled):active {
  background: var(--surface-active);
}

.sp-icon-btn--danger:not(:disabled):hover {
  background: var(--danger-subtle);
  color: var(--danger);
}

.sp-icon-btn:disabled,
.sp-icon-btn[aria-disabled="true"] {
  cursor: not-allowed;
  opacity: 0.45;
  pointer-events: none;
}

.sp-icon-btn > svg,
.sp-icon-btn__glyph {
  flex: none;
}

/* Enveloppe d'icône inline (slot SVG du partial icon_button). */
.sp-icon-btn__glyph {
  display: inline-flex;
}

/* =====================================================================
   CHAMP DE FORMULAIRE — structure label / contrôle / aide / erreur
   .sp-field : conteneur vertical (cf. wrapper React Input/Select)
   ===================================================================== */
.sp-field {
  display: flex;
  flex-direction: column;
  gap: var(--space-2); /* ~6px ; grille 4px */
  font-family: var(--font-sans);
}

.sp-label {
  font-size: var(--text-sm);
  font-weight: var(--fw-medium);
  color: var(--text-body);
}

/* Astérisque « requis » : le form_label natif ajoute la classe `required`
   sur le <label> des champs requis (form_div_layout). */
.sp-label.required::after {
  content: " *";
  color: var(--danger);
}

.sp-help {
  font-size: var(--text-xs);
  color: var(--text-muted);
}

/* Aide rendue AVANT le contrôle (cf. bloc form_row_render du thème de
   formulaire) : la marge remplace le `gap` du conteneur, qui suffit sous le
   contrôle mais serre trop une aide qui doit se détacher du label. */
.sp-help--above {
  margin-bottom: var(--space-3);
}

.sp-error {
  font-size: var(--text-xs);
  color: var(--danger);
}

/* =====================================================================
   INPUT / TEXTAREA / SELECT — contrôles texte.   cf. core/Input.jsx, Select.jsx
   ===================================================================== */
.sp-input,
.sp-textarea,
.sp-select {
  width: 100%;
  font-family: var(--font-sans);
  font-size: var(--text-base);
  color: var(--text-strong);
  background: var(--surface-card);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-md);
  transition:
    border-color var(--duration-fast) var(--ease-standard),
    box-shadow var(--duration-fast) var(--ease-standard);
}

.sp-input,
.sp-select {
  height: 38px;
  padding: 0 var(--space-4); /* 0 12px */
}

.sp-input {
  font-variant-numeric: tabular-nums;
}

/* Tailles partagées input/select */
.sp-input--sm,
.sp-select--sm {
  height: 32px;
}
.sp-input--lg,
.sp-select--lg {
  height: 44px;
  font-size: var(--text-md);
}

/* Variante chiffres/codes (EAN13, quantités) */
.sp-input--mono {
  font-family: var(--font-mono);
}

.sp-textarea {
  min-height: 88px;
  padding: var(--space-3) var(--space-4); /* 8px 12px */
  line-height: var(--leading-normal);
  resize: vertical;
}

/* Placeholder */
.sp-input::placeholder,
.sp-textarea::placeholder {
  color: var(--text-faint);
}

/* Hover (hors focus / disabled) */
.sp-input:hover:not(:disabled):not(:focus),
.sp-textarea:hover:not(:disabled):not(:focus),
.sp-select:hover:not(:disabled):not(:focus) {
  border-color: var(--border-strong);
}

/* Focus — ring teal AA */
.sp-input:focus,
.sp-textarea:focus,
.sp-select:focus {
  outline: none;
  border-color: var(--border-focus);
  box-shadow: var(--shadow-focus);
}

/* Disabled */
.sp-input:disabled,
.sp-textarea:disabled,
.sp-select:disabled {
  background: var(--surface-sunken);
  cursor: not-allowed;
}

/* Erreur — bordure rouge, déclenchée par aria-invalid="true" (a11y-first) */
.sp-input[aria-invalid="true"],
.sp-textarea[aria-invalid="true"],
.sp-select[aria-invalid="true"] {
  border-color: var(--danger);
}

/* Erreur + focus — le ring reprend la couleur d'erreur. Sans ça, le ring teal du
   focus (plus bas en spécificité mais posant le box-shadow que l'erreur ne touche
   pas) masquerait le rouge. Spécificité supérieure → gagne sur :focus. */
.sp-input[aria-invalid="true"]:focus,
.sp-textarea[aria-invalid="true"]:focus,
.sp-select[aria-invalid="true"]:focus {
  border-color: var(--danger);
  box-shadow: var(--shadow-focus-danger);
}

/* SELECT — chevron natif masqué, affordance custom */
.sp-select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 34px; /* place pour le chevron */
  cursor: pointer;
}

/* Conteneur input avec icône / select avec chevron */
.sp-control {
  position: relative;
  display: flex;
  align-items: center;
}

/* Icône en tête de champ (input) */
.sp-control__icon {
  position: absolute;
  left: 11px;
  display: inline-flex;
  color: var(--text-faint);
  pointer-events: none;
}
.sp-control__icon ~ .sp-input {
  padding-left: 34px;
}

/* Chevron de select (positionné à droite) */
.sp-control__chevron {
  position: absolute;
  right: 11px;
  display: inline-flex;
  color: var(--text-muted);
  pointer-events: none;
}

/* =====================================================================
   FILE — champ fichier (CV de candidature…). 100 % CSS, zéro JS :
   la surface publique jobs n'embarque aucun script, on habille donc
   l'input natif. ::file-selector-button devient un bouton secondaire
   du DS ; le nom du fichier choisi reste affiché nativement à sa
   droite (« Aucun fichier choisi » avant sélection). Le libellé du
   bouton reste celui du navigateur (« Choisir un fichier ») :
   intraduisible en CSS, assumé. cf. Forms/FileUpload (claude.ai/design).
   Le pseudo-bouton duplique .sp-btn--sm/--secondary (un pseudo-élément
   ne peut pas porter de classe) — garder en phase ; radius-sm et non md
   car imbriqué dans un conteneur déjà arrondi radius-md.
   ===================================================================== */
.sp-file {
  display: block;
  width: 100%;
  padding: var(--space-3);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  color: var(--text-muted);
  background: var(--surface-card);
  border: 1px dashed var(--border-default);
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: border-color var(--duration-fast) var(--ease-standard);
}
.sp-file:hover {
  border-color: var(--border-strong);
}
.sp-file::file-selector-button {
  height: 32px;
  margin-right: var(--space-4);
  padding: 0 var(--space-4);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  font-weight: var(--fw-semibold);
  color: var(--text-body);
  background: var(--surface-card);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-standard);
}
.sp-file:hover::file-selector-button,
.sp-file:focus-visible::file-selector-button {
  background: var(--surface-hover);
}
.sp-file[aria-invalid="true"] {
  border-color: var(--danger);
}

/* =====================================================================
   COMBOBOX — <select> enrichi en liste filtrable (assets/controllers/
   combobox_controller.js). Progressive enhancement : le <select> natif
   reste le fallback (masqué PAR le JS, jamais dans le HTML statique).
   Filtrage insensible aux accents (convention `unaccent`).
   ===================================================================== */
/* Le natif, masqué par le contrôleur une fois l'enrichissement actif. */
.sp-combobox__source {
  display: none;
}
/* Dans les layouts .sp-control, masque aussi le chevron natif devenu orphelin. */
.sp-combobox__source ~ .sp-control__chevron {
  display: none;
}

.sp-combobox {
  position: relative;
  /* Le natif .sp-select portait width:100% et remplissait son .sp-control flex.
     Ce wrapper le remplace au runtime : sans largeur propre, un enfant flex se
     dimensionne sur son contenu (~207px) et la liste déroulante, calée sur sa
     largeur, faisait wrapper le libellé des options sur deux lignes. On lui rend
     le comportement « remplir la colonne » du select d'origine. */
  width: 100%;
}
.sp-combobox__input {
  padding-right: 34px; /* place pour le chevron, comme .sp-select */
  cursor: text;
}
.sp-combobox__chevron {
  position: absolute;
  top: 0;
  right: 11px;
  height: 38px; /* aligné sur la hauteur de .sp-input */
  display: inline-flex;
  align-items: center;
  color: var(--text-muted);
  pointer-events: none;
}

/* La liste déroulante (role=listbox). z-index modeste : au-dessus des champs
   voisins, sous les <dialog> (top layer).

   ELLE EST `fixed`, ET SES COORDONNÉES VIENNENT DU SCRIPT. En `absolute`, elle
   appartient à la zone défilable de son conteneur : posée dans une grille qui défile,
   elle y ajoutait sa hauteur et faisait NAÎTRE une barre verticale — mesuré, 134 px de
   contenu passaient à 426 pour 173 visibles — puis se faisait découper par ce même
   conteneur, son bas devenant inatteignable. Une liste déroulante n'appartient à aucun
   cadre : elle se pose PAR-DESSUS.

   C'est la règle que la bulle de relâchement a déjà payée sur le planning. */
/* ELLE EST DANS LE TOP LAYER (`popover`), ET LE NAVIGATEUR LUI IMPOSE SES PROPRES
   RÈGLES : `inset: 0` et `margin: auto`, qui la centreraient et l'étireraient. Le
   script pose `left` et `top` ; il faut donc rendre `right` et `bottom` à `auto`,
   sans quoi la boîte est sur-contrainte et sa hauteur remplit la fenêtre. */
.sp-combobox__list {
  position: fixed;
  inset: auto;
  z-index: 30;
  margin: 0;
  padding: var(--space-1);
  list-style: none;
  max-height: 16rem;
  overflow-y: auto;
  background: var(--surface-card);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
}
.sp-combobox__option {
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  color: var(--text-body);
  cursor: pointer;
}
/* Survol souris ET option active au clavier : même affordance. */
.sp-combobox__option:hover,
.sp-combobox__option--active {
  background: var(--surface-hover);
}
/* Valeur actuellement engagée. */
.sp-combobox__option[aria-selected="true"] {
  background: var(--brand-subtle);
  color: var(--brand);
  font-weight: var(--fw-medium);
}
.sp-combobox__empty {
  padding: var(--space-2) var(--space-3);
  font-size: var(--text-sm);
  color: var(--text-faint);
}

/* =====================================================================
   CARD — .sp-card (surface blanche, bordure fine, ombre douce)
   cf. core/Card.jsx
   ===================================================================== */
.sp-card {
  background: var(--surface-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
  font-family: var(--font-sans);
}

.sp-card__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4); /* 12px */
  padding: 14px var(--space-6); /* 14px 20px */
  border-bottom: 1px solid var(--border-subtle);
}

.sp-card__heading {
  min-width: 0;
}
/* Variante avec icône d'état accolée au titre (ex. « Stock faible »). */
.sp-card__heading--icon {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.sp-card__title-icon {
  flex: none;
  color: var(--warning);
}

.sp-card__title {
  margin: 0;
  font-size: var(--text-lg);
  font-weight: var(--fw-semibold);
  color: var(--text-strong);
}

.sp-card__subtitle {
  margin: 2px 0 0;
  font-size: var(--text-sm);
  color: var(--text-muted);
}

.sp-card__actions {
  display: flex;
  gap: var(--space-3); /* 8px */
  flex: none;
}

.sp-card__body {
  padding: var(--space-6); /* 20px */
}

/* Corps sans rembourrage (table pleine largeur) */
.sp-card__body--flush {
  padding: 0;
}

/* =====================================================================
   BADGE — .sp-badge (pastille statut / catégorie, générique)
   Tons : neutral / brand / success / info / warning / danger / violet.
   Tailles : sm / md.   cf. core/Badge.jsx
   Le sens ne doit JAMAIS reposer sur la couleur seule → toujours libellé
   (+ icône recommandée).
   ===================================================================== */
.sp-badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 10px; /* md */
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  font-weight: var(--fw-semibold);
  line-height: 1.2;
  white-space: nowrap;
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  /* défaut = neutral */
  color: var(--state-normal-fg);
  background: var(--state-normal-bg);
}

.sp-badge--sm {
  padding: 2px 8px;
  font-size: var(--text-2xs);
}

.sp-badge__icon {
  display: inline-flex;
  flex: none;
}

/* Tons */
.sp-badge--neutral {
  color: var(--state-normal-fg);
  background: var(--state-normal-bg);
  border-color: transparent;
}
.sp-badge--brand {
  color: var(--brand-subtle-fg);
  background: var(--brand-subtle);
  border-color: transparent;
}
.sp-badge--success {
  color: var(--emerald-700);
  background: var(--success-subtle);
  border-color: transparent;
}
.sp-badge--info {
  color: var(--sky-700);
  background: var(--info-subtle);
  border-color: transparent;
}
.sp-badge--warning {
  color: var(--amber-700);
  background: var(--warning-subtle);
  border-color: var(--amber-100);
}
.sp-badge--danger {
  color: var(--red-700);
  background: var(--danger-subtle);
  border-color: var(--red-100);
}
.sp-badge--violet {
  color: var(--violet-700);
  background: var(--violet-50);
  border-color: var(--violet-100);
}

/* =====================================================================
   CUSTOM ELEMENTS — display & variantes par attribut
   ---------------------------------------------------------------------
   Convention markup (lat.md#Markup des composants) : on bannit <div>/<span>
   au profit d'éléments natifs sémantiques ou de custom elements (à trait
   d'union, ou préfixe x- pour un mot unique). Ces tags sont `display:inline`
   par défaut → on fixe ici leur display pour qu'ils se comportent comme le
   markup d'origine. Voir composants <twig:*> dans templates/components/.
   ===================================================================== */

/* x-card / x-card-* (composant Card) — la classe .sp-card porte déjà le
   style de surface ; on garantit juste le mode bloc des custom elements. */
x-card {
  display: block;
}
x-card-heading {
  display: block;
  min-width: 0;
}
x-card-body {
  display: block;
}
/* Pas d'entrée pour `x-card-header` / `x-card-footer` : `.sp-card__header` et
   `.sp-card__footer` déclarent `display: flex` en propre, là où `.sp-card__heading`
   et `.sp-card__body` ci-dessus ne déclarent aucun display — c'est ce qui vaut à
   ces deux-là leur ligne ici, et non une règle « un tag maison, une entrée ». */

/* Pied de carte (symétrique de .sp-card__header). */
.sp-card__footer {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-3); /* 8px */
  padding: 14px var(--space-6); /* 14px 20px */
  border-top: 1px solid var(--border-subtle);
}

/* x-badge (composant Badge) — reprend .sp-badge (inline-flex) ; les variantes
   sont exposées PAR ATTRIBUT [data-variant] en plus des classes modifier,
   cohérent avec la convention lat. neutral = défaut de .sp-badge. */
x-badge.sp-badge {
  display: inline-flex;
}
x-badge[data-variant="info"] {
  color: var(--sky-700);
  background: var(--info-subtle);
  border-color: transparent;
}
x-badge[data-variant="success"] {
  color: var(--emerald-700);
  background: var(--success-subtle);
  border-color: transparent;
}
x-badge[data-variant="warning"] {
  color: var(--amber-700);
  background: var(--warning-subtle);
  border-color: var(--amber-100);
}
x-badge[data-variant="danger"] {
  color: var(--red-700);
  background: var(--danger-subtle);
  border-color: var(--red-100);
}
/* violet : la classe modifier .sp-badge--violet existait, mais pas son
   attribut — un <twig:Badge variant="violet"> sortait donc silencieusement
   neutre. Le ton porte les repères conventionnels (coefficient) sur les
   écrans Emploi. */
x-badge[data-variant="violet"] {
  color: var(--violet-700);
  background: var(--violet-50);
  border-color: var(--violet-100);
}
/* brand : MÊME OUBLI QUE VIOLET, quatre mois plus tard — la classe modifier
   `.sp-badge--brand` existait, pas son attribut, donc `variant="brand"` sortait
   silencieusement neutre. Deux fois le même défaut par le même mécanisme : c'est
   ce qui a motivé la garde mécanique (`VariantesDeBadgeTest`).

   Le ton suit l'accent du module courant, ce qui convient à un badge d'ÉTAT posé
   dans un module (« En cours » d'un dossier de destruction) et jamais à un badge
   de RÔLE, qui doit rester le même d'un écran à l'autre. */
x-badge[data-variant="brand"] {
  color: var(--brand-subtle-fg);
  background: var(--brand-subtle);
  border-color: transparent;
}
x-badge-icon {
  display: inline-flex;
  flex: none;
}

/* =====================================================================
   MOVEMENT STATUS BADGE — .sp-movement-status (composant domaine registre)
   <movement-status-badge data-status="…" data-kind="state|direction">
   Couleurs portées PAR ATTRIBUT [data-status] (plus de style inline Twig).
   Le sens passe par le libellé + l'icône, jamais la couleur seule.
   ===================================================================== */
.sp-movement-status {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 10px; /* md */
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  font-weight: var(--fw-semibold);
  line-height: 1.2;
  white-space: nowrap;
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  /* défaut neutre si data-status absent/inconnu */
  color: var(--state-normal-fg);
  background: var(--state-normal-bg);
}

.sp-movement-status--sm {
  padding: 2px 8px;
  font-size: var(--text-2xs);
}

.sp-movement-status__icon {
  flex: none;
}

/* États réglementaires (data-kind="state") */
.sp-movement-status[data-status="normale"] {
  color: var(--state-normal-fg);
  background: var(--state-normal-bg);
  border-color: transparent;
}
.sp-movement-status[data-status="erreur"] {
  color: var(--state-error-fg);
  background: var(--state-error-bg);
  border-color: var(--state-error-border);
}
.sp-movement-status[data-status="corrigee"] {
  color: var(--state-corrected-fg);
  background: var(--state-corrected-bg);
  border-color: transparent;
}
.sp-movement-status[data-status="correction"] {
  color: var(--state-correction-fg);
  background: var(--state-correction-bg);
  border-color: var(--state-correction-border);
}

/* Sens du mouvement (data-kind="direction") */
.sp-movement-status[data-status="entree"] {
  color: var(--entree-fg);
  background: var(--entree-bg);
  border-color: var(--entree-border);
}
.sp-movement-status[data-status="sortie"] {
  color: var(--sortie-fg);
  background: var(--sortie-bg);
  border-color: var(--sortie-border);
}

/* =====================================================================
   EMPTY STATE — <empty-state> (vide / sans résultat / erreur)
   Sobre : icône line + phrase calme + une action. data-tone="error" pour
   l'état d'erreur. Pas d'illustration ni de gradient (clinical paper).
   ===================================================================== */
empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-4);
  padding: var(--space-10) var(--space-6);
  text-align: center;
}
.sp-empty-state__icon {
  display: inline-flex;
  color: var(--text-faint);
}
.sp-empty-state__icon svg {
  width: 28px;
  height: 28px;
}
.sp-empty-state__title {
  color: var(--text-strong);
  font-size: var(--text-md);
  font-weight: var(--fw-semibold);
}
.sp-empty-state__desc {
  max-width: 44ch;
  color: var(--text-muted);
  font-size: var(--text-sm);
}
.sp-empty-state__actions {
  display: inline-flex;
  gap: var(--space-3);
  margin-top: var(--space-2);
}
empty-state[data-tone="error"] .sp-empty-state__icon {
  color: var(--danger);
}

/* =====================================================================
   SKELETON — <x-skeleton> (chargement ; jamais un spinner seul)
   Lignes grisées qui préfigurent le contenu. Shimmer fonctionnel, coupé
   sous prefers-reduced-motion.
   ===================================================================== */
x-skeleton {
  display: block;
}
.sp-skeleton__line {
  display: block;
  height: 14px;
  border-radius: var(--radius-sm);
  background: var(--surface-sunken);
  position: relative;
  overflow: hidden;
}
.sp-skeleton__line + .sp-skeleton__line {
  margin-top: var(--space-3);
}
.sp-skeleton__line::after {
  content: "";
  position: absolute;
  inset: 0;
  transform: translateX(-100%);
  background: linear-gradient(
    90deg,
    transparent,
    color-mix(in srgb, var(--slate-300) 55%, transparent),
    transparent
  );
  animation: sp-skeleton-shimmer 1.4s ease-in-out infinite;
}
@keyframes sp-skeleton-shimmer {
  100% {
    transform: translateX(100%);
  }
}
@media (prefers-reduced-motion: reduce) {
  .sp-skeleton__line::after {
    animation: none;
  }
}

/* =====================================================================
   APP SHELL — layout authentifié (sidebar + topbar + contenu)
   templates/app.html.twig + <twig:Sidebar>. Custom elements pour les
   conteneurs, éléments sémantiques (<aside>/<nav>/<ul>/<a>/<header>/<h1>)
   pour le reste. Voir lat.md/screens.md#Fondations communes — app shell.
   ===================================================================== */
sp-app {
  display: flex;
  min-height: 100vh;
}

/* ---- Sidebar ---- */
.sp-sidebar {
  width: var(--sidebar-width);
  flex: none;
  position: sticky;
  top: 0;
  height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--surface-card);
  border-right: var(--border-width) solid var(--border-subtle);
}
.sp-sidebar__brand {
  display: flex;
  align-items: center;
  height: var(--header-height);
  padding: 0 var(--space-5);
  border-bottom: var(--border-width) solid var(--border-subtle);
}
/* Logo : la marque elle-même est portée par le composant <twig:Logo> (.sp-logo,
   défini plus bas). Ici, seul l'enrobage cliquable de la sidebar (lien dashboard). */
.sp-sidebar__logo {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
}
/* Switcher de modules (composant <twig:ModuleSwitcher>) : le lockup tamponné
   ouvre le menu Stup ↔ Jobs. Canonique demos/switcher-modules.html, variante
   <details> sans JS. Rendu dans la Sidebar (app) et la topbar publique Jobs. */
.sp-switcher {
  position: relative;
}
.sp-switcher > summary {
  list-style: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  border-radius: var(--radius-md);
  padding: 3px 6px 3px 3px;
  transition: background-color var(--duration-fast) var(--ease-standard);
}
.sp-switcher > summary::-webkit-details-marker {
  display: none;
}
.sp-switcher > summary:hover {
  background: var(--surface-hover);
}
.sp-switcher > summary:focus-visible {
  outline: none;
  box-shadow: var(--shadow-focus);
}
.sp-switcher__chev {
  flex: none;
  color: var(--text-muted);
  transition: transform var(--duration-fast) var(--ease-standard);
}
.sp-switcher[open] .sp-switcher__chev {
  transform: rotate(180deg);
}
.sp-switcher__menu {
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  z-index: 40;
  width: 320px;
  padding: var(--space-3);
  background: var(--surface-card);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.sp-switcher__label {
  margin: 0;
  padding: 4px var(--space-3) var(--space-2);
  color: var(--text-muted);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: var(--tracking-caps);
  font-weight: var(--fw-semibold);
}
.sp-switcher__list {
  margin: 0;
  padding: 0;
  list-style: none;
}
.sp-switcher__item {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: start;
  gap: var(--space-3);
  padding: var(--space-3);
  border-radius: var(--radius-md);
  text-decoration: none;
  color: var(--text-body);
}
.sp-switcher__item:hover {
  background: var(--surface-hover);
}
.sp-switcher__item[aria-current="page"] {
  background: var(--brand-subtle);
}
.sp-switcher__dot {
  width: 12px;
  height: 12px;
  margin-top: 3px;
  flex: none;
  border-radius: 999px;
}
.sp-switcher__dot--registre {
  background: var(--teal-600);
}
.sp-switcher__dot--emploi {
  background: var(--violet-600);
}
.sp-switcher__dot--rh {
  background: var(--orange-600);
}
/* LE MOT DU TAMPON, en monospace, à côté du nom du module. Il porte
   l'information que la pastille perd en noir et blanc et en vision déficiente —
   « la couleur n'est jamais le seul signal » — et il apprend au passage le
   tampon qu'on retrouvera dans le lockup. Le tableau de bord n'en a pas : il
   n'est pas un module, il n'a pas de tampon. */
.sp-switcher__head {
  display: flex;
  align-items: center;
  gap: 6px;
}
.sp-switcher__word {
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  padding: 1px 4px;
  border: 1px solid currentColor;
  border-radius: 2px;
}
.sp-switcher__word--registre {
  color: var(--teal-700);
}
.sp-switcher__word--rh {
  color: var(--orange-700);
}
.sp-switcher__word--emploi {
  color: var(--violet-700);
}
/* LE TABLEAU DE BORD N'EST PAS UN MODULE : il est l'écran de l'écosystème, d'où
   l'on choisit. D'où un glyphe et non une pastille — les pastilles sont des
   identités de module — et un filet qui le sépare de la liste qui suit. Le ranger
   parmi les modules aplatirait les deux niveaux que tout le produit distingue. */
/* LE CONTENANT, PAS LA POSITION — et il se MONTRE. Sur un écran transverse, aucun
   item n'est courant ; un menu qui ne marque rien laisse pourtant le doute entre
   « aucun de ces items ne me concerne » et « le menu n'a pas su ». La ligne du
   tableau de bord porte donc les signaux de l'entrée de nav active MOINS la coche :
   filet, fond ténu, encre de marque. Trois signaux valent mieux qu'un — mesuré, le
   fond seul rend 1,03:1 contre le blanc du menu, c'est-à-dire rien.

   Le filet est la pièce qui porte : « un filet sépare, il ne nomme pas », et c'est
   la grammaire que la barre latérale emploie déjà pour dire l'appartenance. Deux
   poids visuels pour deux faits — « exactement ici » garde la coche, « dans cet
   ensemble » ne l'a pas. */
.sp-switcher__item--contenant {
  background: var(--brand-subtle);
  box-shadow: inset 2px 0 0 var(--brand);
}
.sp-switcher__item--contenant .sp-switcher__name {
  color: var(--brand-subtle-fg);
}
/* LA TROISIÈME PISTE RESTE, coche ou pas : la grille a trois colonnes, et un item
   sans coche qui n'émettrait rien laisserait la colonne de texte prendre les 16 px
   libérés. Mesuré avant correction : 258 px sur les items non courants contre 242
   sur le courant, donc des promesses qui ne se cassent pas au même mot selon que
   l'item est courant. Un menu dont les lignes bougent se relit à chaque ouverture. */
.sp-switcher__check-slot {
  width: 16px;
}
.sp-switcher__item--hub {
  border-bottom: 1px solid var(--border-subtle);
  border-radius: var(--radius-md) var(--radius-md) 0 0;
  margin-bottom: var(--space-1);
}
.sp-switcher__glyph {
  display: grid;
  place-items: center;
  width: 12px;
  height: 12px;
  margin-top: 3px;
  flex: none;
  color: var(--text-muted);
}
.sp-switcher__name {
  font-weight: var(--fw-semibold);
  color: var(--text-strong);
}
.sp-switcher__desc {
  display: block;
  font-size: var(--text-xs);
  color: var(--text-muted);
  margin-top: 1px;
}
.sp-switcher__here {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: var(--text-2xs);
  font-weight: var(--fw-semibold);
  color: var(--brand);
  margin-top: 3px;
  white-space: nowrap;
}
.sp-switcher__domain {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: var(--text-2xs);
  color: var(--text-muted);
  margin-top: 4px;
  font-family: var(--font-mono);
}
.sp-switcher__check {
  color: var(--brand);
  flex: none;
  margin-top: 2px;
}

/* Marque Spotipharm (composant <twig:Logo>) : capsule teal + croix blanche +
   wordmark bicolore. Source unique, partagée app shell + écrans pré-app.
   Couleurs via tokens (parité DS : --brand #136b63, --text-strong #141b24). */
.sp-logo {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
}
/* Base = taille `md` (sidebar) : un `size` inconnu retombe donc sur md plutôt que
   sur un SVG sans dimensions. `--lg` est le seul modificateur (écran de connexion). */
.sp-logo__mark {
  flex: none;
  width: 28px;
  height: 28px;
}
.sp-logo__mark-square {
  fill: var(--brand);
}
.sp-logo__mark-cross {
  fill: none;
  stroke: var(--brand-on);
}
.sp-logo__wordmark {
  font-weight: var(--fw-semibold);
  font-size: var(--text-lg);
  letter-spacing: var(--tracking-tight);
  color: var(--text-strong);
}
.sp-logo__wordmark-accent {
  color: var(--brand);
}
/* Tampon module — « cadre administratif » à double filet (canonique
   demos/logo-tests.html variante T2, décision Allan 2026-07-07) : le lockup
   devient « Spotipharm [stup] » / « Spotipharm [jobs] ». Encre = --brand
   (thémée par module) ; le mot porte le sens, jamais la couleur seule.
   Chevauche la fin du wordmark (marge négative), tient à 24 px sans repli. */
.sp-logo__stamp {
  display: inline-flex;
  align-items: center;
  position: relative;
  z-index: 1;
  transform: rotate(-4deg) translateY(-1px);
  font-family: var(--font-mono);
  font-size: 9px;
  padding: 2px 5px;
  margin-left: -9px;
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  letter-spacing: 0.08em; /* canonique T2 (plus large que --tracking-caps) */
  line-height: 1;
  color: var(--brand);
  border: 1.5px solid var(--brand);
  outline: 1.5px solid var(--brand);
  outline-offset: 2px;
  border-radius: 2px;
  background: color-mix(in srgb, var(--surface-card) 82%, transparent);
}
.sp-logo--lg .sp-logo__mark {
  width: 36px;
  height: 36px;
}
.sp-logo--lg .sp-logo__wordmark {
  font-size: var(--text-xl);
}
.sp-logo--lg .sp-logo__stamp {
  font-size: 11px;
  padding: 3px 6px;
  margin-left: -11px;
}
.sp-sidebar__nav {
  flex: 1;
  overflow-y: auto;
  padding: var(--space-5) var(--space-4);
}
sp-nav-group {
  display: block;
}
/* ATTENTION, ADJACENCE : `.sp-nav-module` s'intercale entre l'étage racine et le
   premier groupe du module, ce qui rompt DÉLIBÉRÉMENT cette adjacence pour cette
   paire-là. Cette règle ne s'applique donc plus qu'entre groupes d'un MÊME étage — sur l'accueil, aussi
   entre les trois groupes du RH — soit, dans le registre, entre « Opérations quotidiennes »
   et « Administration ». Ce n'est pas un oubli à réparer : l'espace et, au rail, le
   filet qu'elle posait entre racine et module sont désormais portés par l'intitulé
   lui-même (marges de `.sp-nav-module`, tampon au rail). La frontière de module et la
   frontière de groupe se marquent autrement l'une que l'autre, c'est ce qui les
   distingue. Retirer l'intitulé sans rétablir cette adjacence collerait les deux
   étages. */
sp-nav-group + sp-nav-group {
  margin-top: var(--space-6);
}
.sp-nav-group__label {
  padding: 0 var(--space-4) var(--space-3);
}
/* Intitulé de l'étage module — le 2ᵉ des trois niveaux d'intitulé de la barre
   (marque > module > groupes). Il se lit PLUS HAUT que les libellés de groupe, qui
   sont en petites capitales grises à 11px : on lui donne la taille des ENTRÉES de nav
   (`--text-sm`, 13px), une graisse pleine et la couleur forte, au lieu d'un second
   `.eyebrow` qui l'aurait mis au même rang. Et pas de padding horizontal, quand les
   groupes en ont un de `--space-4` : il déborde de 12px à leur gauche, ce qui range
   visuellement les groupes SOUS lui. Trois signaux concordants plutôt qu'un seul.
   Canonique : screens/shell.css. */
.sp-nav-module {
  display: flex;
  align-items: center;
  margin: var(--space-7) 0 var(--space-4);
  font-size: var(--text-sm);
  font-weight: var(--fw-semibold);
  color: var(--text-strong);
}
/* Dans la navigation, le tampon ne mord pas sur un wordmark : on annule le
   chevauchement négatif qu'il porte dans le lockup. */
.sp-logo__stamp--nav {
  margin-left: 0;
}
/* Le tampon vit dans le lockup : le répéter à quarante pixels sous lui, à côté du nom
   écrit en toutes lettres, serait du bruit. En barre dépliée l'intitulé porte donc le
   nom seul ; le tampon ne reparaît qu'au rail (plus bas), où le nom disparaît et où il
   redevient le seul marqueur de la frontière. Les deux ne coexistent jamais. */
.sp-nav-module .sp-logo__stamp--nav {
  display: none;
}
.sp-nav-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.sp-nav {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  color: var(--text-body);
  font-size: var(--text-sm);
  font-weight: var(--fw-medium);
  text-decoration: none;
  transition: background var(--duration-fast) var(--ease-standard);
}
.sp-nav:hover {
  background: var(--surface-hover);
  text-decoration: none;
}
/* Entrée non encore câblée (écran à venir) : pas de href, ton atténué, pas de hover. */
.sp-nav--pending,
.sp-nav--pending .sp-nav__icon {
  color: var(--text-faint);
  cursor: default;
}
.sp-nav--pending:hover {
  background: transparent;
}
.sp-nav[aria-current="page"] {
  background: var(--brand-subtle);
  color: var(--brand-subtle-fg);
  font-weight: var(--fw-semibold);
}
.sp-nav__icon {
  flex: none;
  color: var(--text-muted);
}
.sp-nav[aria-current="page"] .sp-nav__icon {
  color: var(--brand);
}
.sp-nav__label {
  flex: 1;
}

/* Entrée de nav sortante (inter-module) : lien vers le module Jobs.
   Reste une entrée standard (teal du registre) ; une flèche « sortie »
   en fin de ligne signale qu'elle quitte le module. Pastille violette
   discrète = couleur du module de destination (le sens reste porté par
   la flèche et le title, jamais par la couleur seule). Canonique
   ui_kits/registre (registre-mobile.css). */
.rk-nav-out__ext {
  margin-left: auto;
  flex: none;
  color: var(--text-muted);
}
.rk-nav-out__dot {
  flex: none;
  width: 7px;
  height: 7px;
  border-radius: 999px;
  background: var(--violet-600);
  margin-left: auto;
}
.rk-nav-out__dot + .rk-nav-out__ext {
  margin-left: var(--space-2);
}

/* ---- Pied sidebar : opérateur ---- */
.sp-sidebar__user {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4);
  border-top: var(--border-width) solid var(--border-subtle);
}
.sp-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 34px;
  height: 34px;
  border-radius: var(--radius-pill);
  background: var(--brand);
  /* `--brand-on` et non `--text-on-brand` : deux alias pour la même idée, mais
     seul le premier est le pendant déclaré de `--brand`. Un module qui aurait
     besoin d'encre sombre sur son accent corrigeait le bouton et pas l'avatar. */
  color: var(--brand-on);
  font-size: var(--text-xs);
  font-weight: var(--fw-semibold);
}
/* Pastille compacte, pour une identité citée en ligne dans une phrase
   (en-tête de l'accueil officine) plutôt qu'en pied de navigation. */
.sp-avatar--sm {
  width: 24px;
  height: 24px;
  font-size: var(--text-2xs);
}
.sp-sidebar__user-id {
  display: block;
  min-width: 0;
  flex: 1;
}
.sp-sidebar__user-name {
  display: block;
  font-size: var(--text-sm);
  font-weight: var(--fw-semibold);
  color: var(--text-strong);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.sp-sidebar__user-role {
  display: block;
  font-size: var(--text-xs);
  color: var(--text-muted);
}
/* Identité cliquable → « Mon compte ». Occupe l'espace, laisse la déconnexion à droite. */
.sp-sidebar__account {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex: 1;
  min-width: 0;
  padding: var(--space-2);
  margin: calc(-1 * var(--space-2));
  border-radius: var(--radius-md);
  text-decoration: none;
  transition: background var(--duration-fast) var(--ease-standard);
}
.sp-sidebar__account:hover {
  background: var(--surface-hover);
  text-decoration: none;
}
.sp-sidebar__account[aria-current="page"] {
  background: var(--brand-subtle);
}

/* ---- Zone principale ---- */
.sp-main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  background: var(--surface-page);
}
.sp-topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-5);
  height: var(--header-height);
  padding: 0 var(--space-8);
  background: var(--surface-card);
  border-bottom: var(--border-width) solid var(--border-subtle);
}
.sp-topbar__heading {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  /* Le titre garde la priorité sur l'espace : c'est le chip officine du bloc
     de droite qui s'ellipse en premier quand le bandeau serre. */
  flex: 1 1 auto;
}
/* Bloc de droite du bandeau : actions d'écran (CTA) puis contexte officine.
   Rétrécissable (min-width: 0) : le nom d'officine s'ellipse avant que le
   titre de l'écran ne soit sacrifié. */
.sp-topbar__aside {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  flex: 0 1 auto;
  min-width: 0;
}
.sp-topbar__actions {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}
.sp-topbar__title {
  font-size: var(--text-xl);
  font-weight: var(--fw-semibold);
  /* La topbar a une hauteur fixe : quand les actions serrent le titre,
     il s'ellipse au lieu de s'empiler hors du bandeau. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.sp-topbar__subtitle {
  font-size: var(--text-sm);
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.sp-officine {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  flex: 0 1 auto;
  min-width: 0;
  padding: var(--space-2) var(--space-4);
  background: var(--surface-sunken);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-md);
  color: var(--text-muted);
  font-size: var(--text-sm);
}
.sp-officine__name {
  font-weight: var(--fw-semibold);
  color: var(--text-body);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.sp-content {
  display: block;
  flex: 1;
  overflow-y: auto;
  padding: var(--space-8);
  /* Conteneur de référence des layouts internes : les grilles à rail
     (dashboard, saisie) basculent selon la place RÉELLE que leur laisse la
     sidebar, pas selon le viewport (à 1130px de viewport, le contenu ne
     fait que ~800px). Cf. lat.md/design-system.md#Fluidité. */
  container: content / inline-size;
}

/* =====================================================================
   MODALE — <dialog class="sp-modal"> (scrim flou léger, carte centrée)
   ===================================================================== */
.sp-modal {
  position: fixed;
  inset: 0;
  margin: auto;
  border: none;
  padding: 0;
  max-width: 420px;
  width: calc(100% - 2 * var(--space-7));
  height: fit-content;
  background: var(--surface-card);
  color: var(--text-body);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
}
.sp-modal::backdrop {
  background: color-mix(in srgb, var(--slate-900) 45%, transparent);
  backdrop-filter: blur(2px);
}
.sp-modal__body {
  display: block;
  padding: var(--space-7);
}
.sp-modal__title {
  font-size: var(--text-lg);
  font-weight: var(--fw-semibold);
  color: var(--text-strong);
}
.sp-modal__text {
  margin-top: var(--space-3);
  font-size: var(--text-sm);
  color: var(--text-body);
}
sp-modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-3);
  margin-top: var(--space-6);
}

/* =====================================================================
   ÉCRAN D'AUTHENTIFICATION — connexion (pré-app, hors app shell)
   templates/security/login.html.twig. Centré plein écran, une seule
   carte. L'officine (le tenant) est affichée en lecture seule : elle est
   portée par le slug d'URL, jamais saisie. Voir
   lat.md/auth-tenancy.md#Connexion composite + lat.md/screens.md#Lot — Connexion.
   ===================================================================== */
.sp-auth {
  min-height: 100vh;
  display: grid;
  /* Deux rangées : la carte occupe la place libre et s'y centre, la ligne de
     repères légaux se pose en bas. Sans ces pistes, les deux rangées se
     partageraient la hauteur et la ligne flotterait au milieu du bas. */
  grid-template-rows: 1fr auto;
  place-items: center;
  padding: var(--space-6);
  background: var(--surface-page);
}
/* La scène du <main> : elle porte la carte, centrée dans la place libre. */
.sp-auth__stage {
  display: grid;
  place-items: center;
  width: 100%;
}
/* Ligne de repères légaux (déclinaison nue) : l'espacement vient d'ici. */
.sp-auth__legal {
  display: flex;
  justify-content: center;
  padding-top: var(--space-6);
}
.sp-auth__card {
  width: 100%;
  max-width: 26rem;
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  padding: var(--space-9) var(--space-8);
  background: var(--surface-card);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-lg);
}
/* La marque en tête de carte est le composant <twig:Logo size="lg" /> (.sp-logo) ;
   l'espacement vient du `gap` de .sp-auth__card. */
/* Bandeau officine en lecture seule : nom + FINESS (identité du tenant). */
.sp-auth__officine {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-4) var(--space-5);
  background: var(--surface-sunken);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-md);
}
.sp-auth__officine-label {
  font-size: var(--text-xs);
  color: var(--text-muted);
}
.sp-auth__officine-name {
  font-size: var(--text-base);
  font-weight: var(--fw-semibold);
  color: var(--text-strong);
}
.sp-auth__officine-finess {
  font-size: var(--text-xs);
  color: var(--text-muted);
}
.sp-auth__title {
  font-size: var(--text-base);
  font-weight: var(--fw-medium);
  color: var(--text-body);
}
.sp-auth__form {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}
.sp-auth__form .sp-btn {
  margin-top: var(--space-2);
}
/* Erreur d'identifiants : bandeau neutre et bref (pas de détail révélateur). */
.sp-auth__error {
  padding: var(--space-3) var(--space-4);
  font-size: var(--text-sm);
  color: var(--danger);
  background: var(--danger-subtle);
  border: var(--border-width) solid var(--danger);
  border-radius: var(--radius-md);
}

/* =====================================================================
   REGISTRE — table du livre de comptes, filtre segmenté, pagination
   templates/registre/index.html.twig. Voir lat.md/journeys.md#Consulter le registre.
   ===================================================================== */

/* En-tête de liste : filtre à gauche, compteur total à droite. */
/* Intro d'écran : sous-titre discret sous le titre du topbar (rappel de cadre,
   contexte réglementaire). Une ligne de lecture, ton atténué. */
.sp-screen-intro {
  margin: calc(-1 * var(--space-2)) 0 var(--space-5);
  font-size: var(--text-sm);
  color: var(--text-muted);
}

.sp-list-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  margin-bottom: var(--space-5);
}
.sp-list-header__count {
  font-size: var(--text-sm);
  color: var(--text-muted);
}
/* Côté droit de la barre de liste : compteur + action alignés. */
.sp-list-header__side {
  display: inline-flex;
  align-items: center;
  gap: var(--space-4);
}
/* Recherche : le champ occupe l'espace disponible jusqu'à une largeur de lecture. */
.sp-search {
  flex: 1 1 auto;
  max-width: 28rem;
}
.sp-search .sp-input {
  width: 100%;
}

/* ---- Filtre segmenté (type de mouvement) ---- */
.sp-segmented {
  display: inline-flex;
  gap: 2px;
  padding: 3px;
  background: var(--surface-sunken);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-md);
}
.sp-segmented__item {
  padding: var(--space-2) var(--space-4);
  font-size: var(--text-sm);
  font-weight: var(--fw-medium);
  color: var(--text-muted);
  text-decoration: none;
  border-radius: calc(var(--radius-md) - 2px);
  transition: background var(--duration-fast) var(--ease-standard), color var(--duration-fast) var(--ease-standard);
}
.sp-segmented__item:hover {
  color: var(--text-body);
}
.sp-segmented__item[aria-current="true"] {
  background: var(--surface-card);
  color: var(--text-strong);
  box-shadow: var(--shadow-xs);
}
/* Sous-nav (familles de données de référence) : marge sous la barre. */
.sp-subnav {
  margin-bottom: var(--space-5);
}

/* ---- Table générique ---- */
.sp-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-sm);
}
.sp-table thead th {
  padding: var(--space-3) var(--space-5);
  text-align: left;
  font-size: var(--text-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-wide, 0.02em);
  text-transform: uppercase;
  color: var(--text-muted);
  background: var(--surface-sunken);
  border-bottom: var(--border-width) solid var(--border-subtle);
}
.sp-table tbody td {
  padding: var(--space-4) var(--space-5);
  vertical-align: top;
  border-bottom: var(--border-width) solid var(--border-subtle);
}
.sp-table tbody tr:last-child td {
  border-bottom: 0;
}
.sp-table tbody tr:hover td {
  background: var(--surface-hover);
}
.sp-table__num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}
/* L'en-tête d'une colonne numérique doit s'aligner à droite COMME ses cellules :
   sans ce rappel, `.sp-table thead th` (spécificité 0,1,2) l'emporte sur
   `.sp-table__num` (0,1,0) et laisse le titre à gauche, décalé de ses chiffres. */
.sp-table thead th.sp-table__num {
  text-align: right;
}

/* ---- États de ligne du registre (colonne « État ») ---- */
/* Le rendu de ligne est piloté par `:has()` sur le badge d'état DANS la cellule État,
   et non par une classe sur le <tr> : c'est la cellule que le Turbo Stream remplace après
   une déclaration, donc le barré devient **live** (pas besoin de recharger). Pur CSS →
   fonctionne sans JS (un registre réglementaire doit s'afficher même si le JS échoue).
   `[data-status="erreur|corrigee|correction"]` est propre à la cellule État (les badges de
   sens portent `sortie`/`entree`), d'où le scoping à `sp-state`. */

/* Ligne fautive (erreur déclarée, ouverte ou corrigée) : la quantité — le chiffre
   réglementaire contesté — est barrée. La ligne n'est jamais supprimée ni éditée
   (immuabilité R.5132-36) ; le badge d'état porte le sens, jamais la couleur seule. */
.sp-table tbody tr:has(sp-state [data-status="erreur"]) .sp-qty,
.sp-table tbody tr:has(sp-state [data-status="corrigee"]) .sp-qty {
  text-decoration: line-through;
  text-decoration-thickness: 1.5px;
  opacity: 0.6;
}
/* Écriture compensatoire (extourne / re-post) : première cellule indentée + filet
   de rattachement, pour la lire comme dérivée de l'erreur qu'elle corrige. */
.sp-table tbody tr:has(sp-state [data-status="correction"]) > td:first-child {
  padding-left: var(--space-8);
  box-shadow: inset 2px 0 0 var(--border-default);
}

/* La cellule État empile son statut (badge + action primaire) au-dessus d'un
   sous-titre de référence discret. Le badge garde son [data-status] À L'INTÉRIEUR :
   les `:has(sp-state [data-status=…])` ci-dessus (descendant, toute profondeur)
   restent valables — ils ciblent la cellule, pas `.sp-state__main`. */
.sp-state {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
}
.sp-state__main {
  /* Badge + action empilés (« Erreur déclarée » puis « Corriger » à la
     ligne) : jamais côte à côte — canonique v2 du 2026-07-08. */
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-2);
}
/* Sous-titre « Extourne/Re-saisie de #N » / « #N » : mono discret sous le badge. */
.sp-state__ref {
  display: block;
  font-size: var(--text-2xs);
  color: var(--text-muted);
}
/* Référence « #N » cliquable → modale du mouvement visé. Bouton nu stylé en lien. */
.sp-state__ref-link {
  padding: 0;
  border: 0;
  background: none;
  color: var(--text-link);
  font-size: inherit;
  cursor: pointer;
}
.sp-state__ref-link:hover {
  text-decoration: underline;
}
.sp-state__ref-link:focus-visible {
  outline: 2px solid var(--border-focus);
  outline-offset: 2px;
  border-radius: var(--radius-xs);
}

/* ---- Vue groupée par transaction : une CARTE par transaction ---- */
/* Proposition claude.ai/design transcrite. Le groupement est porté par la CARTE
   (bordure + ombre + espacement franc entre cartes), pas par une simple ligne d'en-tête :
   « une opération = un bloc » saute aux yeux. Éléments stylés PAR TAG (custom elements
   sémantiques, sans préfixe `sp-` ni abréviation, cf. Markup des composants) ; variante
   d'état par attribut `data-tone` (jamais la couleur seule : badge + texte portent le sens). */
transaction-groups {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}
transaction-card {
  display: block;
  background: var(--surface-card);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}
/* En-tête de transaction : badge sens/état, tiers + méta, #id + pastille « N lignes ».
   État porté par la teinte d'en-tête (pas d'accent latéral « side-tab » : un filet coloré
   sur un bord est un tell d'UI générée — cf. impeccable). */
transaction-head {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  flex-wrap: wrap;
  padding: var(--space-3) var(--space-5);
  background: var(--surface-sunken);
  border-bottom: var(--border-width) solid var(--border-subtle);
}
transaction-card[data-tone="erreur"] transaction-head { background: var(--state-error-bg); }
transaction-card[data-tone="correction"] transaction-head { background: var(--state-correction-bg); }
transaction-identity { min-width: 0; flex: 1; }
transaction-tiers { font-size: var(--text-md); font-weight: var(--fw-semibold); color: var(--text-strong); }
transaction-sub { margin-left: var(--space-2); font-size: var(--text-xs); font-weight: var(--fw-regular); color: var(--text-muted); }
transaction-meta { display: block; margin-top: 2px; font-size: var(--text-xs); color: var(--text-muted); }
transaction-aside { display: inline-flex; align-items: center; gap: var(--space-3); flex: none; }
transaction-ref { font-size: var(--text-xs); color: var(--text-muted); }
transaction-count {
  font-size: var(--text-xs);
  font-weight: var(--fw-semibold);
  color: var(--text-muted);
  background: var(--surface-card);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-pill);
  padding: 2px var(--space-3);
}

/* Lignes mouvements : rattachées par un connecteur vertical (indentation + filet continu
   à gauche). Grille : spécialité · quantité · stock · état. */
transaction-lines { display: block; padding: var(--space-2) var(--space-5) var(--space-3) 0; }
transaction-line {
  display: grid;
  grid-template-columns: minmax(0, 1fr) max-content max-content minmax(150px, auto);
  column-gap: var(--space-5);
  align-items: center;
  margin-left: var(--space-6);
  padding: var(--space-3) var(--space-4);
  border-left: 2px solid var(--border-subtle);
}
transaction-line:not(:last-child) { border-bottom: var(--border-width) solid var(--border-subtle); }
transaction-line:hover { background: var(--surface-hover); }
transaction-spec { min-width: 0; }
transaction-line > .sp-qty,
transaction-line > .sp-stock { justify-self: end; }
/* Ligne fautive (erreur/corrigée) : quantité barrée, piloté par :has() sur le badge de
   la cellule État — live après Turbo Stream, comme la vue lignes. */
transaction-line:has(sp-state [data-status="erreur"]) .sp-qty,
transaction-line:has(sp-state [data-status="corrigee"]) .sp-qty {
  text-decoration: line-through;
  text-decoration-thickness: 1.5px;
  opacity: 0.6;
}

/* Cellule à deux niveaux : valeur forte + sous-ligne discrète.
   La « valeur » est un custom element neutre (pas <strong>) : le poids visuel
   est porté par le CSS, sans emphase sémantique imposée aux lecteurs d'écran. */
.sp-cell {
  display: flex;
  flex-direction: column;
  gap: 1px;
  color: var(--text-strong);
}
.sp-cell__value {
  font-weight: var(--fw-semibold);
}
.sp-cell__sub {
  font-size: var(--text-xs);
  font-weight: var(--fw-regular, 400);
  color: var(--text-muted);
}

/* Quantité signée : + en entrée (vert), − en sortie (ambre/danger discret). */
.sp-qty {
  font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums;
}
.sp-qty--entree {
  color: var(--success);
}
.sp-qty--sortie {
  color: var(--danger);
}
.sp-qty__unit {
  font-weight: var(--fw-regular, 400);
  color: var(--text-muted);
}

/* Chaîne de stock : initial → final (final mis en avant). */
.sp-stock {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.sp-stock__final {
  font-weight: var(--fw-semibold);
  color: var(--text-strong);
}
.sp-stock__arrow {
  color: var(--border-strong, var(--text-muted));
}

/* ---- Pagination ---- */
.sp-pagination {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  margin-top: var(--space-5);
}
.sp-pagination__status {
  font-size: var(--text-sm);
  color: var(--text-muted);
}

/* =====================================================================
   Saisie d'un mouvement (Nouvelle sortie / Nouvelle entrée)
   ===================================================================== */

/* ---- Bandeaux de notification (flash) ---- */
.sp-alert {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-md);
  margin-bottom: var(--space-5);
  font-size: var(--text-sm);
}
/* Masquée : invisible ET sans place réservée (défaut). Sans cette règle, le
   `display: flex` ci-dessus écrase l'attribut [hidden] → boîte vide visible. */
.sp-alert[hidden] {
  display: none;
}
/* Variante « réservée » (ex. notice du modal de création) : invisible quand vide
   mais garde sa place (une ligne), pour qu'un message qui apparaît ne décale pas
   le layout sous le curseur. */
.sp-alert--reserved[hidden] {
  display: flex;
  visibility: hidden;
}
.sp-alert--reserved .sp-alert__text {
  min-height: 1.4em;
}
.sp-alert__icon {
  flex: none;
  margin-top: 1px;
}
.sp-alert--success {
  background: var(--success-subtle);
  border-color: color-mix(in srgb, var(--success) 35%, transparent);
  color: var(--text-strong);
}
.sp-alert--success .sp-alert__icon {
  color: var(--success);
}
.sp-alert--error {
  background: var(--danger-subtle);
  border-color: color-mix(in srgb, var(--danger) 35%, transparent);
  color: var(--text-strong);
}
.sp-alert--error .sp-alert__icon {
  color: var(--danger);
}
/* Information neutre (ex. scan d'une spécialité déjà présente) : teinte « sortie ». */
.sp-alert--info {
  background: var(--sky-50);
  border-color: color-mix(in srgb, var(--sky-500) 35%, transparent);
  color: var(--text-strong);
}
.sp-alert--info .sp-alert__icon {
  color: var(--sky-500);
}
/* Avertissement : le geste reste possible, rien n'a échoué, mais l'écart est
   notable et sera conservé — dénaturer avant le délai d'un mois de l'article
   R.5132-36, par exemple.

   ELLE MANQUAIT, ET DEUX ÉCRANS L'UTILISAIENT DÉJÀ POUR RIEN. `sp-alert--warning`
   était posée sur le dépôt et la gestion d'annonce sans qu'aucune règle ne la
   porte : mesuré au navigateur, fond transparent et bordure grise, soit
   l'apparence exacte d'une note neutre. Un commentaire du code l'avait constaté
   sans le corriger. Transcrite depuis `proposals/sp-alert-warning.html`. */
.sp-alert--warning {
  background: var(--warning-subtle);
  border-color: color-mix(in srgb, var(--warning) 35%, transparent);
  color: var(--text-strong);
}
.sp-alert--warning .sp-alert__icon {
  color: var(--warning);
}
/* Rappel réglementaire (callout teint « marque ») : l'accent teal discret du DS. */
.sp-alert--brand {
  background: var(--brand-subtle);
  border-color: color-mix(in srgb, var(--brand) 35%, transparent);
  color: var(--text-strong);
}
.sp-alert--brand .sp-alert__icon {
  color: var(--brand);
}

/* =====================================================================
   GESTION DES PÉRIMÉS — bandeau d'action, toolbar in-card, sélection, table
   ===================================================================== */
.perimes-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
  margin-bottom: var(--space-4);
}
.perimes-banner__intro {
  margin: 0;
}
/* Deux actions côte à côte dans le bandeau (onglet Isolés : Retour patient + dossier). */
.perimes-banner__actions {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
}
.perimes-toolbar {
  position: sticky;
  top: 0;
  z-index: 2;
  display: flex;
  align-items: flex-end;
  gap: var(--space-4);
  flex-wrap: wrap;
  padding: var(--space-4) var(--space-5);
  border-bottom: var(--border-width) solid var(--border-subtle);
  /* Voile translucide + flou : la table défile visiblement sous la toolbar collante. */
  background: color-mix(in srgb, var(--surface-card) 88%, transparent);
  backdrop-filter: blur(6px);
}
.perimes-toolbar__filters {
  display: flex;
  align-items: flex-end;
  gap: var(--space-3);
  flex-wrap: wrap;
  flex: 1 1 auto;
}
.perimes-toolbar__search {
  flex: 1 1 16rem;
  min-width: 13rem;
}
.perimes-toolbar__select {
  width: 12rem;
}
.perimes-count {
  margin-left: auto;
  align-self: center;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.perimes-selbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  /* Hauteur réservée même à vide : neutre par défaut, teintée à la sélection —
     évite le décalage (CLS) au premier clic. */
  min-height: 2.75rem;
  padding: var(--space-2) var(--space-5);
  background: var(--surface-card);
  border-bottom: var(--border-width) solid var(--border-subtle);
}
/* Variante EN BAS de la liste (onglet Isolés) : collante — on coche de haut en bas,
   l'action attend là où le geste finit, pas de remontée vers un bandeau. Action à gauche. */
.perimes-selbar--bottom {
  position: sticky;
  bottom: 0;
  z-index: 2;
  justify-content: flex-start;
  border-bottom: 0;
  border-top: var(--border-width) solid var(--border-subtle);
}
.perimes-selbar--bottom .perimes-selbar__clear {
  margin-left: auto;
}
.perimes-selbar--active {
  background: var(--brand-subtle);
}
.perimes-selbar__count {
  font-size: var(--text-sm);
  color: var(--text-muted);
}
/* Dette : encre teal-800 en dur sur fond thémé, pas d'alias à cette valeur (l'encre
   de marque s'arrête à teal-700). Inerte tant que l'écran vit dans Stup. Recensée
   dans TableDesAccentsTest::TEAL_TOLERE. */
.perimes-selbar--active .perimes-selbar__count {
  font-weight: var(--fw-semibold);
  color: var(--teal-800);
}
.perimes-selbar__clear {
  border: 0;
  background: transparent;
  color: var(--brand);
  font: inherit;
  font-weight: var(--fw-semibold);
  cursor: pointer;
}
.perimes-selbar__clear:hover {
  text-decoration: underline;
}
/* Lignes centrées verticalement : les cellules multi-lignes (libellé + CIP13,
   date + pastille) ne doivent pas tirer le n° de lot / la quantité vers le haut. */
.perimes-table tbody td {
  vertical-align: middle;
}
.perimes-col-check {
  width: 2.75rem;
  text-align: center;
}
.perimes-col-action {
  text-align: right;
  white-space: nowrap;
}
.sp-checkbox {
  width: 16px;
  height: 16px;
  accent-color: var(--brand);
  cursor: pointer;
}
/* Ligne sélectionnée teintée — pur CSS (:has sur la case), live sans JS. */
.perimes-table tbody tr:has(.sp-checkbox:checked) td {
  background: var(--brand-subtle);
}

/* =====================================================================
   DOSSIER DE DESTRUCTION (Écran 2) — 2 colonnes, acteurs PV, timeline
   ===================================================================== */
.dossier-screen {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--space-5);
}
.dossier-main {
  flex: 1 1 480px;
  min-width: 320px;
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}
.dossier-timeline {
  flex: 1 1 320px;
  min-width: 300px;
}
.dossier-actors {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-4);
  margin-bottom: var(--space-5);
}
@media (max-width: 720px) {
  .dossier-actors {
    grid-template-columns: 1fr;
  }
}
.dossier-actor {
  display: block;
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-md);
  padding: var(--space-4);
}
.dossier-actor:first-child {
  background: var(--surface-sunken);
}
.dossier-actor .sp-field + .sp-field,
.dossier-actor .sp-field + .sp-form-grid {
  margin-top: var(--space-3);
}
.dossier-actor__id {
  display: flex;
  flex-direction: column;
  margin-bottom: var(--space-2);
}
/* Timeline verticale (étapes du dossier) */
.timeline {
  display: block;
}
.timeline__step {
  display: grid;
  grid-template-columns: 32px 1fr;
  column-gap: var(--space-3);
}
.timeline__marker {
  display: flex;
  flex-direction: column;
  align-items: center;
}
.timeline__dot {
  width: 32px;
  height: 32px;
  flex: none;
  border-radius: 999px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1.5px solid var(--slate-300, var(--border-default));
  background: var(--surface-sunken);
  color: var(--text-muted);
}
.timeline__step[data-state="fait"] .timeline__dot {
  border-color: var(--success);
  background: var(--success-subtle);
  color: var(--success);
}
.timeline__step[data-state="encours"] .timeline__dot {
  border-color: var(--brand);
  background: var(--brand-subtle);
  color: var(--brand);
}
.timeline__rail {
  width: 2px;
  flex: 1;
  min-height: 22px;
  margin: var(--space-1) 0;
  background: var(--border-default);
}
.timeline__step[data-state="fait"] .timeline__rail {
  background: var(--success);
}
.timeline__body {
  padding-bottom: var(--space-5);
}
.timeline__step:last-child .timeline__body {
  padding-bottom: 0;
}
.timeline__title {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  font-size: var(--text-sm);
  font-weight: var(--fw-semibold);
  color: var(--text-strong);
}
.timeline__step[data-state="avenir"] .timeline__title {
  color: var(--text-muted);
  font-weight: 500;
}
.timeline__date {
  font-size: var(--text-2xs, var(--text-xs));
  color: var(--text-faint);
  margin-top: 2px;
}
.timeline__desc {
  font-size: var(--text-xs);
  color: var(--text-muted);
  margin-top: var(--space-1);
  line-height: 1.5;
}

/* ---- Ossature du formulaire ---- */
.sp-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}
.sp-form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--space-4);
}
.sp-form-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-3);
}

/* ---- Scan douchette : raccourci d'identification en tête des lignes ---- */
.sp-scan {
  display: block;
  margin-bottom: var(--space-5);
  padding: var(--space-4);
  background: var(--surface-sunken);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-md);
}
.sp-scan .sp-label {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.sp-scan .sp-label > svg {
  color: var(--brand);
}
.sp-scan .sp-alert {
  margin-top: var(--space-3);
}

/* ---- Lignes de spécialité ---- */
.sp-lines {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}
.sp-line {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding-bottom: var(--space-4);
  border-bottom: var(--border-width) dashed var(--border-subtle);
}
.sp-line:last-of-type {
  border-bottom: 0;
  padding-bottom: 0;
}
.sp-line__grid {
  display: grid;
  grid-template-columns: 1fr 9rem auto;
  align-items: end;
  gap: var(--space-3);
}
.sp-line__remove {
  display: flex;
  /* Aligner le bouton sur la ligne de saisie (sous le label). */
  padding-bottom: 2px;
}
/* En-tête de ligne : rangée dédiée portant le bouton « Replier » (aligné à droite,
   au-dessus des champs). Masqué par défaut ; le JS le montre sur ligne complète. */
.sp-line__head {
  display: flex;
  justify-content: flex-end;
}
.sp-line__head:has(.sp-line__collapse[hidden]) {
  display: none;
}
.sp-line__collapse {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  background: none;
  border: 0;
  border-radius: var(--radius-sm);
  color: var(--text-muted);
  font-size: var(--text-sm);
  cursor: pointer;
}
.sp-line__collapse:hover {
  background: var(--surface-sunken);
  color: var(--text-strong);
}
.sp-line__collapse:focus-visible {
  outline: 2px solid var(--border-focus);
  outline-offset: 2px;
}
.sp-line__comment .sp-label__optional {
  color: var(--text-faint);
  font-weight: 400;
}
/* Lot + péremption (entrée) : deux colonnes alignées sous la grille de ligne. */
.sp-line__lot {
  display: grid;
  grid-template-columns: 1fr 9rem;
  align-items: end;
  gap: var(--space-3);
}
.sp-line__lot .sp-label__optional {
  color: var(--text-faint);
  font-weight: 400;
}
@media (max-width: 640px) {
  .sp-line__lot {
    grid-template-columns: 1fr;
  }
}

/* ---- Ligne repliée : résumé une-ligne ----
   À l'ajout d'une spécialité, les lignes déjà renseignées se replient en un résumé
   cliquable (label + quantité) pour garder le formulaire compact. L'état est porté
   par [data-collapsed] sur la ligne ; la grille de saisie et le résumé s'excluent.
   Le résumé (et sa poubelle) sont construits en JS colocalisé (form.html.twig). */
.sp-line__summary {
  display: none;
}
.sp-line[data-collapsed] > .sp-line__head,
.sp-line[data-collapsed] > .sp-line__grid,
.sp-line[data-collapsed] > .sp-line__lot,
.sp-line[data-collapsed] > .sp-add-link,
.sp-line[data-collapsed] > .sp-line__comment {
  display: none;
}
.sp-line[data-collapsed] > .sp-line__summary {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}
.sp-line__summary-btn {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  text-align: left;
  color: var(--text-strong);
  background: var(--surface-sunken);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-md);
  cursor: pointer;
}
.sp-line__summary-btn:hover {
  border-color: var(--border-strong);
}
.sp-line__summary-check {
  display: inline-flex;
  align-self: center;
  flex: none;
  color: var(--success);
}
.sp-line__summary-name {
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sp-line__summary-sep {
  flex: none;
  color: var(--text-faint);
}
.sp-line__summary-meta {
  flex: none;
  color: var(--text-muted);
  white-space: nowrap;
}
.sp-line__summary-edit {
  flex: none;
  margin-left: auto;
  padding-left: var(--space-3);
  color: var(--brand);
  font-weight: 500;
}

.sp-lines__add {
  display: block;
  margin-top: var(--space-5);
}

/* Actions placées DANS la carte de saisie (Réinitialiser / Enregistrer), cf. DS. */
.sp-card .sp-form-actions {
  margin-top: var(--space-6);
}
/* Enregistrer grisé tant que le formulaire est invalide — CSS pur, sans JS :
   un champ requis vide (combobox, quantité) match :invalid → bouton inactif. */
.sp-movement-layout__form:has(:invalid) button[type="submit"] {
  cursor: not-allowed;
  opacity: 0.5;
  pointer-events: none;
}

/* =====================================================================
   SAISIE — mise en page deux colonnes (formulaire + récap collant)
   Reprise du design canonique (DS ui_kits/registre/MovementForm.jsx) :
   le formulaire ne s'étale plus pleine largeur, le récap de stock vit à droite.
   ===================================================================== */
.sp-movement-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 340px;
  gap: var(--space-7);
  align-items: start;
  max-width: 1000px;
}
.sp-movement-layout__form {
  min-width: 0;
}
.sp-movement-layout__recap {
  position: sticky;
  top: 0; /* aligne le haut du récap sur celui de la carte du formulaire (cf. DS). */
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

/* Étiquette de section (puce-icône + texte) à l'intérieur d'une carte. */
.sp-section-label {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-5);
}
.sp-section-label__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: var(--radius-md);
  background: var(--surface-sunken);
  color: var(--text-muted);
}
.sp-section-label--entree .sp-section-label__icon {
  background: var(--entree-bg);
  color: var(--entree-fg);
}
.sp-section-label--sortie .sp-section-label__icon {
  background: var(--sortie-bg);
  color: var(--sortie-fg);
}
.sp-section-label__text {
  font-size: var(--text-2xs);
  font-weight: var(--fw-semibold);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.sp-section-label__hint {
  margin-left: auto;
  font-size: var(--text-xs);
  color: var(--text-faint);
}

/* Filet de séparation entre deux sections d'une même carte. */
.sp-divider {
  display: block;
  height: var(--border-width);
  margin: var(--space-6) 0;
  background: var(--border-subtle);
}

/* ---- Récapitulatif du stock (aside) ---- */
.sp-recap__card {
  display: flex;
  flex-direction: column;
  padding: var(--space-5);
  background: var(--surface-card);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}
.sp-recap__label {
  margin-bottom: var(--space-4);
  font-size: var(--text-2xs);
  font-weight: var(--fw-semibold);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.sp-recap__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.sp-recap__empty {
  display: block;
  padding: var(--space-5) var(--space-2);
  font-size: var(--text-sm);
  color: var(--text-faint);
  text-align: center;
}
.sp-recap__operator {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: var(--border-width) solid var(--border-subtle);
  font-size: var(--text-xs);
  color: var(--text-muted);
}
/* Même dette que .perimes-selbar__count : encre teal-800 en dur sur fond thémé,
   cf. TableDesAccentsTest::TEAL_TOLERE. */
.sp-recap__notice {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-4);
  background: var(--brand-subtle);
  border-radius: var(--radius-md);
  color: var(--teal-800);
}
/* Icônes de l'aside : empêcher le flex de les écraser (sinon le SVG, shrink:1
   dans un conteneur étroit, est rogné en largeur). Le DS les fige en flex:none. */
.sp-recap__notice > svg {
  flex: none;
  margin-top: 1px;
}
.sp-recap__operator > svg {
  flex: none;
}
.sp-section-label__icon {
  flex: none;
}
.sp-recap__notice-text {
  font-size: var(--text-xs);
  line-height: var(--leading-normal);
}

/* ---- StockCalc, variante « block » : chaîne initial → ±qté → final ---- */
.sp-stockcalc {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-4);
  background: var(--surface-sunken);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-md);
}
.sp-stockcalc--warn {
  background: var(--danger-subtle);
  border-color: var(--danger);
}
.sp-stockcalc__spec {
  display: block;
  font-size: var(--text-sm);
  font-weight: var(--fw-semibold);
  color: var(--text-strong);
}
.sp-stockcalc__decomp {
  display: block;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--text-xs);
  color: var(--text-body);
}
.sp-stockcalc__cells {
  display: flex;
  align-items: stretch;
  gap: var(--space-2);
}
.sp-stockcalc__cell {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.sp-stockcalc__cell-label {
  font-size: var(--text-2xs);
  font-weight: var(--fw-semibold);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.sp-stockcalc__cell-value {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--text-xl);
  font-weight: var(--fw-semibold);
  color: var(--text-body);
}
.sp-stockcalc__cell--strong .sp-stockcalc__cell-value {
  color: var(--text-strong);
}
/* Accent directionnel (entrée = emerald, sortie = sky), comme le DS StockCalc. */
.sp-stockcalc--entree .sp-stockcalc__cell--accent .sp-stockcalc__cell-value,
.sp-stockcalc--entree .sp-stockcalc__decomp-total {
  color: var(--entree-fg);
}
.sp-stockcalc--sortie .sp-stockcalc__cell--accent .sp-stockcalc__cell-value,
.sp-stockcalc--sortie .sp-stockcalc__decomp-total {
  color: var(--sortie-fg);
}
.sp-stockcalc__cell--warn .sp-stockcalc__cell-value {
  color: var(--danger);
}

@media (max-width: 640px) {
  .sp-line__grid {
    grid-template-columns: 1fr auto;
  }
  .sp-line__qty {
    grid-column: 1 / 2;
  }
}

/* =====================================================================
   STACK — empilement vertical d'éléments (cartes, sections d'un écran)
   ===================================================================== */
.sp-stack {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}
.sp-stack--narrow {
  max-width: 640px;
}

/* =====================================================================
   DASHBOARD GRID — corps du tableau de bord : colonne principale large
   + rail étroit (cf. design-system, DashboardScreen.jsx). Chaque colonne
   empile ses cartes ; retombe en une seule colonne quand l'écran est trop
   étroit pour le rail.
   ===================================================================== */
dashboard-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 340px;
  gap: var(--space-6);
  align-items: start;
}
dashboard-main,
dashboard-rail {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  min-width: 0;
}
/* Bascule pilotée par la largeur du CONTENEUR : sous ~940px de contenu,
   le rail de 340px écraserait la colonne principale (tables tronquées).
   La media query 1024 reste en filet pour les navigateurs sans @container. */
@container content (max-width: 939px) {
  dashboard-grid {
    grid-template-columns: 1fr;
  }
  .sp-movement-layout {
    grid-template-columns: 1fr;
  }
  .sp-movement-layout__recap {
    position: static;
  }
}
@media (max-width: 1024px) {
  dashboard-grid {
    grid-template-columns: 1fr;
  }
  /* Saisie : sous le rail, le récap repasse sous le formulaire (et cesse d'être collant). */
  .sp-movement-layout {
    grid-template-columns: 1fr;
  }
  .sp-movement-layout__recap {
    position: static;
  }
}

/* =====================================================================
   META — paires clé/valeur en lecture seule (Mon compte, Infos, officine)
   <dl class="sp-meta"> : grille étiquette / valeur, chiffres tabulaires.
   ===================================================================== */
.sp-meta {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: var(--space-3) var(--space-6);
  margin: 0;
  align-items: baseline;
}
.sp-meta dt {
  font-size: var(--text-sm);
  color: var(--text-muted);
}
.sp-meta dd {
  margin: 0;
  font-size: var(--text-sm);
  font-weight: var(--fw-medium);
  color: var(--text-strong);
}
.sp-meta dd.mono {
  font-family: var(--font-mono);
}

/* =====================================================================
   PROSE — bloc de texte courant (texte réglementaire, aide longue)
   ===================================================================== */
.sp-prose {
  display: block;
  color: var(--text-body);
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
}
.sp-prose p + p {
  margin-top: var(--space-4);
}

/* Colonne d'actions de table : alignée à droite, sur une ligne. */
.sp-table__actions {
  text-align: right;
  white-space: nowrap;
}

/* Grappe d'actions d'une ligne (liens + formulaires inline), alignée à droite. */
.sp-actions {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

/* ---- Création de référence à la volée ---- */

/* Lien « + Nouveau … » sous un <select> de référence. */
.sp-add-link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  align-self: flex-start;
  margin-top: var(--space-1);
  padding: 0;
  background: none;
  border: 0;
  color: var(--text-link);
  font-size: var(--text-sm);
  cursor: pointer;
}
.sp-add-link:hover {
  text-decoration: underline;
}
.sp-add-link:focus-visible {
  outline: 2px solid var(--border-focus);
  outline-offset: 2px;
  border-radius: var(--radius-xs);
}

/* Dialog modal de création (natif <dialog>). */
.sp-dialog {
  width: min(34rem, calc(100vw - var(--space-8)));
  padding: 0;
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-lg);
  background: var(--surface-card);
  color: var(--text-body);
  box-shadow: var(--shadow-lg);
}
.sp-dialog::backdrop {
  background: color-mix(in srgb, var(--surface-inverse) 45%, transparent);
}

.sp-ref-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  padding: var(--space-6);
}
.sp-ref-form__title {
  font-size: var(--text-lg);
  color: var(--text-strong);
}

/* Détail d'un mouvement en lecture seule (modale du registre, turbo-frame). */
.sp-movement-detail {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  padding: var(--space-6);
}
.sp-movement-detail__head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}
.sp-movement-detail__title {
  font-size: var(--text-lg);
  color: var(--text-strong);
}
.sp-movement-detail__grid {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: var(--space-2) var(--space-5);
  margin: 0;
}
.sp-movement-detail__grid dt {
  align-self: center;
  font-size: var(--text-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.sp-movement-detail__grid dd {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--text-strong);
}
.sp-muted {
  color: var(--text-muted);
  font-weight: var(--fw-regular);
}

/* Champ + bouton sur une même ligne (ex. CIP13 + « Rechercher »). */
.sp-inline {
  display: flex;
  gap: var(--space-3);
  align-items: stretch;
}
.sp-inline > .sp-input {
  flex: 1 1 auto;
}
.sp-inline > .sp-btn {
  flex: none;
}

/* Case à cocher en ligne (ex. « ayant droit »). */
.sp-check {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  font-size: var(--text-sm);
  color: var(--text-body);
  cursor: pointer;
}
.sp-check input {
  width: 16px;
  height: 16px;
  accent-color: var(--brand);
  cursor: pointer;
}

/* =====================================================================
   Matrice de droits par personne (écran « Utilisateurs »)
   Capacités CAN_* groupées par rubrique : chaque rubrique = un <fieldset>
   (légende = eyebrow) contenant une liste bordée de cases pleine largeur,
   cliquables sur toute la ligne. cf. templates/admin/utilisateurs/droits.html.twig
   ===================================================================== */
.sp-permission-groups {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}
.sp-permission-group {
  border: 0;
  min-width: 0;
  margin: 0;
  padding: 0;
}
.sp-permission-group__label {
  display: block;
  margin-bottom: var(--space-2);
  padding: 0;
}
.sp-permission-list {
  display: block;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  overflow: hidden;
}
.sp-permission {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-top: 1px solid var(--border-subtle);
  font-size: var(--text-sm);
  color: var(--text-body);
  cursor: pointer;
}
.sp-permission:first-child {
  border-top: 0;
}
.sp-permission:hover {
  background: var(--surface-hover);
}
.sp-permission:has(input:checked) {
  color: var(--text-strong);
  font-weight: var(--fw-medium);
}
.sp-permission input {
  width: 16px;
  height: 16px;
  accent-color: var(--brand);
  cursor: pointer;
  flex: none;
}
/* Pastille « Défaut » : marque une capacité du preset du groupe (le socle).
   Case cochée hors pastille = ajout ; case décochée sur pastille = retrait. */
.sp-permission__tag {
  margin-left: auto;
  flex: none;
  font-size: var(--text-2xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--text-muted);
  background: var(--surface-sunken);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-pill);
  padding: 2px var(--space-2);
  white-space: nowrap;
}

/* =====================================================================
   STAT — .sp-stat (tuile de KPI du tableau de bord)
   Rangée de cartes-chiffres : eyebrow + valeur tabulaire + sous-texte.
   Même surface « clinical paper » que .sp-card. La couleur n'est jamais
   le seul porteur de sens (cf. design-system) : un compteur en alerte
   porte aussi un libellé. cf. screens/DashboardScreen.jsx
   ===================================================================== */
.sp-stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--space-4);
}
.sp-stat {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  background: var(--surface-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  padding: var(--space-5) var(--space-6);
  font-family: var(--font-sans);
}
/* En-tête de tuile : eyebrow + icône de cadran discrète (cf. DashboardScreen.jsx).
   L'icône reste en teinte faible : le chiffre est le point focal, l'urgence est
   déjà portée par --quiet/--alert. */
.sp-stat__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-3);
}
.sp-stat__icon {
  flex: none;
  color: var(--text-faint);
}
.sp-stat__value {
  font-size: var(--text-2xl);
  font-weight: var(--fw-semibold);
  color: var(--text-strong);
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}
.sp-stat__sub {
  font-size: var(--text-xs);
  color: var(--text-muted);
}
/* Compteur à zéro : on désature pour que l'œil aille vers ce qui demande une action. */
.sp-stat--quiet .sp-stat__value {
  color: var(--text-muted);
}
/* Compteur en alerte (erreurs ouvertes) : teinte d'état, jamais seule (libellé présent). */
.sp-stat--alert .sp-stat__value {
  color: var(--state-error-fg);
}

/* =====================================================================
   GAUGE — stock-gauge (jauge stock vs seuil, carte « stock faible »)
   Barre fine : remplissage ambre = niveau de stock relatif au seuil
   d'alerte. Plus la barre est basse, plus l'alerte est sévère (vide =
   rupture, seuil 0). Décorative (aria-hidden) : les chiffres stock/seuil
   portent le sens (jamais la couleur seule). cf. DashboardScreen.jsx
   ===================================================================== */
stock-gauge {
  display: block;
  height: 5px;
  margin-top: var(--space-2);
  border-radius: 999px;
  background: var(--surface-sunken);
  overflow: hidden;
}
stock-gauge > gauge-fill {
  display: block;
  height: 100%;
  border-radius: 999px;
  background: var(--warning);
}

/* File « À traiter » (rail du tableau de bord) : lignes-liens cliquables,
   pastille colorée par nature. Transcription de DashboardScreen.jsx. */
.sp-todo {
  display: block;
}
.sp-todo__item {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-5);
  text-decoration: none;
  color: inherit;
  transition: background var(--duration-fast) var(--ease-standard);
}
.sp-todo__item + .sp-todo__item {
  border-top: 1px solid var(--border-subtle);
}
.sp-todo__item:hover {
  background: var(--surface-hover);
}
.sp-todo__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 34px;
  height: 34px;
  border-radius: var(--radius-md);
}
.sp-todo__icon--alert {
  background: var(--state-error-bg);
  color: var(--state-error-fg);
}
.sp-todo__icon--validate {
  background: var(--brand-subtle);
  color: var(--brand);
}
.sp-todo__body {
  flex: 1;
  min-width: 0;
}
.sp-todo__title {
  display: block;
  font-size: var(--text-sm);
  font-weight: var(--fw-semibold);
  color: var(--text-strong);
}
.sp-todo__meta {
  display: block;
  font-size: var(--text-xs);
  color: var(--text-muted);
}
.sp-todo__chevron {
  flex: none;
  color: var(--text-faint);
}
/* Ligne de débordement « … et N autres » : pas de pastille, alignée au texte. */
.sp-todo__item--more {
  justify-content: space-between;
  font-size: var(--text-sm);
  font-weight: var(--fw-semibold);
  color: var(--text-link);
}

/* Stock faible (rail) : liste légère — nom + unités + jauge, sans table. */
.sp-lowstock {
  display: block;
  padding: var(--space-2) 0;
}
.sp-lowstock__row {
  display: block;
  padding: var(--space-3) 0;
}
.sp-lowstock__row + .sp-lowstock__row {
  border-top: 1px solid var(--border-subtle);
}
.sp-lowstock__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
}
.sp-lowstock__name {
  font-size: var(--text-sm);
  font-weight: var(--fw-medium);
  color: var(--text-strong);
}
.sp-lowstock__units {
  flex: none;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--text-sm);
  font-weight: var(--fw-semibold);
  color: var(--warning);
}

/* =====================================================================
   BALANCES — écran maître-détail (R.5132-36)
   Rail des périodes (mensuelles + annuelles, groupées) + détail de la
   période sélectionnée (réconciliation + validation). Transcription du
   design claude.ai (BalancesScreen.jsx). Voir lat.md/screens.md#Balances.
   ===================================================================== */

/* En-tête d'écran : intro réglementaire + accès à la balance dynamique. */
.sp-balances__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-5);
  flex-wrap: wrap;
}
.sp-balances__intro {
  max-width: 64ch;
  font-size: var(--text-sm);
  color: var(--text-muted);
  line-height: var(--leading-normal);
}

/* Grille maître-détail : rail fixe + détail extensible ; une colonne sous 1024px. */
.sp-balances {
  display: grid;
  grid-template-columns: 300px minmax(0, 1fr);
  gap: var(--space-6);
  align-items: start;
}
@media (max-width: 1024px) {
  .sp-balances {
    grid-template-columns: 1fr;
  }
}

/* ---- Rail des périodes ---- */
.sp-balances__rail {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  min-width: 0;
}
.sp-rail-group {
  display: block;
}
.sp-rail-group__label {
  padding: 0 var(--space-4) var(--space-3);
}
.sp-rail-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

/* Une période cliquable : surface neutre, surlignée quand active (carte + ombre). */
.sp-period {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-4);
  border: var(--border-width) solid transparent;
  border-radius: var(--radius-lg);
  text-decoration: none;
  transition: background var(--duration-fast) var(--ease-standard);
}
.sp-period:hover {
  background: var(--surface-hover);
  text-decoration: none;
}
/* Période ouverte : marquée par [aria-current], comme la nav latérale et le
   filtre segmenté (idiome du projet — pas de classe d'état dédiée). */
.sp-period[aria-current="page"] {
  background: var(--surface-card);
  border-color: var(--border-default);
  box-shadow: var(--shadow-sm);
}
.sp-period__top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}
.sp-period__label {
  font-size: var(--text-md);
  font-weight: var(--fw-semibold);
  color: var(--text-strong);
}
.sp-period__meta {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  font-size: var(--text-xs);
}

/* ---- Détail : la carte de réconciliation + validation ---- */
.sp-balances__detail {
  display: block;
  min-width: 0;
}
/* La surface est portée par .sp-card ; les sections internes se séparent par des
   filets, comme le mockup (en-tête / stats / table / pied). */
.sp-balance__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
  padding: var(--space-5) var(--space-6);
  border-bottom: var(--border-width) solid var(--border-subtle);
}
.sp-balance__titles {
  display: block;
  min-width: 0;
}
.sp-balance__title-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
}
.sp-balance__title {
  margin: 0;
  font-size: var(--text-xl);
  font-weight: var(--fw-semibold);
  color: var(--text-strong);
}
.sp-balance__sub {
  margin: var(--space-1) 0 0;
  font-size: var(--text-sm);
  color: var(--text-muted);
}

/* Bande de 3 stats, séparées par un filet vertical (cf. mockup). */
.sp-balance__stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  border-bottom: var(--border-width) solid var(--border-subtle);
}
.sp-balance__stat {
  display: block;
  padding: var(--space-4) var(--space-6);
}
.sp-balance__stat + .sp-balance__stat {
  border-left: var(--border-width) solid var(--border-subtle);
}
.sp-balance__stat-label {
  display: block;
  font-size: var(--text-2xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--text-muted);
}
.sp-balance__stat-value {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-2);
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--text-2xl);
  font-weight: var(--fw-semibold);
  color: var(--text-strong);
}
.sp-balance__stat-value > svg {
  flex: none;
}
.sp-balance__stat-value--entree {
  color: var(--success);
}
.sp-balance__stat-value--sortie {
  color: var(--danger);
}
/* Erreurs ouvertes : vert si zéro (porte ouverte à la validation), ambre sinon.
   L'icône (check / alerte) double la teinte — jamais la couleur seule. */
.sp-balance__stat-value--ok {
  color: var(--success);
}
.sp-balance__stat-value--warn {
  color: var(--warning);
}

/* Pied : l'acte de validation (pièce scellée) ou le bouton pour le poser. */
.sp-balance__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
  padding: var(--space-5) var(--space-6);
  background: var(--surface-sunken);
}
.sp-balance__sealed {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  font-size: var(--text-sm);
  color: var(--text-body);
}
.sp-balance__sealed-icon {
  display: inline-flex;
  flex: none;
  color: var(--success);
}
.sp-balance__note {
  max-width: 52ch;
  font-size: var(--text-sm);
  color: var(--text-muted);
  line-height: var(--leading-normal);
}
/* Le garde-fou de confirmation n'introduit pas de boîte : le bouton reste l'élément
   du pied (le <dialog> fermé est display:none). */
balance-validate {
  display: contents;
}

/* =====================================================================
   INVENTAIRE — assistant de comptage en 3 étapes (R.5132-36)
   Stepper + panneaux ; le comportement est en JS colocalisé. Voir
   lat.md/screens.md#Inventaire annuel & régularisation des écarts.
   ===================================================================== */
.sp-inv {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}
.sp-inv__stepper {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
}
.sp-inv__chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-pill);
  border: var(--border-width) solid var(--border-subtle);
  font-size: var(--text-sm);
  font-weight: var(--fw-semibold);
  color: var(--text-muted);
}
.sp-inv__chip.is-active {
  background: var(--brand-subtle);
  border-color: transparent;
  color: var(--brand-subtle-fg);
}
.sp-inv__num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  flex: none;
  border-radius: var(--radius-pill);
  background: var(--surface-sunken);
  color: var(--text-muted);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
}
.sp-inv__chip.is-active .sp-inv__num {
  background: var(--brand);
  color: var(--brand-on);
}
.sp-inv__chip.is-done .sp-inv__num {
  background: var(--success);
  color: var(--brand-on);
}
.sp-inv__sep {
  display: inline-block;
  width: 18px;
  height: 1px;
  background: var(--border-default);
}
inv-panel {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}
inv-panel[hidden] {
  display: none;
}
/* Barre d'actions de l'assistant : compteur/retour à gauche, action à droite. */
.sp-inv__actions {
  justify-content: space-between;
  align-items: center;
}
.sp-inv__counter {
  font-size: var(--text-sm);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
/* Côté droit de la barre d'actions : indice inline + bouton (étape 2 → 3). */
.sp-inv__foot-end {
  display: inline-flex;
  align-items: center;
  gap: var(--space-4);
}
.sp-inv__hint {
  font-size: var(--text-xs);
  color: var(--text-muted);
}
.sp-inv__hint[hidden] {
  display: none;
}
.sp-inv__note {
  margin-top: var(--space-2);
}
/* État « tout conforme » (0 écart) : carte rassurante centrée. */
.sp-inv__conform {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-8) var(--space-6);
  text-align: center;
  background: var(--surface-card);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}
.sp-inv__conform[hidden] {
  display: none;
}
.sp-inv__conform-icon {
  display: inline-flex;
  width: 48px;
  height: 48px;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-pill);
  background: var(--success-subtle);
  color: var(--success);
}
.sp-inv__conform-title {
  display: block;
  font-size: var(--text-lg);
  font-weight: var(--fw-semibold);
  color: var(--text-strong);
}

/* =====================================================================
   NET-ERROR — bandeau d'erreur réseau global (<twig:NetError>)
   Toast fixe, transitoire : un échec de requête Turbo (serveur injoignable)
   ne navigue pas → sans ça, l'action « disparaît » en silence. z-index sous
   les <dialog> (top layer). Voir lat.md/screens.md#Fondations communes.
   ===================================================================== */
.sp-net-error {
  position: fixed;
  bottom: var(--space-6);
  left: 50%;
  transform: translateX(-50%);
  z-index: 60;
  width: max-content;
  max-width: min(40rem, calc(100vw - 2 * var(--space-6)));
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
}
.sp-net-error[hidden] {
  display: none;
}
/* Réutilise .sp-alert--error ; on neutralise sa marge de flux (toast flottant). */
.sp-net-error .sp-alert {
  margin-bottom: 0;
  align-items: center;
}
.sp-net-error__text {
  flex: 1;
}
.sp-net-error__close {
  display: inline-flex;
  flex: none;
  padding: 0;
  background: none;
  border: 0;
  color: var(--text-muted);
  cursor: pointer;
}
.sp-net-error__close:hover {
  color: var(--text-strong);
}

/* =====================================================================
   APP SHELL MOBILE — déclinaison ≤ 768px de l'app registre
   Transcrit du canonique claude.ai/design `ui_kits/registre/` (index.html,
   login.html, registre-mobile.css). Le desktop est la référence de
   comptoir : rien ne change au-dessus du point de rupture, tout passe
   par des media queries.

   Décision de navigation (canonique, README du kit) : TAB BAR BASSE
   avec les 4 gestes du quotidien (Tableau, Registre, Entrée, Sortie)
   toujours au pouce + bouton « Menu » qui ouvre la SIDEBAR COMPLÈTE EN
   FEUILLE (administration, switcher de module, compte, déconnexion).
   Un burger seul enterrerait les saisies urgentes derrière un tap ; un
   rail d'icônes ne tient pas les 12 entrées.

   Enrichissement progressif : la feuille repose sur :target (ancres
   #menu-mobile / #contenu), le switcher sur <details> — AUCUN JS requis,
   essentiel pour un registre réglementaire. Voir
   lat.md/screens.md#Fondations communes — app shell.
   ===================================================================== */

/* Chrome mobile masqué par défaut (desktop intact). */
.sp-mobilebar,
.sp-tabbar,
.sp-sheet-scrim,
.sp-sheet-close,
.sp-scroll-hint,
.sp-page-title,
.sp-mvt-list {
  display: none;
}
/* Le tableau du registre : rendu desktop, remplacé par .sp-mvt-list en mobile. */
.sp-desktop-table {
  display: block;
}
/* Colonne « Mouvement » fusionnée : quantité dominante, ligne stock dessous
   (décision Allan 2026-07-08, canonique registre.html v2). */
.sp-move-cell {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 3px;
}
.sp-move-cell .sp-qty {
  font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums;
}
.sp-move-cell .sp-stock {
  font-size: var(--text-xs);
  color: var(--text-muted);
}
/* Type en picto seul aux largeurs contraintes (tablette, écran scindé) :
   piloté par la largeur du CONTENEUR content, pas du viewport. Sous 760px,
   la colonne Type disparaît et le picto entrée/sortie se fond dans la
   cellule Date (aria-label porte le libellé, jamais la couleur seule). */
.sp-date-cell {
  display: flex;
  align-items: center;
}
.sp-date-picto {
  display: none;
  flex: none;
  margin-right: var(--space-3);
}
.sp-date-picto[data-status="entree"] {
  color: var(--entree-fg);
}
.sp-date-picto[data-status="sortie"] {
  color: var(--sortie-fg);
}
@container content (max-width: 760px) {
  .sp-col-type {
    display: none;
  }
  .sp-date-picto {
    display: inline-flex;
  }
}
/* Conteneur de défilement des tables denses : toujours armé — inerte quand
   la table tient, il prend le relais dès qu'un conteneur la serre (rail du
   dashboard, viewport intermédiaire), au lieu de laisser tronquer une
   colonne. Les min-width garantissent des colonnes lisibles plutôt
   qu'écrasées. */
sp-table-scroll {
  display: block;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
.sp-table-scroll .sp-table {
  min-width: 560px;
}
.sp-table-scroll--wide .sp-table {
  min-width: 860px;
}

@media (max-width: 768px) {
  /* ---- Structure : la sidebar sort du flux, le contenu prend tout ---- */
  sp-app {
    display: block;
  }

  /* Cibles tactiles : au moins 44px sur toute la surface. */
  .sp-btn,
  .sp-input,
  .sp-select,
  .sp-textarea {
    min-height: 44px;
  }

  /* ---- 1) Sidebar → feuille (même markup, source unique) ---- */
  .sp-sidebar {
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    width: min(86vw, 320px);
    height: 100%;
    z-index: 60;
    transform: translateX(-100%);
    transition: transform var(--duration-normal) var(--ease-standard);
    box-shadow: var(--shadow-lg);
    overflow-y: auto;
  }
  .sp-sidebar:target {
    transform: translateX(0);
  }
  /* Scrim : visible quand la feuille est ouverte (sélecteur de voisinage). */
  .sp-sidebar:target + .sp-sheet-scrim {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 55;
    background: color-mix(in srgb, var(--slate-900) 45%, transparent);
  }
  /* Bouton de fermeture (dans l'en-tête de la feuille). */
  .sp-sheet-close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    margin-left: auto;
    flex: none;
    border-radius: var(--radius-md);
    color: var(--text-muted);
    text-decoration: none;
  }
  .sp-sheet-close:hover {
    background: var(--surface-hover);
  }
  /* Respiration entre lockup et croix de fermeture (canonique). */
  .sp-sidebar__brand {
    gap: var(--space-3);
  }
  /* Le switcher de modules reste utilisable au doigt et borné à la feuille. */
  .sp-sidebar .sp-switcher > summary {
    min-height: 44px;
  }
  .sp-sidebar .sp-switcher__menu {
    width: calc(min(86vw, 320px) - 2 * var(--space-5));
  }

  /* ---- 2) Topbar mobile (lockup + officine) ---- */
  .sp-topbar {
    display: none; /* topbar desktop masquée (titre → contenu, actions → tab bar) */
  }
  /* La mobilebar est COLLANTE et opaque : un saut d'ancre aligne le haut de la
     cible sur le haut du viewport, donc DERRIÈRE elle — le titre de page
     atterrissait masqué. `scroll-padding-top` sur le CONTENEUR DE DÉFILEMENT
     plutôt qu'un `scroll-margin-top` sur la cible : il vaut pour toute ancre, là
     où l'autre forme aurait laissé le piège armé pour la suivante.
     La hauteur ne vit qu'ICI et `.sp-mobilebar` la lit : les deux valeurs
     doivent être égales, autant que ce soit la même. */
  html {
    --mobilebar-height: 56px;
    scroll-padding-top: var(--mobilebar-height);
  }
  .sp-mobilebar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    position: sticky;
    top: 0;
    z-index: 40;
    height: var(--mobilebar-height);
    padding: 0 var(--space-4);
    background: var(--surface-card);
    border-bottom: var(--border-width) solid var(--border-subtle);
  }
  .sp-mobilebar .sp-officine {
    padding: var(--space-2) var(--space-3);
    font-size: var(--text-xs);
    max-width: 52vw;
  }
  .sp-mobilebar .sp-officine__name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* Titre de page : première ligne de contenu (au desktop il vit dans la topbar). */
  .sp-page-title {
    display: block;
    margin-bottom: var(--space-5);
  }
  .sp-page-title h1 {
    margin: 0;
    font-size: var(--text-xl);
    font-weight: var(--fw-semibold);
    color: var(--text-strong);
  }
  .sp-page-title p {
    margin: 4px 0 0;
    font-size: var(--text-sm);
    color: var(--text-muted);
  }

  /* ---- Contenu ---- */
  .sp-main {
    min-height: 100vh;
  }
  .sp-content {
    padding: var(--space-5) var(--space-4) calc(72px + env(safe-area-inset-bottom, 0px));
  }

  /* KPIs : 2 colonnes à 390px (l'auto-fit desktop en mettrait 1, trop long). */
  .sp-stat-grid {
    grid-template-columns: 1fr 1fr;
    gap: var(--space-3);
  }
  .sp-stat {
    padding: var(--space-4);
  }

  /* ---- 3) Tables denses : défilement horizontal dans la carte ----
     On ne casse pas la table en cartes : le registre est un document,
     l'alignement colonne compte. L'indice « Faites défiler » est pincé
     au-dessus du conteneur qui défile. */
  .sp-scroll-hint {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: var(--space-2) var(--space-4);
    font-size: var(--text-xs);
    color: var(--text-muted);
    border-bottom: var(--border-width) solid var(--border-subtle);
  }
  .sp-scroll-hint svg {
    flex: none;
  }
  /* Barre de liste (recherche + filtres) : empilée, recherche pleine largeur,
     filtres sur leur propre rangée (repli si étroit). */
  .sp-list-header {
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-3);
  }
  .sp-search {
    max-width: none;
  }
  .sp-list-header__side {
    flex-wrap: wrap;
  }

  /* ---- 4) Tab bar basse — transcrite de screens-shell/tabbar-mobile.html ----
     La règle qui décide du contenu vit dans
     lat.md/design-system.md#La barre d'onglets basse porte deux fentes fixes et
     un milieu. Ici, deux choses que le rendu ne trahit pas :

     LES ITEMS NE S'ÉTIRENT PAS — liste centrée, item plafonné à 104 px. À deux
     items, des cibles pleine largeur se liraient comme des boutons primaires.

     LA POSITION COURANTE A TROIS SIGNAUX, jamais la couleur seule : le rail de
     2 px, le libellé en demi-gras, `aria-current`. Le demi-gras ne se voit que
     si le repos est à 400 : le poids par défaut était à 500, hérité du
     prototype `.rk-tab`, et il rabotait le signal que ces lignes portent. */
  .sp-tabbar {
    display: block;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 50;
    background: var(--surface-card);
    /* `--border-default` et non `--border-subtle` : la barre flotte au-dessus
       d'un contenu qui défile sous elle, le canonique a délibérément renforcé
       le filet. Le ténu venait du prototype. */
    border-top: var(--border-width) solid var(--border-default);
    padding-bottom: env(safe-area-inset-bottom, 0px);
  }
  .sp-tabbar__list {
    display: flex;
    justify-content: center;
    align-items: stretch;
    list-style: none;
    margin: 0;
    padding: 0;
  }
  .sp-tabbar__item {
    flex: 1 1 0;
    max-width: 104px;
    display: flex;
  }
  .sp-tabbar__link {
    position: relative;
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;
    min-height: 56px;
    padding: 6px 4px;
    color: var(--text-muted);
    text-decoration: none;
    font-size: 11px;
  }
  .sp-tabbar__link svg {
    flex: none;
  }
  .sp-tabbar__label {
    line-height: 1.1;
    letter-spacing: -0.01em;
  }
  .sp-tabbar__link:hover {
    color: var(--text-body);
    background: var(--surface-hover);
  }
  .sp-tabbar__link:focus-visible {
    outline: 2px solid var(--border-focus);
    outline-offset: -2px;
  }
  /* À cinq items, 360 px d'écran donnent 72 px par cible — au-dessus du minimum
     tactile. C'est le libellé qui manque de place, pas le doigt. */
  .sp-tabbar[data-items="5"] .sp-tabbar__label {
    font-size: 10px;
    letter-spacing: -0.02em;
  }
  .sp-tabbar[data-items="5"] .sp-tabbar__link {
    padding: 6px 2px;
  }
  .sp-tabbar__link[aria-current="page"] {
    color: var(--brand-ink);
  }
  .sp-tabbar__link[aria-current="page"]::before {
    content: "";
    position: absolute;
    top: -1px;
    left: 14px;
    right: 14px;
    height: 2px;
    border-radius: 0 0 2px 2px;
    background: var(--brand);
  }
  .sp-tabbar__link[aria-current="page"] .sp-tabbar__label {
    font-weight: var(--fw-semibold);
  }
  /* Pas d'override pour « Accueil » courant, contrairement au canonique : il
     n'est courant que sur l'écran racine, qui porte DÉJÀ `data-module="racine"`,
     donc l'accent sky. La règle du canonique y produirait les mêmes octets, et
     nulle part ailleurs — `nav_active` ne vaut 'accueil' que là. */
  .sp-tabbar__link--ecrire {
    color: var(--brand-ink);
  }
  .sp-tabbar__link--ecrire .sp-tabbar__label {
    font-weight: var(--fw-semibold);
  }
  /* La pastille « plus » dit l'écriture SANS la teinte : elle se lit en vision
     déficiente et du coin de l'œil, là où l'encre de marque seule ne suffit
     pas. Bordure à la couleur de la barre pour la détacher du glyphe. */
  .sp-tabbar__badge {
    position: absolute;
    top: 7px;
    left: calc(50% + 7px);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 15px;
    height: 15px;
    border-radius: 999px;
    background: var(--brand);
    color: var(--brand-on);
    border: 1.5px solid var(--surface-card);
  }

  /* ---- Connexion : carte pleine largeur, contrôles tactiles ---- */
  .sp-auth {
    padding: var(--space-5);
    align-items: flex-start;
    padding-top: var(--space-9);
  }
  .sp-auth__card {
    max-width: 100%;
  }
  .sp-auth__form .sp-btn {
    width: 100%;
  }
}

/* =====================================================================
   REGISTRE DES MOUVEMENTS — déclinaison mobile (canonique
   ui_kits/registre/registre.html, 2026-07-08). Le tableau 8 colonnes
   cache l'essentiel en scroll horizontal : sous le point de rupture il
   devient des lignes riches pleine largeur (colonnes fusionnées), pas
   des cartes — filets fins, ordre chronologique préservé, rien caché.
   ===================================================================== */
@media (max-width: 768px) {
  .sp-desktop-table {
    display: none;
  }
  .sp-mvt-list {
    display: block;
    margin: 0;
    padding: 0;
    list-style: none;
  }

  /* Un mouvement = un bloc pleine largeur, séparé par un filet fin. */
  .sp-mvt {
    display: block;
    padding: var(--space-4) var(--space-4);
    border-bottom: var(--border-width) solid var(--border-subtle);
  }
  .sp-mvt:last-child {
    border-bottom: 0;
  }

  /* Tête : type + date à gauche, figures (quantité sur stock) à droite. */
  .sp-mvt__head {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
  }
  .sp-mvt__headmain {
    min-width: 0;
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: 6px;
    align-items: flex-start;
  }
  .sp-mvt__date {
    display: block;
    font-size: var(--text-sm);
    color: var(--text-body);
    font-variant-numeric: tabular-nums;
  }
  .sp-mvt__date-sub {
    display: block;
    font-size: var(--text-2xs);
    color: var(--text-muted);
    margin-top: 1px;
  }
  /* Figures fusionnées : quantité (dominante) au-dessus du stock. */
  .sp-mvt__figures {
    flex: none;
    text-align: right;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 3px;
  }
  .sp-mvt__figures .sp-qty {
    font-size: var(--text-lg);
    font-weight: var(--fw-semibold);
  }
  .sp-mvt__stock-line {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
  }
  .sp-mvt__stock-line svg {
    flex: none;
  }

  /* Spécialité + CIP13 fusionnés (nom, puis CIP mono dessous). */
  .sp-mvt__spec {
    display: block;
    margin-top: var(--space-4);
  }
  .sp-mvt__spec-name {
    display: block;
    font-weight: var(--fw-semibold);
    color: var(--text-strong);
    line-height: 1.3;
  }
  .sp-mvt__cip {
    display: block;
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    color: var(--text-muted);
    margin-top: 3px;
  }
  .sp-mvt__note {
    margin: 4px 0 0;
    font-size: var(--text-xs);
    color: var(--text-muted);
    line-height: 1.4;
  }

  /* Méta : tiers, opérateur, état (clé / valeur). */
  .sp-mvt__meta {
    margin: var(--space-4) 0 0;
    display: grid;
    grid-template-columns: 84px 1fr;
    gap: var(--space-2) var(--space-3);
  }
  .sp-mvt__meta dt {
    font-size: var(--text-2xs);
    text-transform: uppercase;
    letter-spacing: var(--tracking-caps);
    color: var(--text-muted);
    align-self: center;
  }
  .sp-mvt__meta dd {
    margin: 0;
    font-size: var(--text-sm);
    color: var(--text-strong);
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 6px;
  }
  .sp-mvt__meta dd .mono {
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    color: var(--text-muted);
  }
  .sp-mvt__meta .sp-add-link {
    min-height: 40px;
  }
}

/* =====================================================================
   SIDEBAR REPLIABLE EN RAIL DE PICTOS (desktop uniquement) — canonique
   ui_kits/registre/sidebar-collapse.html. Déplié par défaut ;
   .sp-app--rail active le rail (bascule Stimulus persistée, sans JS on
   reste déplié). Le sens des icônes est porté par leur title.
   ===================================================================== */
.sp-collapse-btn {
  display: none; /* visible seulement au desktop */
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  min-height: 44px;
  padding: 0 var(--space-5);
  border: 0;
  border-top: var(--border-width) solid var(--border-subtle);
  background: var(--surface-card);
  color: var(--text-muted);
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  font-weight: var(--fw-medium);
  cursor: pointer;
}
.sp-collapse-btn:hover {
  background: var(--surface-hover);
  color: var(--text-strong);
}
.sp-collapse-btn__chev {
  flex: none;
  transition: transform var(--duration-fast) var(--ease-standard);
}
.sp-collapse-btn__label {
  white-space: nowrap;
}

@media (min-width: 769px) {
  .sp-collapse-btn {
    display: flex;
  }

  .sp-app--rail .sp-sidebar {
    width: 68px;
  }
  /* Marque : capsule + croix seules (wordmark, tampon et chevron du
     switcher masqués au rail ; le menu du switcher reste accessible). */
  .sp-app--rail .sp-sidebar__brand {
    justify-content: center;
    padding: 0;
  }
  .sp-app--rail .sp-logo__wordmark,
  .sp-app--rail .sp-logo__stamp,
  .sp-app--rail .sp-switcher__chev {
    display: none;
  }
  /* Intitulés de groupe : RIEN ne les remplace au rail — ils n'ont pas de title,
     c'est le filet entre groupes (plus bas) qui marque la frontière à leur place. */
  .sp-app--rail .sp-nav-group__label {
    display: none;
  }
  /* Intitulé de module au rail : la même frontière vue de profil. Le nom tombe, le
     tampon prend sa place et devient le seul marqueur — un filet aurait séparé les
     deux étages sans nommer celui qui commence. */
  .sp-app--rail .sp-nav-module {
    justify-content: center;
    margin-top: var(--space-6);
  }
  .sp-app--rail .sp-nav-module__label {
    display: none;
  }
  /* Plus spécifique que le `.sp-app--rail .sp-logo__stamp { display: none }` qui
     dépouille le lockup ci-dessus : ce tampon-ci est justement celui qu'on garde. */
  .sp-app--rail .sp-nav-module .sp-logo__stamp--nav {
    display: inline-flex;
  }
  /* Libellé du lien masqué : il quitte l'arbre d'accessibilité et le nom du lien
     retombe sur son `title` (posé par <twig:NavLink>). C'est CETTE règle que le
     `title` de chaque entrée compense — la basculer les rendrait tous anonymes. */
  .sp-app--rail .sp-nav__label {
    display: none;
  }
  /* Lien sortant Jobs : au rail, pastille et flèche disparaissent
     (icône mallette seule + title, comme les autres entrées). */
  .sp-app--rail .rk-nav-out__ext,
  .sp-app--rail .rk-nav-out__dot {
    display: none;
  }
  .sp-app--rail .sp-nav {
    justify-content: center;
    padding-left: 0;
    padding-right: 0;
    gap: 0;
    position: relative;
  }
  /* Le filet du rail entre deux GROUPES. Il ne sépare plus l'étage racine du module :
     `.sp-nav-module` s'intercale et rompt l'adjacence, délibérément — là, c'est le
     tampon de nav qui marque la frontière, parce qu'un filet sépare sans nommer.
     Cf. le commentaire « ATTENTION, ADJACENCE » de `sp-nav-group + sp-nav-group`. */
  .sp-app--rail .sp-nav-group + .sp-nav-group {
    margin-top: var(--space-4);
    padding-top: var(--space-4);
    border-top: var(--border-width) solid var(--border-subtle);
  }
  /* Badge (ex. périmés) réduit en pastille d'angle sur l'icône. `font-size: 0`
     efface le chiffre à l'œil mais PAS de l'arbre d'accessibilité : la pastille
     garde son nom, qui prime alors sur le `title` du lien. */
  .sp-app--rail .sp-nav .sp-badge {
    position: absolute;
    top: 4px;
    right: 10px;
    min-width: 8px;
    height: 8px;
    padding: 0;
    font-size: 0;
    border-radius: 999px;
  }
  .sp-app--rail .sp-nav .sp-badge .sp-badge__icon {
    display: none;
  }
  /* Pied : avatar seul, identité et déconnexion masquées. */
  .sp-app--rail .sp-sidebar__user {
    justify-content: center;
    padding: var(--space-4) 0;
  }
  .sp-app--rail .sp-sidebar__user-id {
    display: none;
  }
  .sp-app--rail .sp-sidebar__account {
    justify-content: center;
  }
  .sp-app--rail .sp-sidebar__user .sp-icon-btn {
    display: none;
  }
  /* Affordance : le libellé disparaît, le chevron pivote. */
  .sp-app--rail .sp-collapse-btn {
    justify-content: center;
    padding: 0;
  }
  .sp-app--rail .sp-collapse-btn__label {
    display: none;
  }
  .sp-app--rail .sp-collapse-btn__chev {
    transform: rotate(180deg);
  }
}

/* Très petits écrans : KPIs en 1 colonne si vraiment étroit. */
@media (max-width: 360px) {
  .sp-stat-grid {
    grid-template-columns: 1fr;
  }
}

/* =====================================================================
   PIED DE REPÈRES — .sp-legal (<twig:PiedReperes>)
   Transcrit du canonique `components/Surfaces/PiedReperes/`. Rend les
   repères légaux atteignables là où le pied public ne va pas (LCEN
   art. 1-1 : identification de l'éditeur ; RGPD art. 13 : information
   des personnes).

   Ce n'est PAS un `.em-footer` réduit : les deux pieds ne coexistent
   jamais sur un même écran. La règle de pose et la frontière entre eux
   appartiennent au gabarit, pas à cette feuille — voir
   lat.md/design-system.md#Chrome de page : la chrome suit le principal.

   Vit dans le DS partagé et non dans jobs.css parce que le DESSIN est
   commun aux deux surfaces (classes `.sp-*`). */
.sp-legal {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-4);
  font-size: var(--text-xs);
  color: var(--text-muted);
  line-height: 1.5;
}

.sp-legal__editor {
  font-weight: var(--fw-medium);
  color: var(--text-body);
}

.sp-legal__sep {
  color: var(--border-default);
}

/* Page courante : quand la ligne figure sur l'une des deux pages qu'elle
   référence, l'entrée n'est pas un lien mort qui boucle mais un <span
   aria-current="page">. Même poids que l'éditeur, sans soulignement. */
.sp-legal__current {
  color: var(--text-body);
  font-weight: var(--fw-medium);
}

.sp-legal a {
  color: var(--text-muted);
  text-decoration: none;
  border-bottom: 1px solid var(--border-default);
}

.sp-legal a:hover {
  color: var(--text-body);
  border-bottom-color: var(--text-muted);
}

.sp-legal a:focus-visible {
  outline: none;
  box-shadow: var(--shadow-focus);
  border-radius: 2px;
}

/* Déclinaison 1 — colonne de travail : hub Jobs, gestion d'annonce, pages
   de repères. La ligne clôt la colonne, alignée sur elle, séparée par un
   filet fin. Elle vient APRÈS le dernier bloc utile et ne concurrence
   aucune action.

   NE PAS la poser sur l'app shell du REGISTRE tant que le registre n'a pas
   ses propres pages de repères : le composant pointe les routes du module
   Emploi, et `path()` les génère SANS erreur même quand JOBS_ENABLED=0 —
   la faute serait donc silencieuse, deux liens morts sur l'instance la
   plus réglementée. Motif complet dans
   lat.md/design-system.md#Chrome de page : la chrome suit le principal. */
.sp-legal--shell {
  margin-top: var(--space-9);
  padding-top: var(--space-5);
  border-top: var(--border-width) solid var(--border-subtle);
}

/* Déclinaison 2 — scène Typeform (tunnel de dépôt) : la scène est
   volontairement dépouillée et centrée, toute l'attention doit rester sur
   la question posée. La ligne est centrée sous la scène, sans filet, plus
   espacée et plus discrète encore : présente sans jamais entrer dans le
   champ de décision. La discrétion se joue sur la position, l'espacement
   et le contraste, JAMAIS en descendant sous le plancher typographique de
   12 px du projet (--text-xs) : une mention légalement exigée rendue sous
   le seuil de lisibilité (public 40+) est une dérive, pas de la
   discrétion. */
.sp-legal--stage {
  justify-content: center;
  max-width: 680px;
  margin: 0 auto;
  padding: var(--space-8) var(--space-6) var(--space-7);
}

.sp-legal--stage a {
  border-bottom-color: transparent;
}

.sp-legal--stage a:hover {
  border-bottom-color: var(--border-default);
}

/* Mobile : la ligne s'enroule au besoin mais reste une LIGNE, jamais un
   pavé. Le repli resserre les espacements ; le corps de texte, lui, ne
   bouge pas (cf. plancher ci-dessus). */
@media (max-width: 560px) {
  .sp-legal {
    gap: var(--space-2) var(--space-3);
  }

  .sp-legal--shell {
    margin-top: var(--space-7);
    padding-top: var(--space-4);
  }

  .sp-legal--stage {
    padding: var(--space-6) var(--space-4);
  }
}
