/* ==========================================================================
   MVM — DESIGN SYSTEM
   Socle unique. Ce fichier est la seule source de vérité pour les tokens
   et les primitives d'interface.

   PALETTE OFFICIELLE — 4 couleurs de marque, aucune autre teinte.
     --ds-ink    #0B0F19  encre        texte principal, nav, footer, titres
     --ds-paper  #FAFAF9  blanc cassé  fonds de page
     --ds-muted  #6B7280  gris neutre  texte secondaire, bordures, disabled
     --ds-accent #2563EB  bleu         CTA, liens, focus, états actifs

   SEULE EXCEPTION : --ds-danger (#DC2626), rouge FONCTIONNEL réservé aux
   erreurs de formulaire et aux actions destructrices (opposition carte,
   suppression). Ce n'est PAS une couleur de marque : jamais en décoration.

   Toute variation (surface, hover, bordure, fond subtil) est dérivée de ces
   teintes via color-mix(). N'introduisez jamais une 5e teinte.
   Le blanc pur et le noir pur sont achromatiques : ils servent uniquement
   d'agents d'éclaircissement/assombrissement dans les color-mix(), pas de
   couleur de marque.
   ========================================================================== */

/* ==========================================================================
   1. TOKENS
   ========================================================================== */

:root {
  /* ---------- 1.1 Couleurs de marque (immuables) ---------- */
  --ds-ink: #0B0F19;
  --ds-paper: #FAFAF9;
  --ds-muted: #6B7280;
  --ds-accent: #2563EB;

  /* Exception fonctionnelle — erreurs & actions destructrices uniquement */
  --ds-danger: #DC2626;

  /* ---------- 1.2 Surfaces ---------- */
  --ds-surface: var(--ds-paper);
  --ds-surface-raised: color-mix(in srgb, var(--ds-paper) 40%, #FFFFFF);
  --ds-surface-sunken: color-mix(in srgb, var(--ds-paper) 94%, var(--ds-ink));
  --ds-surface-inverse: var(--ds-ink);
  /* Voile d'overlay (modale, drawer) */
  --ds-overlay: color-mix(in srgb, var(--ds-ink) 55%, transparent);

  /* ---------- 1.3 Bordures ---------- */
  /* --ds-border        : séparateur DÉCORATIF (1px hairline, 1.46:1).
       Exempté de WCAG 1.4.11 : il ne porte aucune information. À n'utiliser
       que pour des filets, jamais pour délimiter un composant interactif. */
  /* --ds-border-strong : bordure PORTEUSE d'info (inputs, boutons secondaires)
       #888D98 → 3.18:1 sur papier, 3.27:1 sur surface-raised (seuil 3:1). */
  /* --ds-border-inverse : contour d'un composant posé sur l'encre
       (.ds-btn--inverse). Relevé de 16% → 36% de blanc : 1.57:1 était très
       en dessous du seuil 3:1 de WCAG 1.4.11. Désormais 3.30:1. */
  --ds-border: color-mix(in srgb, var(--ds-muted) 30%, var(--ds-paper));
  --ds-border-strong: color-mix(in srgb, var(--ds-muted) 80%, var(--ds-paper));
  --ds-border-inverse: color-mix(in srgb, #FFFFFF 36%, transparent);

  /* ---------- 1.4 Textes ---------- */
  --ds-text: var(--ds-ink);                                   /* ≈ 18.3:1 */
  /* --ds-text-muted : assombri de var(--ds-muted) vers l'encre (88%).
       #6B7280 brut passait sur papier (4.63:1) mais ÉCHOUAIT sur toutes les
       surfaces teintées : surface-sunken 4.09:1 (en-têtes de tableau),
       accent-subtle 4.04:1 (alertes info), danger-subtle 4.04:1,
       muted-subtle 4.00:1 (badges neutres). #5F6674 tient partout :
       papier 5.51:1 · raised 5.66:1 · sunken 4.87:1 · subtles 4.77→4.81:1. */
  --ds-text-muted: color-mix(in srgb, var(--ds-muted) 88%, var(--ds-ink));
  /* Décoratif UNIQUEMENT (séparateur de fil d'Ariane) — 3.07:1, jamais du texte */
  --ds-text-subtle: color-mix(in srgb, var(--ds-muted) 78%, var(--ds-paper));
  --ds-text-inverse: var(--ds-paper);
  --ds-text-inverse-muted: color-mix(in srgb, var(--ds-paper) 72%, var(--ds-ink));
  /* Désactivé : WCAG exempte les contrôles inactifs, mais 2.10:1 les rendait
     illisibles. Relevé à 85% de gris → 3.48:1 sur papier, 3.01:1 sur le fond
     muted-subtle des boutons désactivés, tout en restant nettement plus clair
     que --ds-text-muted : l'état inactif reste lisible d'un coup d'œil. */
  --ds-text-disabled: color-mix(in srgb, var(--ds-muted) 85%, var(--ds-paper));
  --ds-text-on-accent: #FFFFFF;                               /* ≈ 5.17:1 */

  /* ---------- 1.5 États accent ---------- */
  --ds-accent-hover: color-mix(in srgb, var(--ds-accent) 84%, var(--ds-ink));
  --ds-accent-active: color-mix(in srgb, var(--ds-accent) 70%, var(--ds-ink));
  --ds-accent-subtle: color-mix(in srgb, var(--ds-accent) 10%, var(--ds-paper));
  /* Bordure de badge / d'alerte info : c'est elle qui délimite le composant,
     le fond accent-subtle étant presque indiscernable du papier. Relevée de
     40% → 80% d'accent : 1.79:1 → 3.51:1 sur papier, 3.07:1 sur accent-subtle. */
  --ds-accent-border: color-mix(in srgb, var(--ds-accent) 80%, var(--ds-paper));
  --ds-accent-ring: color-mix(in srgb, var(--ds-accent) 30%, transparent);
  /* Accent LISIBLE SUR FOND ENCRE (header, footer, cartes inversées).
     var(--ds-accent) brut ne fait que 3.71:1 sur l'encre : suffisant pour un
     anneau de focus (seuil 3:1) mais INSUFFISANT pour du texte ou un lien.
     Utilisez ce token à la place → 10.24:1 sur l'encre. */
  --ds-accent-inverse: color-mix(in srgb, var(--ds-accent) 40%, var(--ds-paper));

  /* ---------- 1.6 États ink (surfaces sombres / neutres) ---------- */
  --ds-ink-hover: color-mix(in srgb, var(--ds-ink) 88%, #FFFFFF);
  --ds-ink-active: color-mix(in srgb, var(--ds-ink) 78%, #FFFFFF);
  --ds-ink-subtle: color-mix(in srgb, var(--ds-ink) 6%, var(--ds-paper));

  /* ---------- 1.7 États muted ---------- */
  --ds-muted-subtle: color-mix(in srgb, var(--ds-muted) 12%, var(--ds-paper));
  --ds-muted-hover: color-mix(in srgb, var(--ds-muted) 20%, var(--ds-paper));

  /* ---------- 1.8 États danger (fonctionnel) ---------- */
  --ds-danger-hover: color-mix(in srgb, var(--ds-danger) 84%, var(--ds-ink));
  --ds-danger-active: color-mix(in srgb, var(--ds-danger) 70%, var(--ds-ink));
  --ds-danger-subtle: color-mix(in srgb, var(--ds-danger) 9%, var(--ds-paper));
  /* Même correction que --ds-accent-border : 42% → 80% de rouge.
     1.97:1 → 3.65:1 sur papier, 3.18:1 sur danger-subtle (seuil 3:1). */
  --ds-danger-border: color-mix(in srgb, var(--ds-danger) 80%, var(--ds-paper));
  --ds-danger-ring: color-mix(in srgb, var(--ds-danger) 28%, transparent);
  --ds-text-on-danger: #FFFFFF;

  /* ---------- 1.9 Typographie : familles ---------- */
  --ds-font-sans: 'Geist', system-ui, -apple-system, 'Segoe UI', Roboto,
    'Helvetica Neue', Arial, sans-serif;
  /* Departure Mono : montants, IBAN, références, codes — jamais du texte courant */
  --ds-font-mono: 'Departure Mono', ui-monospace, 'SF Mono', SFMono-Regular,
    Menlo, Monaco, Consolas, monospace;

  /* ---------- 1.10 Typographie : échelle fluide (clamp) ---------- */
  --ds-text-xs: clamp(0.75rem, 0.74rem + 0.05vw, 0.78125rem);      /* 12 → 12.5 */
  --ds-text-sm: clamp(0.875rem, 0.86rem + 0.08vw, 0.9375rem);      /* 14 → 15   */
  --ds-text-base: clamp(1rem, 0.98rem + 0.1vw, 1.0625rem);         /* 16 → 17   */
  --ds-text-lg: clamp(1.125rem, 1.08rem + 0.22vw, 1.25rem);        /* 18 → 20   */
  --ds-text-xl: clamp(1.3125rem, 1.23rem + 0.4vw, 1.5rem);         /* 21 → 24   */
  --ds-text-2xl: clamp(1.5rem, 1.34rem + 0.8vw, 1.875rem);         /* 24 → 30   */
  --ds-text-3xl: clamp(1.875rem, 1.6rem + 1.35vw, 2.5rem);         /* 30 → 40   */
  --ds-text-4xl: clamp(2.25rem, 1.7rem + 2.75vw, 3.5rem);          /* 36 → 56   */

  /* ---------- 1.11 Typographie : hauteurs de ligne ---------- */
  --ds-leading-none: 1;
  --ds-leading-tight: 1.15;
  --ds-leading-snug: 1.3;
  --ds-leading-normal: 1.5;
  --ds-leading-relaxed: 1.65;

  /* ---------- 1.12 Typographie : graisses & interlettrage ---------- */
  --ds-weight-regular: 400;
  --ds-weight-medium: 500;
  --ds-weight-semibold: 600;
  --ds-weight-bold: 700;

  --ds-tracking-tight: -0.02em;
  --ds-tracking-normal: 0;
  --ds-tracking-wide: 0.02em;
  --ds-tracking-caps: 0.06em;

  /* Longueur de ligne confortable */
  --ds-measure: 65ch;

  /* ---------- 1.13 Espacement — base 4px ---------- */
  --ds-space-1: 0.25rem;   /*  4px */
  --ds-space-2: 0.5rem;    /*  8px */
  --ds-space-3: 0.75rem;   /* 12px */
  --ds-space-4: 1rem;      /* 16px */
  --ds-space-5: 1.25rem;   /* 20px */
  --ds-space-6: 1.5rem;    /* 24px */
  --ds-space-7: 1.75rem;   /* 28px */
  --ds-space-8: 2rem;      /* 32px */
  --ds-space-10: 2.5rem;   /* 40px */
  --ds-space-12: 3rem;     /* 48px */
  --ds-space-14: 3.5rem;   /* 56px */
  --ds-space-16: 4rem;     /* 64px */

  /* Respiration verticale de section, fluide (mobile → desktop) */
  --ds-section-y: clamp(2.5rem, 1.5rem + 5vw, 5rem);
  --ds-gutter: clamp(1rem, 0.6rem + 2vw, 2rem);

  /* ---------- 1.14 Rayons ---------- */
  --ds-radius-sm: 0.375rem;  /*  6px — badges, petits champs */
  --ds-radius-md: 0.625rem;  /* 10px — boutons, inputs */
  --ds-radius-lg: 1rem;      /* 16px — cartes, modales */
  --ds-radius-full: 9999px;

  /* ---------- 1.15 Élévations (sobres, jamais lourdes) ---------- */
  --ds-shadow-sm: 0 1px 2px color-mix(in srgb, var(--ds-ink) 6%, transparent);
  --ds-shadow-md: 0 4px 14px color-mix(in srgb, var(--ds-ink) 8%, transparent);

  /* ---------- 1.16 Mouvement ---------- */
  --ds-transition: 160ms ease-out;
  --ds-transition-slow: 200ms ease-out;

  /* ---------- 1.17 Layout ---------- */
  --ds-container: 1200px;
  --ds-container-narrow: 720px;
  --ds-container-wide: 1440px;

  /* Cibles tactiles — WCAG 2.5.5 */
  --ds-touch-target: 44px;

  /* Focus visible — identique partout */
  --ds-focus-width: 2px;
  --ds-focus-offset: 2px;
  --ds-focus-color: var(--ds-accent);

  /* ---------- 1.18 Empilement ---------- */
  --ds-z-base: 1;
  --ds-z-dropdown: 1000;
  --ds-z-sticky: 1020;
  --ds-z-fixed: 1030;
  --ds-z-backdrop: 1040;
  --ds-z-modal: 1050;
  --ds-z-popover: 1060;
  --ds-z-tooltip: 1070;
  --ds-z-skip-link: 100000;

  /* ---------- 1.19 Breakpoints (documentation — mobile first) ---------- */
  --ds-bp-sm: 640px;
  --ds-bp-md: 768px;
  --ds-bp-lg: 1024px;
  --ds-bp-xl: 1280px;
}

/* ==========================================================================
   2. RESET MODERNE
   ========================================================================== */

*,
*::before,
*::after {
  box-sizing: border-box;
}

* {
  margin: 0;
}

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: var(--ds-space-16);
  tab-size: 4;
}

body {
  min-height: 100svh;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

img,
picture,
video,
canvas,
svg {
  display: block;
  max-width: 100%;
  height: auto;
}

input,
button,
textarea,
select {
  font: inherit;
  color: inherit;
}

button {
  background: none;
  border: none;
  cursor: pointer;
}

p,
h1,
h2,
h3,
h4,
h5,
h6 {
  overflow-wrap: break-word;
}

ul[class],
ol[class] {
  list-style: none;
  padding: 0;
}

table {
  border-collapse: collapse;
  border-spacing: 0;
}

:target {
  scroll-margin-block: var(--ds-space-16);
}

/* Focus visible unique et systématique.
   NB : on ne force PAS de border-radius ici. L'ancienne règle réécrivait le
   rayon de l'élément au moment du focus — un bouton icône ou un badge en
   radius-full sautait visuellement à 6px dès la tabulation, et le contour
   ne suivait plus la forme réelle du composant. L'outline épouse déjà le
   border-radius natif de l'élément dans tous les navigateurs modernes. */
:focus-visible {
  outline: var(--ds-focus-width) solid var(--ds-focus-color);
  outline-offset: var(--ds-focus-offset);
}

/* Sur une surface encre (header, footer, panneau inversé), l'accent ne fait
   que 3.71:1 : ça passe le seuil 3:1 d'un indicateur de focus, mais c'est
   juste. On bascule sur le papier (18.3:1) pour un focus franchement visible. */
.ds-surface-inverse :focus-visible,
[data-surface='inverse'] :focus-visible {
  outline-color: var(--ds-text-inverse);
}

/* ==========================================================================
   3. PRIMITIVES — LAYOUT
   ========================================================================== */

.ds-container {
  width: 100%;
  max-width: var(--ds-container);
  margin-inline: auto;
  padding-inline: var(--ds-gutter);
}

.ds-container--narrow { max-width: var(--ds-container-narrow); }
.ds-container--wide { max-width: var(--ds-container-wide); }

/* Empilement vertical : gap réglable via --ds-stack-gap */
.ds-stack {
  display: flex;
  flex-direction: column;
  gap: var(--ds-stack-gap, var(--ds-space-4));
}

.ds-stack--tight { --ds-stack-gap: var(--ds-space-2); }
.ds-stack--loose { --ds-stack-gap: var(--ds-space-8); }

/* Groupe horizontal qui passe à la ligne */
.ds-cluster {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ds-cluster-gap, var(--ds-space-3));
}

.ds-cluster--between { justify-content: space-between; }
.ds-cluster--end { justify-content: flex-end; }
.ds-cluster--center { justify-content: center; }

/* Grille auto-fit : jamais de débordement à 320px grâce au min() */
.ds-grid {
  display: grid;
  gap: var(--ds-grid-gap, var(--ds-space-6));
  grid-template-columns: repeat(
    auto-fit,
    minmax(min(100%, var(--ds-grid-min, 260px)), 1fr)
  );
}

.ds-grid--2 { --ds-grid-min: 320px; }
.ds-grid--3 { --ds-grid-min: 240px; }
.ds-grid--4 { --ds-grid-min: 180px; }

.ds-section {
  padding-block: var(--ds-section-y);
}

.ds-divider {
  border: 0;
  border-top: 1px solid var(--ds-border);
  margin-block: var(--ds-space-6);
}

/* ==========================================================================
   4. PRIMITIVES — BOUTONS
   ========================================================================== */

.ds-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--ds-space-2);
  min-height: var(--ds-touch-target);
  padding: var(--ds-space-3) var(--ds-space-6);
  border: 1px solid transparent;
  border-radius: var(--ds-radius-md);
  font-family: var(--ds-font-sans);
  font-size: var(--ds-text-sm);
  font-weight: var(--ds-weight-semibold);
  line-height: var(--ds-leading-none);
  letter-spacing: var(--ds-tracking-normal);
  text-align: center;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  user-select: none;
  transition:
    background-color var(--ds-transition),
    border-color var(--ds-transition),
    color var(--ds-transition),
    box-shadow var(--ds-transition);
}

.ds-btn:focus-visible {
  outline: var(--ds-focus-width) solid var(--ds-focus-color);
  outline-offset: var(--ds-focus-offset);
}

.ds-btn:disabled,
.ds-btn[aria-disabled='true'],
.ds-btn.is-disabled {
  cursor: not-allowed;
  background-color: var(--ds-muted-subtle);
  border-color: var(--ds-border);
  color: var(--ds-text-disabled);
  box-shadow: none;
}

/* --- Primaire : une seule par écran --- */
.ds-btn--primary {
  background-color: var(--ds-accent);
  color: var(--ds-text-on-accent);
  border-color: var(--ds-accent);
}
.ds-btn--primary:hover:not(:disabled):not(.is-disabled) {
  background-color: var(--ds-accent-hover);
  border-color: var(--ds-accent-hover);
}
.ds-btn--primary:active:not(:disabled):not(.is-disabled) {
  background-color: var(--ds-accent-active);
  border-color: var(--ds-accent-active);
}

/* --- Secondaire : contour encre --- */
.ds-btn--secondary {
  background-color: transparent;
  color: var(--ds-text);
  border-color: var(--ds-border-strong);
}
.ds-btn--secondary:hover:not(:disabled):not(.is-disabled) {
  background-color: var(--ds-ink-subtle);
  border-color: var(--ds-ink);
}
.ds-btn--secondary:active:not(:disabled):not(.is-disabled) {
  background-color: var(--ds-muted-hover);
}

/* --- Ghost : action tertiaire --- */
.ds-btn--ghost {
  background-color: transparent;
  color: var(--ds-accent);
  border-color: transparent;
}
.ds-btn--ghost:hover:not(:disabled):not(.is-disabled) {
  background-color: var(--ds-accent-subtle);
  color: var(--ds-accent-hover);
}
.ds-btn--ghost:active:not(:disabled):not(.is-disabled) {
  background-color: color-mix(in srgb, var(--ds-accent) 18%, var(--ds-paper));
}

/* --- Danger : suppression, opposition carte --- */
.ds-btn--danger {
  background-color: var(--ds-danger);
  color: var(--ds-text-on-danger);
  border-color: var(--ds-danger);
}
.ds-btn--danger:hover:not(:disabled):not(.is-disabled) {
  background-color: var(--ds-danger-hover);
  border-color: var(--ds-danger-hover);
}
.ds-btn--danger:active:not(:disabled):not(.is-disabled) {
  background-color: var(--ds-danger-active);
  border-color: var(--ds-danger-active);
}
.ds-btn--danger:focus-visible {
  outline-color: var(--ds-danger);
}

/* --- Sur fond encre (header, footer) --- */
.ds-btn--inverse {
  background-color: transparent;
  color: var(--ds-text-inverse);
  border-color: var(--ds-border-inverse);
}
.ds-btn--inverse:hover:not(:disabled):not(.is-disabled) {
  background-color: color-mix(in srgb, #FFFFFF 10%, transparent);
  border-color: color-mix(in srgb, #FFFFFF 28%, transparent);
}

/* --- Tailles --- */
.ds-btn--sm {
  min-height: 36px;
  padding: var(--ds-space-2) var(--ds-space-4);
  font-size: var(--ds-text-xs);
  border-radius: var(--ds-radius-sm);
}
.ds-btn--lg {
  min-height: 52px;
  padding: var(--ds-space-4) var(--ds-space-8);
  font-size: var(--ds-text-base);
}

.ds-btn--block {
  display: flex;
  width: 100%;
}

/* Bouton icône seule — garde la cible tactile */
.ds-btn--icon {
  padding: 0;
  width: var(--ds-touch-target);
  min-width: var(--ds-touch-target);
}

/* --- État loading --- */
.ds-btn.is-loading,
.ds-btn--loading {
  position: relative;
  cursor: progress;
  pointer-events: none;
  color: transparent;
}

/* opacity, PAS visibility : `visibility: hidden` retire le libellé de l'arbre
   d'accessibilité, et un bouton en cours d'envoi se retrouvait sans nom
   accessible (« bouton », rien de plus, au lecteur d'écran). L'opacité le
   masque visuellement tout en le laissant lisible par les technologies
   d'assistance. Pensez à poser aria-busy="true" pendant l'envoi. */
.ds-btn.is-loading > *,
.ds-btn--loading > * {
  opacity: 0;
}

.ds-btn.is-loading::after,
.ds-btn--loading::after {
  content: '';
  position: absolute;
  inset: 50% auto auto 50%;
  width: 1.125em;
  height: 1.125em;
  margin: -0.5625em 0 0 -0.5625em;
  border: 2px solid currentColor;
  border-radius: var(--ds-radius-full);
  border-top-color: transparent;
  color: var(--ds-text-on-accent);
  animation: ds-spin 700ms linear infinite;
}

.ds-btn--secondary.is-loading::after,
.ds-btn--ghost.is-loading::after,
.ds-btn--secondary.ds-btn--loading::after,
.ds-btn--ghost.ds-btn--loading::after {
  color: var(--ds-accent);
}

@keyframes ds-spin {
  to { transform: rotate(360deg); }
}

/* ==========================================================================
   5. PRIMITIVES — FORMULAIRES
   ========================================================================== */

.ds-field {
  display: flex;
  flex-direction: column;
  gap: var(--ds-space-2);
}

.ds-label {
  font-size: var(--ds-text-sm);
  font-weight: var(--ds-weight-medium);
  line-height: var(--ds-leading-snug);
  color: var(--ds-text);
}

/* L'astérisque est une FORME, pas une couleur : l'obligation reste perçue en
   niveaux de gris. --ds-danger-hover plutôt que --ds-danger : 5.98:1 au lieu
   de 4.62:1, et le glyphe est petit donc il a besoin de marge.
   Côté markup, l'attribut `required` (ou aria-required) reste obligatoire :
   un ::after n'est pas restitué de façon fiable par les lecteurs d'écran. */
.ds-label--required::after {
  content: ' *';
  color: var(--ds-danger-hover);
}

.ds-field-hint {
  font-size: var(--ds-text-xs);
  line-height: var(--ds-leading-normal);
  color: var(--ds-text-muted);
}

/* --ds-danger-hover et non --ds-danger : le message d'erreur est du petit
   texte (12px) et il peut être posé sur --ds-danger-subtle (dans une alerte),
   où --ds-danger ne fait que 4.03:1. --ds-danger-hover donne 5.98:1 sur papier
   et 5.22:1 sur danger-subtle.
   L'erreur ne doit JAMAIS être signalée par la seule couleur : ce bloc
   s'accompagne d'une icône (.ds-field-error > svg) et l'input porte
   aria-invalid="true" + aria-describedby vers ce message. */
.ds-field-error {
  display: flex;
  align-items: flex-start;
  gap: var(--ds-space-2);
  font-size: var(--ds-text-xs);
  line-height: var(--ds-leading-normal);
  font-weight: var(--ds-weight-medium);
  color: var(--ds-danger-hover);
}

/* L'icône du message d'erreur ne rétrécit pas et reste alignée sur la 1re ligne */
.ds-field-error > svg,
.ds-field-error > .ds-field-error__icon {
  flex: 0 0 auto;
  width: 1em;
  height: 1em;
  margin-top: 0.2em;
}

.ds-input,
.ds-select,
.ds-textarea {
  width: 100%;
  min-height: var(--ds-touch-target);
  padding: var(--ds-space-3) var(--ds-space-4);
  background-color: var(--ds-surface-raised);
  border: 1px solid var(--ds-border-strong);
  border-radius: var(--ds-radius-md);
  font-family: var(--ds-font-sans);
  font-size: var(--ds-text-base);
  line-height: var(--ds-leading-normal);
  color: var(--ds-text);
  transition:
    border-color var(--ds-transition),
    box-shadow var(--ds-transition),
    background-color var(--ds-transition);
}

/* Placeholder au ratio AA (4.66:1) — jamais --ds-text-subtle, trop clair */
.ds-input::placeholder,
.ds-textarea::placeholder {
  color: var(--ds-text-muted);
  opacity: 1;
}

.ds-input:hover:not(:disabled),
.ds-select:hover:not(:disabled),
.ds-textarea:hover:not(:disabled) {
  border-color: var(--ds-muted);
}

.ds-input:focus-visible,
.ds-select:focus-visible,
.ds-textarea:focus-visible,
.ds-input:focus,
.ds-select:focus,
.ds-textarea:focus {
  outline: var(--ds-focus-width) solid var(--ds-focus-color);
  outline-offset: var(--ds-focus-offset);
  border-color: var(--ds-accent);
}

.ds-input:disabled,
.ds-select:disabled,
.ds-textarea:disabled {
  background-color: var(--ds-muted-subtle);
  border-color: var(--ds-border);
  color: var(--ds-text-disabled);
  cursor: not-allowed;
}

.ds-input[readonly],
.ds-textarea[readonly] {
  background-color: var(--ds-surface-sunken);
}

/* État erreur — piloté par aria-invalid ou par la classe */
.ds-input[aria-invalid='true'],
.ds-select[aria-invalid='true'],
.ds-textarea[aria-invalid='true'],
.ds-input.is-invalid,
.ds-select.is-invalid,
.ds-textarea.is-invalid {
  border-color: var(--ds-danger);
  background-color: var(--ds-danger-subtle);
}

.ds-input[aria-invalid='true']:focus-visible,
.ds-select[aria-invalid='true']:focus-visible,
.ds-textarea[aria-invalid='true']:focus-visible,
.ds-input.is-invalid:focus-visible,
.ds-select.is-invalid:focus-visible,
.ds-textarea.is-invalid:focus-visible {
  outline-color: var(--ds-danger);
}

.ds-textarea {
  min-height: 7.5rem;
  resize: vertical;
}

/* Montants, IBAN, références */
.ds-input--mono,
.ds-mono {
  font-family: var(--ds-font-mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--ds-tracking-wide);
}

.ds-select {
  appearance: none;
  padding-right: var(--ds-space-10);
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position:
    calc(100% - 20px) calc(50% + 2px),
    calc(100% - 14px) calc(50% + 2px);
  background-size: 6px 6px, 6px 6px;
  background-repeat: no-repeat;
}

/* --- Case à cocher & radio --- */
.ds-checkbox,
.ds-radio {
  display: inline-flex;
  align-items: flex-start;
  gap: var(--ds-space-3);
  min-height: var(--ds-touch-target);
  padding-block: var(--ds-space-2);
  font-size: var(--ds-text-sm);
  line-height: var(--ds-leading-normal);
  color: var(--ds-text);
  cursor: pointer;
}

.ds-checkbox input[type='checkbox'],
.ds-radio input[type='radio'] {
  appearance: none;
  flex: 0 0 auto;
  width: 1.25rem;
  height: 1.25rem;
  margin: 0;
  margin-top: 0.125rem;
  background-color: var(--ds-surface-raised);
  border: 1px solid var(--ds-border-strong);
  cursor: pointer;
  transition:
    background-color var(--ds-transition),
    border-color var(--ds-transition);
}

.ds-checkbox input[type='checkbox'] {
  border-radius: var(--ds-radius-sm);
}

.ds-radio input[type='radio'] {
  border-radius: var(--ds-radius-full);
}

.ds-checkbox input[type='checkbox']:hover:not(:disabled),
.ds-radio input[type='radio']:hover:not(:disabled) {
  border-color: var(--ds-accent);
}

.ds-checkbox input[type='checkbox']:checked,
.ds-radio input[type='radio']:checked {
  background-color: var(--ds-accent);
  border-color: var(--ds-accent);
}

/* Coche blanche — SVG inline, aucune requête réseau */
.ds-checkbox input[type='checkbox']:checked {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23FFFFFF' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 8.5 6.4 12 13 4.5'/%3E%3C/svg%3E");
  background-size: 78% 78%;
  background-position: center;
  background-repeat: no-repeat;
}

.ds-radio input[type='radio']:checked {
  box-shadow: inset 0 0 0 3px var(--ds-surface-raised);
}

.ds-checkbox input[type='checkbox']:focus-visible,
.ds-radio input[type='radio']:focus-visible {
  outline: var(--ds-focus-width) solid var(--ds-focus-color);
  outline-offset: var(--ds-focus-offset);
}

.ds-checkbox input[type='checkbox']:disabled,
.ds-radio input[type='radio']:disabled {
  background-color: var(--ds-muted-subtle);
  border-color: var(--ds-border);
  cursor: not-allowed;
}

.ds-checkbox:has(input:disabled),
.ds-radio:has(input:disabled) {
  color: var(--ds-text-disabled);
  cursor: not-allowed;
}

/* --- Interrupteur --- */
.ds-switch {
  display: inline-flex;
  align-items: center;
  gap: var(--ds-space-3);
  min-height: var(--ds-touch-target);
  font-size: var(--ds-text-sm);
  color: var(--ds-text);
  cursor: pointer;
}

.ds-switch input[type='checkbox'] {
  appearance: none;
  position: relative;
  flex: 0 0 auto;
  width: 2.75rem;
  height: 1.5rem;
  margin: 0;
  background-color: var(--ds-muted);
  border: 1px solid transparent;
  border-radius: var(--ds-radius-full);
  cursor: pointer;
  transition: background-color var(--ds-transition);
}

.ds-switch input[type='checkbox']::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 3px;
  width: 1.125rem;
  height: 1.125rem;
  transform: translateY(-50%);
  background-color: #FFFFFF;
  border-radius: var(--ds-radius-full);
  transition: left var(--ds-transition);
}

.ds-switch input[type='checkbox']:checked {
  background-color: var(--ds-accent);
}

.ds-switch input[type='checkbox']:checked::after {
  left: calc(100% - 1.125rem - 3px);
}

.ds-switch input[type='checkbox']:hover:not(:disabled) {
  background-color: var(--ds-muted-hover);
}

.ds-switch input[type='checkbox']:checked:hover:not(:disabled) {
  background-color: var(--ds-accent-hover);
}

.ds-switch input[type='checkbox']:focus-visible {
  outline: var(--ds-focus-width) solid var(--ds-focus-color);
  outline-offset: var(--ds-focus-offset);
}

.ds-switch input[type='checkbox']:disabled {
  background-color: var(--ds-border);
  cursor: not-allowed;
}

.ds-switch:has(input:disabled) {
  color: var(--ds-text-disabled);
  cursor: not-allowed;
}

/* ==========================================================================
   6. PRIMITIVES — CARTE
   ========================================================================== */

.ds-card {
  display: flex;
  flex-direction: column;
  gap: var(--ds-space-4);
  padding: var(--ds-space-6);
  background-color: var(--ds-surface-raised);
  border: 1px solid var(--ds-border);
  border-radius: var(--ds-radius-lg);
  color: var(--ds-text);
}

.ds-card--flush { padding: 0; }
.ds-card--sunken { background-color: var(--ds-surface-sunken); }
.ds-card--raised { box-shadow: var(--ds-shadow-sm); }

/* Carte cliquable */
.ds-card--interactive {
  text-decoration: none;
  cursor: pointer;
  transition:
    border-color var(--ds-transition),
    box-shadow var(--ds-transition),
    background-color var(--ds-transition);
}
.ds-card--interactive:hover {
  border-color: var(--ds-border-strong);
  box-shadow: var(--ds-shadow-md);
}
.ds-card--interactive:focus-visible {
  outline: var(--ds-focus-width) solid var(--ds-focus-color);
  outline-offset: var(--ds-focus-offset);
}
.ds-card--interactive:active {
  background-color: var(--ds-ink-subtle);
}

.ds-card--selected {
  border-color: var(--ds-accent);
  box-shadow: inset 0 0 0 1px var(--ds-accent);
}

.ds-card__header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--ds-space-3);
}

.ds-card__title {
  font-size: var(--ds-text-lg);
  font-weight: var(--ds-weight-semibold);
  line-height: var(--ds-leading-snug);
  letter-spacing: var(--ds-tracking-tight);
  color: var(--ds-text);
}

.ds-card__body {
  font-size: var(--ds-text-base);
  line-height: var(--ds-leading-relaxed);
  color: var(--ds-text-muted);
}

.ds-card__footer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ds-space-3);
  padding-top: var(--ds-space-4);
  border-top: 1px solid var(--ds-border);
}

/* ==========================================================================
   7. PRIMITIVES — BADGE
   ========================================================================== */

.ds-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--ds-space-1);
  padding: 0.25rem var(--ds-space-2);
  background-color: var(--ds-muted-subtle);
  border: 1px solid var(--ds-border);
  border-radius: var(--ds-radius-full);
  font-size: var(--ds-text-xs);
  font-weight: var(--ds-weight-medium);
  line-height: var(--ds-leading-snug);
  letter-spacing: var(--ds-tracking-wide);
  color: var(--ds-text-muted);
  white-space: nowrap;
}

/* Neutre / en attente */
.ds-badge--neutral {
  background-color: var(--ds-muted-subtle);
  border-color: var(--ds-border);
  color: var(--ds-text-muted);
}

/* Actif / validé / positif */
.ds-badge--success,
.ds-badge--active {
  background-color: var(--ds-accent-subtle);
  border-color: var(--ds-accent-border);
  color: var(--ds-accent-hover);
}

/* Information */
.ds-badge--info {
  background-color: var(--ds-accent-subtle);
  border-color: var(--ds-accent-border);
  color: var(--ds-accent-hover);
}

/* Attention — traitée en encre, pas en couleur décorative */
.ds-badge--warning {
  background-color: var(--ds-ink-subtle);
  border-color: var(--ds-border-strong);
  color: var(--ds-text);
}

/* Erreur / rejeté / opposé */
.ds-badge--danger,
.ds-badge--error {
  background-color: var(--ds-danger-subtle);
  border-color: var(--ds-danger-border);
  color: var(--ds-danger-hover);
}

/* Fort contraste (compteurs, mises en avant) */
.ds-badge--solid {
  background-color: var(--ds-ink);
  border-color: var(--ds-ink);
  color: var(--ds-text-inverse);
}

.ds-badge--dot::before {
  content: '';
  width: 0.375rem;
  height: 0.375rem;
  border-radius: var(--ds-radius-full);
  background-color: currentColor;
}

/* ==========================================================================
   8. PRIMITIVES — ALERTE
   ========================================================================== */

.ds-alert {
  display: flex;
  align-items: flex-start;
  gap: var(--ds-space-3);
  padding: var(--ds-space-4);
  background-color: var(--ds-surface-raised);
  border: 1px solid var(--ds-border);
  border-left: 3px solid var(--ds-muted);
  border-radius: var(--ds-radius-md);
  font-size: var(--ds-text-sm);
  line-height: var(--ds-leading-relaxed);
  color: var(--ds-text);
}

.ds-alert__icon {
  flex: 0 0 auto;
  width: 1.25rem;
  height: 1.25rem;
  margin-top: 0.125rem;
}

.ds-alert__content {
  flex: 1 1 auto;
  min-width: 0;
}

.ds-alert__title {
  display: block;
  margin-bottom: var(--ds-space-1);
  font-size: var(--ds-text-sm);
  font-weight: var(--ds-weight-semibold);
}

.ds-alert__close {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  margin: -0.25rem -0.25rem -0.25rem 0;
  border-radius: var(--ds-radius-sm);
  color: var(--ds-text-muted);
  transition: background-color var(--ds-transition), color var(--ds-transition);
}
.ds-alert__close:hover {
  background-color: var(--ds-muted-subtle);
  color: var(--ds-text);
}

.ds-alert--info {
  background-color: var(--ds-accent-subtle);
  border-color: var(--ds-accent-border);
  border-left-color: var(--ds-accent);
}

.ds-alert--success {
  background-color: var(--ds-accent-subtle);
  border-color: var(--ds-accent-border);
  border-left-color: var(--ds-accent);
}

.ds-alert--warning {
  background-color: var(--ds-ink-subtle);
  border-color: var(--ds-border-strong);
  border-left-color: var(--ds-ink);
}

.ds-alert--error,
.ds-alert--danger {
  background-color: var(--ds-danger-subtle);
  border-color: var(--ds-danger-border);
  border-left-color: var(--ds-danger);
}

/* ==========================================================================
   9. PRIMITIVES — TABLEAU (responsive)
   ========================================================================== */

/* Conteneur obligatoire autour de .ds-table : c'est lui qui scrolle. */
.ds-table-wrap {
  width: 100%;
  max-width: 100%;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch;
  border: 1px solid var(--ds-border);
  border-radius: var(--ds-radius-lg);
  background-color: var(--ds-surface-raised);
}

.ds-table-wrap:focus-visible {
  outline: var(--ds-focus-width) solid var(--ds-focus-color);
  outline-offset: var(--ds-focus-offset);
}

.ds-table {
  width: 100%;
  min-width: 36rem;
  border-collapse: collapse;
  font-size: var(--ds-text-sm);
  line-height: var(--ds-leading-normal);
  color: var(--ds-text);
}

.ds-table caption {
  padding: var(--ds-space-4);
  text-align: left;
  font-size: var(--ds-text-sm);
  color: var(--ds-text-muted);
}

.ds-table thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  padding: var(--ds-space-3) var(--ds-space-4);
  background-color: var(--ds-surface-sunken);
  border-bottom: 1px solid var(--ds-border-strong);
  font-size: var(--ds-text-xs);
  font-weight: var(--ds-weight-semibold);
  letter-spacing: var(--ds-tracking-caps);
  text-transform: uppercase;
  text-align: left;
  color: var(--ds-text-muted);
  white-space: nowrap;
}

.ds-table tbody td {
  padding: var(--ds-space-4);
  border-bottom: 1px solid var(--ds-border);
  vertical-align: middle;
}

.ds-table tbody tr:last-child td {
  border-bottom: 0;
}

.ds-table tbody tr:hover td {
  background-color: var(--ds-ink-subtle);
}

.ds-table__num,
.ds-table td.ds-table__num,
.ds-table th.ds-table__num {
  text-align: right;
  font-family: var(--ds-font-mono);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.ds-table--compact tbody td { padding: var(--ds-space-2) var(--ds-space-3); }

/* ==========================================================================
   10. PRIMITIVES — ONGLETS
   ========================================================================== */

.ds-tabs {
  display: flex;
  gap: var(--ds-space-1);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: thin;
  border-bottom: 1px solid var(--ds-border);
}

.ds-tab {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: var(--ds-space-2);
  min-height: var(--ds-touch-target);
  padding: var(--ds-space-3) var(--ds-space-4);
  border: 0;
  background: none;
  font-size: var(--ds-text-sm);
  font-weight: var(--ds-weight-medium);
  line-height: var(--ds-leading-none);
  color: var(--ds-text-muted);
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: color var(--ds-transition), background-color var(--ds-transition);
}

.ds-tab::after {
  content: '';
  position: absolute;
  left: var(--ds-space-2);
  right: var(--ds-space-2);
  bottom: -1px;
  height: 2px;
  border-radius: 2px;
  background-color: transparent;
  transition: background-color var(--ds-transition);
}

.ds-tab:hover {
  color: var(--ds-text);
  background-color: var(--ds-ink-subtle);
}

.ds-tab:focus-visible {
  outline: var(--ds-focus-width) solid var(--ds-focus-color);
  outline-offset: calc(var(--ds-focus-offset) * -1);
}

.ds-tab--active,
.ds-tab.is-active,
.ds-tab[aria-selected='true'] {
  color: var(--ds-accent);
  font-weight: var(--ds-weight-semibold);
}

.ds-tab--active::after,
.ds-tab.is-active::after,
.ds-tab[aria-selected='true']::after {
  background-color: var(--ds-accent);
}

.ds-tab:disabled,
.ds-tab[aria-disabled='true'] {
  color: var(--ds-text-disabled);
  cursor: not-allowed;
}

.ds-tabpanel {
  padding-top: var(--ds-space-6);
}

.ds-tabpanel[hidden] { display: none; }

/* ==========================================================================
   11. PRIMITIVES — MODALE
   ========================================================================== */

.ds-modal {
  position: fixed;
  inset: 0;
  z-index: var(--ds-z-modal);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding: 0;
}

.ds-modal[hidden],
.ds-modal:not(.is-open):not([open]) {
  display: none;
}

.ds-modal__backdrop {
  position: fixed;
  inset: 0;
  z-index: var(--ds-z-backdrop);
  background-color: var(--ds-overlay);
}

.ds-modal__panel {
  position: relative;
  z-index: calc(var(--ds-z-modal) + 1);
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 34rem;
  max-height: 92svh;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--ds-space-6);
  background-color: var(--ds-surface-raised);
  border: 1px solid var(--ds-border);
  border-radius: var(--ds-radius-lg) var(--ds-radius-lg) 0 0;
  box-shadow: var(--ds-shadow-md);
  animation: ds-modal-in var(--ds-transition-slow);
}

@keyframes ds-modal-in {
  from { opacity: 0; transform: translateY(12px); }
  to { opacity: 1; transform: none; }
}

.ds-modal__header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--ds-space-4);
  margin-bottom: var(--ds-space-4);
}

.ds-modal__title {
  font-size: var(--ds-text-xl);
  font-weight: var(--ds-weight-semibold);
  line-height: var(--ds-leading-snug);
  letter-spacing: var(--ds-tracking-tight);
  color: var(--ds-text);
}

.ds-modal__close {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--ds-touch-target);
  height: var(--ds-touch-target);
  margin: calc(var(--ds-space-2) * -1) calc(var(--ds-space-2) * -1) 0 0;
  border-radius: var(--ds-radius-md);
  color: var(--ds-text-muted);
  transition: background-color var(--ds-transition), color var(--ds-transition);
}
.ds-modal__close:hover {
  background-color: var(--ds-muted-subtle);
  color: var(--ds-text);
}

.ds-modal__body {
  flex: 1 1 auto;
  font-size: var(--ds-text-base);
  line-height: var(--ds-leading-relaxed);
  color: var(--ds-text-muted);
}

.ds-modal__footer {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--ds-space-3);
  margin-top: var(--ds-space-6);
}

.ds-modal__footer > .ds-btn {
  flex: 1 1 auto;
}

@media (min-width: 640px) {
  .ds-modal {
    align-items: center;
    padding: var(--ds-space-6);
  }
  .ds-modal__panel {
    border-radius: var(--ds-radius-lg);
    max-height: 85svh;
  }
  .ds-modal__footer > .ds-btn {
    flex: 0 0 auto;
  }
}

/* ==========================================================================
   12. PRIMITIVES — SKELETON
   ========================================================================== */

.ds-skeleton {
  display: block;
  width: 100%;
  height: 1em;
  border-radius: var(--ds-radius-sm);
  background-color: var(--ds-muted-subtle);
  background-image: linear-gradient(
    90deg,
    transparent 0%,
    color-mix(in srgb, var(--ds-muted) 18%, transparent) 50%,
    transparent 100%
  );
  background-size: 200% 100%;
  background-repeat: no-repeat;
  animation: ds-shimmer 1.4s ease-in-out infinite;
}

.ds-skeleton--text { height: 0.75em; margin-block: 0.25em; }
.ds-skeleton--title { height: 1.5em; width: 60%; }
.ds-skeleton--line-short { width: 45%; }
.ds-skeleton--block { height: 8rem; border-radius: var(--ds-radius-lg); }
.ds-skeleton--circle {
  width: 2.5rem;
  height: 2.5rem;
  border-radius: var(--ds-radius-full);
}

@keyframes ds-shimmer {
  from { background-position: 150% 0; }
  to { background-position: -50% 0; }
}

/* ==========================================================================
   13. ACCESSIBILITÉ
   ========================================================================== */

.ds-sr-only {
  position: absolute !important;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Visible seulement au focus clavier */
.ds-sr-only-focusable:not(:focus):not(:focus-within) {
  position: absolute !important;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.ds-skip-link {
  position: absolute;
  top: 0;
  left: 0;
  z-index: var(--ds-z-skip-link);
  transform: translateY(-120%);
  padding: var(--ds-space-3) var(--ds-space-6);
  background-color: var(--ds-accent);
  color: var(--ds-text-on-accent);
  font-size: var(--ds-text-sm);
  font-weight: var(--ds-weight-semibold);
  text-decoration: none;
  border-bottom-right-radius: var(--ds-radius-md);
  transition: transform var(--ds-transition);
}

.ds-skip-link:focus {
  transform: translateY(0);
  outline: var(--ds-focus-width) solid var(--ds-ink);
  outline-offset: var(--ds-focus-offset);
}

/* Neutralise tout mouvement pour les utilisateurs sensibles */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    animation-delay: 0ms !important;
    transition-duration: 0.01ms !important;
    transition-delay: 0ms !important;
    scroll-behavior: auto !important;
  }

  html { scroll-behavior: auto; }

  .ds-skeleton {
    animation: none !important;
    background-image: none;
  }

  .ds-btn.is-loading::after,
  .ds-btn--loading::after {
    animation: none !important;
    border-top-color: currentColor;
    opacity: 0.5;
  }
}

/* Renforcement si l'utilisateur demande plus de contraste.
   L'ancien --ds-border à 60% ne montait qu'à 2.27:1 : en mode contraste
   renforcé, même les filets décoratifs doivent franchir 3:1. */
@media (prefers-contrast: more) {
  :root {
    --ds-border: color-mix(in srgb, var(--ds-muted) 85%, var(--ds-paper));  /* 3.48:1 */
    --ds-border-strong: var(--ds-muted);                                    /* 4.63:1 */
    --ds-border-inverse: color-mix(in srgb, #FFFFFF 55%, transparent);      /* 6.23:1 sur encre */
    --ds-text-muted: color-mix(in srgb, var(--ds-muted) 78%, var(--ds-ink));/* 6.41:1 */
    --ds-text-disabled: var(--ds-muted);                                    /* 4.63:1 */
    --ds-focus-width: 3px;
  }

  /* --ds-text-subtle est décoratif et sous 3:1 : en contraste renforcé on
     le remonte au niveau du texte secondaire plutôt que de le laisser filer. */
  :root {
    --ds-text-subtle: var(--ds-text-muted);
  }
}

/* ==========================================================================
   14. UTILITAIRES MINIMAUX
   ========================================================================== */

.ds-text-muted { color: var(--ds-text-muted); }
/* Variantes -hover et non les teintes brutes : ces utilitaires atterrissent
   souvent sur un fond teinté (alerte, badge, carte sélectionnée) où
   --ds-danger tombe à 4.03:1 et --ds-accent à 4.32:1. Les variantes tiennent
   partout : danger-hover 5.22→5.98:1, accent-hover 5.43→6.39:1. */
.ds-text-danger { color: var(--ds-danger-hover); }
.ds-text-accent { color: var(--ds-accent-hover); }
.ds-measure { max-width: var(--ds-measure); }
.ds-tabular { font-variant-numeric: tabular-nums; }
.ds-truncate {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ds-scroll-x {
  overflow-x: auto;
  overscroll-behavior-x: contain;
}

/* ==========================================================================
   15. SIGNAUX NON COLORIMÉTRIQUES  (WCAG 1.4.1 « Utilisation de la couleur »)

   Aucune information ne doit reposer sur la seule couleur. Un crédit et un
   débit qui ne diffèrent que par le rouge et le vert/bleu sont indistincts
   pour un daltonien, en niveaux de gris et à l'impression.
   Ces classes ajoutent un SIGNE typographique — donc une forme — en plus de
   la teinte.

   Attention : n'ajoutez pas .ds-amount--out sur un montant dont le markup
   écrit déjà « − » ou « + », le signe serait doublé.
   ========================================================================== */

.ds-amount {
  font-family: var(--ds-font-mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--ds-tracking-wide);
  white-space: nowrap;
}

/* Entrée / crédit — signe plus + libellé pour lecteur d'écran côté markup */
.ds-amount--in::before {
  content: '+\00A0';
}

/* Sortie / débit — U+2212 SIGNE MOINS, pas un trait d'union */
.ds-amount--out::before {
  content: '\2212\00A0';
}

.ds-amount--in { color: var(--ds-accent-hover); }   /* 6.23:1 sur papier */
.ds-amount--out { color: var(--ds-text); }          /* 18.3:1 — le signe suffit */

/* Variante de tendance : la flèche porte l'information, la couleur l'appuie */
.ds-trend {
  display: inline-flex;
  align-items: center;
  gap: var(--ds-space-1);
  font-variant-numeric: tabular-nums;
}
.ds-trend--up::before { content: '\2191'; }         /* ↑ */
.ds-trend--down::before { content: '\2193'; }       /* ↓ */
.ds-trend--flat::before { content: '\2192'; }       /* → */
.ds-trend--up { color: var(--ds-accent-hover); }
.ds-trend--down { color: var(--ds-text); }

/* ==========================================================================
   16. MODE COULEURS FORCÉES (Windows contraste élevé)
   Les fonds/bordures peints en color-mix() sont écrasés par le système :
   on s'assure que les composants restent délimités et le focus visible.
   ========================================================================== */

@media (forced-colors: active) {
  .ds-btn,
  .ds-input,
  .ds-select,
  .ds-textarea,
  .ds-card,
  .ds-badge,
  .ds-alert,
  .ds-table-wrap,
  .ds-modal__panel {
    border: 1px solid CanvasText;
  }

  .ds-btn:focus-visible,
  .ds-input:focus-visible,
  .ds-select:focus-visible,
  .ds-textarea:focus-visible,
  .ds-tab:focus-visible,
  .ds-card--interactive:focus-visible {
    outline: 3px solid Highlight;
    outline-offset: 2px;
  }

  /* L'état actif d'un onglet ne peut plus passer par la couleur : on garde
     le liseré, que le mode couleurs forcées conserve via CanvasText. */
  .ds-tab--active::after,
  .ds-tab.is-active::after,
  .ds-tab[aria-selected='true']::after {
    background-color: CanvasText;
    forced-color-adjust: none;
  }

  /* Sans cela le fond accent est effacé par le système et l'état « coché »
     devient invisible. Le liseré épais garantit que l'état reste perçu même
     si le glyphe blanc se confond avec un thème clair. */
  .ds-checkbox input[type='checkbox']:checked,
  .ds-radio input[type='radio']:checked {
    forced-color-adjust: none;
    background-color: Highlight;
    border: 2px solid CanvasText;
  }
}
