/* =============================================================================
   MAREMBAL — FEUILLE DE L'APPLICATION
   =============================================================================

   Configurateur de cotation, mini-CRM, formulaire de contact. Tout ce qui porte
   de l'état, de la validation ou une écriture — et qui reste donc en React.

   Elle est chargée EN PLUS de site.css, jamais à la place : boutons, plaque,
   tableaux, filets, chips de spécification et courbes de mouvement viennent de
   là-bas. Ce fichier n'ajoute que ce qui n'existe pas dans une vitrine : des
   contrôles de saisie, une machine à étapes, un panneau latéral, une table de
   données dense.

   C'est ce qui fait que le configurateur ressemble au site plutôt qu'à un
   formulaire posé derrière lui. Le passage de l'un à l'autre ne doit pas se
   voir — c'est le moment où un acheteur décide si la maison est sérieuse.

   ── Sommaire ───────────────────────────────────────────────────────────────
    1. Coque applicative
    2. Rail d'étapes
    3. Contrôles de saisie
    4. Choix : cartes et segments
    5. Pièces jointes
    6. Récapitulatif et plaque vivante
    7. Confirmation
    8. Mini-CRM
    9. Mouvement réduit
   ============================================================================= */

/* =============================================================================
   1. COQUE APPLICATIVE
   ============================================================================= */

.app {
  --e-sortie: var(--ease-sortie, cubic-bezier(0.16, 1, 0.3, 1));
  --e-doux: var(--ease-doux, cubic-bezier(0.4, 0, 0.2, 1));
  --e-franc: var(--ease-franc, cubic-bezier(0.7, 0, 0.2, 1));
  --e-ressort: var(--ease-ressort, cubic-bezier(0.34, 1.56, 0.64, 1));

  --filet: var(--color-rule);
  --filet-fort: var(--color-navy-500);
  --fond: var(--color-paper);
  --fond-creux: color-mix(in srgb, var(--color-kraft-200) 38%, var(--color-paper));
  --sur: var(--color-navy-900);
  --sur-faible: var(--color-ink-500);

  min-height: 100dvh;
  background: var(--color-paper);
  display: flex;
  flex-direction: column;
}

/* --- Barre d'application --------------------------------------------------
   Volontairement différente de la barre du site : ici on travaille, on ne
   navigue plus. Pas de méga-menu, pas d'appel à l'action — une seule sortie,
   clairement nommée. */

.app-barre {
  position: sticky;
  top: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 0 clamp(16px, 3vw, 40px);
  height: 66px;
  background: var(--color-navy-900);
  color: var(--color-paper);
  border-bottom: 1px solid var(--color-navy-700);
}

.app-marque {
  display: flex;
  align-items: center;
  gap: 14px;
  text-decoration: none;
  min-width: 0;
  transition: opacity 200ms var(--e-doux);
}

.app-marque:hover { opacity: 0.86; }

/* Le logo, et le nom de l'écran séparé de lui par un filet vertical. Deux
   informations de nature différente — qui édite, et quoi — que rien ne doit
   laisser lire comme une seule ligne. */
.app-marque .logo { --logo-l: 126px; flex: 0 0 auto; }

.app-marque span::before {
  content: '';
  display: inline-block;
  width: 1px;
  height: 14px;
  margin-right: 14px;
  vertical-align: -2px;
  background: var(--color-navy-500);
}

.app-marque span {
  font-family: var(--font-sans);
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--color-paper) 52%, transparent);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.app-barre-droite {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 0 0 auto;
}

/* Bouton discret de la barre : le contour n'apparaît qu'au survol. */
.app-bouton {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 8px 14px;
  border: 1px solid transparent;
  border-radius: var(--radius-controle, 6px);
  background: transparent;
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  font-weight: 500;
  color: color-mix(in srgb, var(--color-paper) 78%, transparent);
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
  transition: color 200ms var(--e-doux), border-color 200ms var(--e-doux),
    background-color 200ms var(--e-doux);
}

.app-bouton:hover {
  color: var(--color-paper);
  border-color: var(--color-navy-500);
  background: var(--color-navy-700);
}

.app-bouton--or { color: var(--color-gold-300); }
.app-bouton--or:hover { color: var(--color-navy-900); background: var(--color-gold-500); border-color: var(--color-gold-500); }

/* Sur un écran étroit, la barre lâche dans l'ordre inverse de l'utilité : le
   nom de l'écran d'abord — il est déjà dans le titre de l'onglet — puis
   l'identité du connecté, qui n'est pas une action. Le logo, lui, ne bouge
   pas : c'est la seule chose de cette barre qui dise où l'on se trouve, et
   trois boutons qui débordent le poussaient jusqu'ici hors de l'écran. */
@media (width < 46rem) {
  .app-marque span { display: none; }
  .app-marque .logo { --logo-l: 108px; }
  .app-barre { gap: 8px; }
  .app-bouton { padding: 8px 10px; }
}

@media (width < 34rem) {
  .app-bouton--identite { display: none; }
}

.app-corps {
  flex: 1;
  width: 100%;
  max-width: 1360px;
  margin-inline: auto;
  padding: clamp(28px, 4vw, 56px) clamp(16px, 3vw, 40px) clamp(40px, 6vw, 96px);
}

/* =============================================================================
   2. RAIL D'ÉTAPES
   =============================================================================
   Six jalons numérotés reliés par un trait qui se remplit.

   Il dit trois choses d'un coup d'œil, et c'est exactement ce qu'un acheteur
   veut savoir avant de s'engager dans un formulaire : où il en est, combien il
   reste, et qu'il peut revenir en arrière sans rien perdre.
   ============================================================================= */

.rail {
  position: sticky;
  top: 66px;
  z-index: 30;
  background: color-mix(in srgb, var(--color-paper) 92%, transparent);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--color-rule);
}

.rail-inner {
  width: 100%;
  max-width: 1360px;
  margin-inline: auto;
  padding: 14px clamp(16px, 3vw, 40px);
  display: flex;
  align-items: center;
  gap: clamp(12px, 2vw, 28px);
}

.rail-jalons {
  display: flex;
  align-items: center;
  flex: 1;
  min-width: 0;
  list-style: none;
  margin: 0;
  padding: 0;
}

.rail-jalon {
  display: flex;
  align-items: center;
  flex: 1;
  min-width: 0;
}

.rail-jalon:last-child { flex: 0 0 auto; }

.rail-pastille {
  position: relative;
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  flex: 0 0 auto;
  border: 1px solid var(--color-rule);
  border-radius: 50%;
  background: var(--color-paper);
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 0.6875rem;
  color: var(--color-ink-500);
  cursor: default;
  padding: 0;
  transition: border-color 320ms var(--e-doux), background-color 320ms var(--e-doux),
    color 320ms var(--e-doux), transform 320ms var(--e-ressort);
}

/* Étape franchie : pleine, et cliquable pour y revenir. */
.rail-jalon[data-etat='faite'] .rail-pastille {
  background: var(--color-navy-900);
  border-color: var(--color-navy-900);
  color: var(--color-paper);
  cursor: pointer;
}

.rail-jalon[data-etat='faite'] .rail-pastille:hover {
  background: var(--color-gold-500);
  border-color: var(--color-gold-500);
  color: var(--color-navy-900);
  transform: scale(1.12);
}

/* Étape courante : l'anneau d'or, et un halo qui la détache. */
.rail-jalon[data-etat='courante'] .rail-pastille {
  border-color: var(--color-gold-500);
  color: var(--color-navy-900);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--color-gold-500) 18%, transparent);
  font-weight: 500;
}

.rail-trait {
  flex: 1;
  height: 1px;
  min-width: 10px;
  margin-inline: 8px;
  background: var(--color-rule);
  position: relative;
  overflow: hidden;
}

.rail-trait::after {
  content: '';
  position: absolute;
  inset: 0;
  background: var(--teinte, var(--color-navy-900));
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 520ms var(--e-sortie);
}

.rail-jalon[data-etat='faite'] .rail-trait::after { transform: scaleX(1); }

.rail-libelle {
  flex: 0 0 auto;
  display: flex;
  align-items: baseline;
  gap: 10px;
  text-align: right;
}

.rail-libelle b {
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  font-weight: 500;
  color: var(--color-navy-900);
  white-space: nowrap;
}

.rail-libelle span {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 0.75rem;
  color: var(--color-ink-500);
  white-space: nowrap;
}

/* Sous 52 rem, les jalons deviennent une simple barre : six pastilles de 30 px
   plus leurs traits ne tiennent pas, et les tasser les rendrait illisibles. */
@media (width < 52rem) {
  .rail-jalons { display: none; }
  .rail-inner { gap: 12px; }
  .rail-barre { display: block; }
}

.rail-barre {
  display: none;
  flex: 1;
  height: 3px;
  border-radius: 2px;
  background: var(--color-rule);
  overflow: hidden;
}

.rail-barre i {
  display: block;
  height: 100%;
  background: var(--teinte, var(--color-navy-900));
  transform: scaleX(var(--avance, 0));
  transform-origin: left;
  transition: transform 520ms var(--e-sortie);
}

/* =============================================================================
   3. GRILLE DU CONFIGURATEUR
   ============================================================================= */

.conf {
  display: grid;
  gap: clamp(28px, 4vw, 64px);
  align-items: start;
}

@media (width >= 64rem) {
  .conf { grid-template-columns: minmax(0, 1.25fr) minmax(0, 0.75fr); }
}

.conf-tete { margin-bottom: clamp(26px, 3vw, 38px); }

.conf-num {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 0.6875rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--color-kraft-500);
}

.conf-titre {
  margin: 12px 0 0;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(1.7rem, 1.2rem + 1.6vw, 2.5rem);
  line-height: 1.08;
  letter-spacing: -0.02em;
  color: var(--color-navy-900);
  outline: none;
  text-wrap: balance;
}

.conf-chapeau {
  margin: 14px 0 0;
  max-width: 62ch;
  font-family: var(--font-sans);
  font-size: 1.0625rem;
  line-height: 1.6;
  color: var(--color-ink-500);
  text-wrap: pretty;
}

/* --- Transition d'étape ---------------------------------------------------
   L'étape entrante glisse dans le sens de la marche : depuis la droite quand on
   avance, depuis la gauche quand on revient. Le sens du geste est ainsi lisible
   avant même d'avoir lu le titre.

   L'animation est jouée à l'entrée seulement. Animer aussi la sortie
   obligerait à garder deux étapes montées en même temps, donc à dupliquer
   l'état du formulaire — pour un gain que personne ne remarque. */

.etape {
  animation: etape-entre 520ms var(--e-sortie) both;
}

.etape[data-sens='arriere'] {
  animation-name: etape-entre-arriere;
}

@keyframes etape-entre {
  from { opacity: 0; transform: translate3d(26px, 0, 0); }
  to { opacity: 1; transform: none; }
}

@keyframes etape-entre-arriere {
  from { opacity: 0; transform: translate3d(-26px, 0, 0); }
  to { opacity: 1; transform: none; }
}

/* Les champs d'une étape arrivent en cascade derrière elle. */
.etape-grille {
  display: grid;
  gap: clamp(18px, 2.2vw, 26px);
}

@media (width >= 40rem) {
  .etape-grille { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .etape-grille > .pleine { grid-column: 1 / -1; }
}

.etape-grille > * {
  animation: champ-entre 460ms var(--e-sortie) both;
  animation-delay: calc(90ms + var(--rang, 0) * 45ms);
}

@keyframes champ-entre {
  from { opacity: 0; transform: translate3d(0, 12px, 0); }
  to { opacity: 1; transform: none; }
}

/* --- Barre d'action ------------------------------------------------------- */

.conf-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  margin-top: clamp(30px, 4vw, 44px);
  padding-top: 24px;
  border-top: 1px solid var(--color-rule);
}

.conf-actions .app-note {
  margin-left: auto;
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  color: var(--color-ink-500);
}

/* =============================================================================
   4. CONTRÔLES DE SAISIE
   =============================================================================
   Contrôles natifs, habillés. Navigation clavier complète, sélecteur système
   sur mobile, aucun JavaScript de composant — et le comportement que
   l'utilisateur connaît déjà.
   ============================================================================= */

.champ { display: block; min-width: 0; }

.champ-label {
  display: flex;
  align-items: baseline;
  gap: 8px;
  font-family: var(--font-sans);
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--color-ink-900);
}

.champ-label em {
  font-style: normal;
  font-size: 0.75rem;
  font-weight: 400;
  color: var(--color-ink-500);
}

.champ-label i {
  font-style: normal;
  color: var(--color-gold-500);
}

.champ-aide {
  margin: 5px 0 0;
  max-width: 60ch;
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  line-height: 1.5;
  color: var(--color-ink-500);
}

.champ-boite { position: relative; margin-top: 9px; }

.champ-saisie {
  width: 100%;
  padding: 13px 15px;
  border: 1px solid var(--color-rule);
  border-radius: var(--radius-controle, 6px);
  background: var(--color-paper);
  font-family: var(--font-sans);
  font-size: 1rem;
  line-height: 1.4;
  color: var(--color-ink-900);
  transition: border-color 220ms var(--e-doux), box-shadow 220ms var(--e-doux),
    background-color 220ms var(--e-doux);
  -webkit-appearance: none;
  appearance: none;
}

.champ-saisie::placeholder { color: var(--color-ink-500); opacity: 0.75; }

.champ-saisie:hover { border-color: var(--color-navy-500); }

/* Le halo de mise au point est doux et large : il désigne le champ actif sans
   donner l'impression d'une alerte. L'anneau or du focus clavier, lui, reste
   posé par-dessus par la règle globale. */
.champ-saisie:focus {
  outline: none;
  border-color: var(--color-navy-500);
  background: #fff;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-navy-500) 12%, transparent);
}

.champ-saisie[aria-invalid='true'] {
  border-color: var(--color-gold-500);
  background: color-mix(in srgb, var(--color-gold-300) 8%, var(--color-paper));
}

.champ-saisie[aria-invalid='true']:focus {
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-gold-500) 22%, transparent);
}

textarea.champ-saisie { min-height: 108px; resize: vertical; line-height: 1.55; }

/* Le chevron du select est dessiné en fond : aucun élément supplémentaire, et
   il suit la couleur du texte. */
select.champ-saisie {
  padding-right: 42px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' fill='none'%3E%3Cpath d='m1 1 4 4 4-4' stroke='%235a6472' stroke-width='1.4'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 15px center;
  cursor: pointer;
}

/* L'unité est un SUFFIXE du champ, jamais un placeholder : un placeholder
   disparaît à la première frappe, or c'est précisément pendant la saisie que
   l'acheteur a besoin de savoir s'il tape des grammes ou des kilos. */
.champ-unite {
  position: absolute;
  right: 15px;
  top: 50%;
  transform: translateY(-50%);
  pointer-events: none;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 0.875rem;
  color: var(--color-ink-500);
  opacity: 0.6;
  transition: opacity 200ms var(--e-doux), color 200ms var(--e-doux);
}

.champ-boite:has(.champ-saisie:not(:placeholder-shown)) .champ-unite,
.champ-unite[data-rempli] {
  opacity: 1;
  color: var(--color-ink-900);
}

.champ-saisie[data-avec-unite] { padding-right: 72px; }

/* --- Erreur ---------------------------------------------------------------
   Elle donne l'action corrective et un exemple. Jamais « champ invalide »,
   jamais d'excuse. Elle arrive par le haut, sans décaler ce qui suit d'un
   à-coup. */

.champ-erreur {
  display: flex;
  gap: 9px;
  margin: 9px 0 0;
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  line-height: 1.5;
  color: var(--color-navy-900);
  animation: erreur-entre 320ms var(--e-sortie) both;
}

.champ-erreur::before {
  content: '';
  flex: 0 0 auto;
  width: 12px;
  height: 3px;
  margin-top: 0.5em;
  background: var(--color-gold-500);
}

@keyframes erreur-entre {
  from { opacity: 0; transform: translate3d(0, -5px, 0); }
  to { opacity: 1; transform: none; }
}

/* =============================================================================
   5. CHOIX : CARTES ET CASES
   =============================================================================
   Sous cinq options, on montre tout : un menu déroulant cache les possibilités
   au moment précis où l'acheteur veut les comparer.
   ============================================================================= */

.choix {
  display: grid;
  gap: 10px;
  margin: 9px 0 0;
  padding: 0;
  border: 0;
}

@media (width >= 34rem) {
  .choix--deux { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.choix-carte {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 13px;
  padding: 15px 17px;
  border: 1px solid var(--color-rule);
  border-radius: var(--radius-controle, 6px);
  background: var(--color-paper);
  cursor: pointer;
  transition: border-color 240ms var(--e-doux), background-color 240ms var(--e-doux),
    transform 240ms var(--e-sortie);
}

.choix-carte:hover { border-color: var(--color-navy-500); }
.choix-carte:active { transform: scale(0.995); }

.choix-carte:has(input:checked) {
  border-color: var(--color-navy-900);
  background: var(--fond-creux);
}

/* Le liseré de matière apparaît sur la tranche quand la carte est retenue :
   c'est le même signal que sur la plaque, au même endroit. */
.choix-carte::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 3px;
  border-radius: var(--radius-controle, 6px) 0 0 var(--radius-controle, 6px);
  background: var(--teinte, var(--color-gold-500));
  transform: scaleY(0);
  transform-origin: center;
  transition: transform 320ms var(--e-ressort);
}

.choix-carte:has(input:checked)::before { transform: scaleY(1); }

/* La coche native est masquée, mais reste dans l'arbre d'accessibilité et
   garde tout son comportement clavier. */
.choix-carte input {
  position: absolute;
  opacity: 0;
  width: 1px;
  height: 1px;
  margin: 0;
}

.choix-marque {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 18px;
  height: 18px;
  margin-top: 2px;
  border: 1px solid var(--color-rule);
  border-radius: 50%;
  background: var(--color-paper);
  transition: border-color 240ms var(--e-doux), background-color 240ms var(--e-doux);
}

.choix-marque--carree { border-radius: 3px; }

.choix-marque::after {
  content: '';
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--color-paper);
  transform: scale(0);
  transition: transform 280ms var(--e-ressort);
}

.choix-marque--carree::after { border-radius: 1px; }

.choix-carte:has(input:checked) .choix-marque {
  border-color: var(--color-navy-900);
  background: var(--color-navy-900);
}

.choix-carte:has(input:checked) .choix-marque::after { transform: scale(1); }

.choix-carte:has(input:focus-visible) {
  outline: 2px solid var(--color-gold-500);
  outline-offset: 2px;
}

.choix-texte { min-width: 0; }

.choix-texte b {
  display: block;
  font-family: var(--font-sans);
  font-size: 0.9375rem;
  font-weight: 500;
  color: var(--color-ink-900);
}

.choix-texte span {
  display: block;
  margin-top: 4px;
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  line-height: 1.5;
  color: var(--color-ink-500);
}

.choix-code {
  margin-left: auto;
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.12em;
  color: var(--color-ink-500);
}

/* Cases à cocher en ligne, pour les champs à choix multiple. */
.choix--enligne {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.choix--enligne .choix-carte { padding: 10px 14px; align-items: center; }
.choix--enligne .choix-marque { margin-top: 0; }
.choix--enligne .choix-texte b { font-size: 0.875rem; }

/* =============================================================================
   6. PIÈCES JOINTES
   ============================================================================= */

.depot {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  margin-top: 10px;
  padding: 26px 20px;
  border: 1px dashed var(--color-rule);
  border-radius: var(--radius-controle, 6px);
  background: var(--fond-creux);
  cursor: pointer;
  text-align: center;
  transition: border-color 240ms var(--e-doux), background-color 240ms var(--e-doux);
}

.depot:hover { border-color: var(--color-navy-500); }
.depot:has(input:focus-visible) { outline: 2px solid var(--color-gold-500); outline-offset: 2px; }

.depot b {
  font-family: var(--font-sans);
  font-size: 0.9375rem;
  font-weight: 500;
  color: var(--color-navy-900);
}

.depot span {
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  color: var(--color-ink-500);
}

.depot input { position: absolute; opacity: 0; width: 1px; height: 1px; }

.fichiers {
  list-style: none;
  margin: 12px 0 0;
  padding: 0;
  border: 1px solid var(--color-rule);
  border-radius: var(--radius-technique, 2px);
  overflow: hidden;
}

.fichiers li {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 11px 15px;
  background: var(--color-paper);
  animation: champ-entre 340ms var(--e-sortie) both;
}

.fichiers li + li { border-top: 1px solid var(--color-rule); }

.fichiers b {
  font-family: var(--font-sans);
  font-size: 0.875rem;
  font-weight: 400;
  color: var(--color-ink-900);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.fichiers .taille {
  margin-left: auto;
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 0.75rem;
  color: var(--color-ink-500);
}

.fichiers button {
  flex: 0 0 auto;
  border: 0;
  background: none;
  padding: 4px;
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  color: var(--color-navy-500);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
  transition: color 200ms var(--e-doux);
}

.fichiers button:hover { color: var(--color-gold-500); }

/* =============================================================================
   7. RÉCAPITULATIF — LA PLAQUE VIVANTE
   =============================================================================
   La même plaque que sur les pages produit, qui se remplit à mesure. C'est le
   cœur de la démonstration : l'acheteur voit sa fiche technique se construire
   pendant qu'il répond, et il comprend qu'on l'écoute.
   ============================================================================= */

.recap { position: relative; }

@media (width >= 64rem) {
  .recap { position: sticky; top: 132px; }
}

.recap-tete {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 12px;
}

.recap-tete p {
  margin: 0;
  font-family: var(--font-sans);
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--color-ink-500);
}

.recap-completude {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 0.75rem;
  color: var(--color-ink-500);
}

/* Chaque ligne du récapitulatif apparaît quand sa valeur est saisie. */
.recap .plaque-spec { animation: recap-ligne 420ms var(--e-sortie) both; }

@keyframes recap-ligne {
  from { opacity: 0; transform: translate3d(0, -6px, 0); }
  to { opacity: 1; transform: none; }
}

.recap-vide {
  padding: 28px 24px;
  border: 1px dashed var(--color-rule);
  border-radius: var(--radius-technique, 2px);
  font-family: var(--font-sans);
  font-size: 0.875rem;
  line-height: 1.6;
  color: var(--color-ink-500);
}

.recap-modifier {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 16px;
  margin-top: 14px;
  padding-top: 13px;
  border-top: 1px solid var(--color-rule);
}

.recap-modifier button {
  border: 0;
  background: none;
  padding: 0;
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  color: var(--color-navy-500);
  text-decoration: underline;
  text-decoration-color: var(--color-rule);
  text-underline-offset: 4px;
  cursor: pointer;
  transition: color 200ms var(--e-doux), text-decoration-color 200ms var(--e-doux);
}

.recap-modifier button:hover {
  color: var(--color-gold-500);
  text-decoration-color: var(--color-gold-500);
}

/* Bandeau de reprise d'un brouillon. */
.reprise {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 14px;
  padding: 14px clamp(16px, 3vw, 40px);
  background: var(--fond-creux);
  border-bottom: 1px solid var(--color-rule);
}

.reprise p {
  margin: 0;
  flex: 1 1 20rem;
  font-family: var(--font-sans);
  font-size: 0.875rem;
  color: var(--color-ink-900);
}

.reprise-actions { display: flex; gap: 10px; flex: 0 0 auto; }

/* =============================================================================
   7b. PAGE CONTACT
   =============================================================================
   Une page de présentation qui porte un formulaire. Elle reprend la coque et
   les composants de la vitrine ; seul le bloc de saisie vient d'ici.
   ============================================================================= */

.contact-coords {
  display: grid;
  gap: 22px;
  margin: clamp(28px, 3vw, 40px) 0 0;
  padding-top: 26px;
  border-top: 1px solid var(--color-rule);
}

@media (width >= 34rem) {
  .contact-coords { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.contact-coords dt {
  font-family: var(--font-sans);
  font-size: 0.625rem;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--color-ink-500);
}

.contact-coords dd { margin: 8px 0 0; }

.contact-coords address {
  font-style: normal;
  font-family: var(--font-sans);
  font-size: 0.9375rem;
  line-height: 1.65;
  color: var(--color-ink-900);
}

/* Le téléphone est la première action de la page : il en a la taille. */
.contact-fort {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: clamp(1.15rem, 1rem + 0.6vw, 1.45rem);
  color: var(--color-navy-900);
  text-decoration: none;
  border-bottom: 1px solid var(--color-rule);
  padding-bottom: 3px;
  transition: color 220ms var(--e-doux), border-color 220ms var(--e-doux);
}

.contact-fort:hover { color: var(--color-gold-500); border-color: var(--color-gold-500); }

.contact-lien {
  font-family: var(--font-sans);
  font-size: 0.9375rem;
  color: var(--color-navy-500);
  text-decoration: underline;
  text-decoration-color: var(--color-rule);
  text-underline-offset: 4px;
  transition: color 220ms var(--e-doux), text-decoration-color 220ms var(--e-doux);
}

.contact-lien:hover { color: var(--color-gold-500); text-decoration-color: var(--color-gold-500); }

/* Le formulaire est posé sur une plaque : sur une page de présentation, une
   zone de saisie doit se distinguer du texte qui l'entoure, sans quoi on ne
   sait pas où commence le travail. */
.contact-formulaire {
  padding: clamp(22px, 2.6vw, 32px);
  border: 1px solid var(--color-rule);
  border-radius: var(--radius-technique, 2px);
  background: var(--color-paper);
  box-shadow: var(--ombre-pose);
}

.contact-formulaire .etape-grille > * { animation: none; }

/* =============================================================================
   8. CONFIRMATION
   ============================================================================= */

.confirme {
  max-width: 62rem;
  margin-inline: auto;
  padding-block: clamp(30px, 5vw, 60px);
}

/* Le sceau : un cercle qui se trace, puis une coche. Il dure moins d'une
   seconde. Au-delà, une animation de succès devient une attente de plus. */
.confirme-sceau {
  width: 62px;
  height: 62px;
  margin-bottom: 26px;
}

.confirme-sceau circle {
  fill: none;
  stroke: var(--color-gold-500);
  stroke-width: 1.5;
  stroke-dasharray: 189;
  stroke-dashoffset: 189;
  animation: sceau-trace 760ms var(--e-sortie) both;
}

.confirme-sceau path {
  fill: none;
  stroke: var(--color-navy-900);
  stroke-width: 2;
  stroke-linecap: square;
  stroke-dasharray: 34;
  stroke-dashoffset: 34;
  animation: sceau-trace 420ms var(--e-sortie) 560ms both;
}

@keyframes sceau-trace {
  to { stroke-dashoffset: 0; }
}

.confirme-grille {
  display: grid;
  gap: clamp(26px, 4vw, 52px);
  margin-top: clamp(26px, 3vw, 40px);
  align-items: start;
}

@media (width >= 60rem) {
  .confirme-grille { grid-template-columns: minmax(0, 1fr) minmax(0, 0.85fr); }
}

.confirme-suite {
  list-style: none;
  margin: 0;
  padding: 0;
  counter-reset: suite;
}

.confirme-suite li {
  position: relative;
  padding: 0 0 22px 40px;
  counter-increment: suite;
}

.confirme-suite li:last-child { padding-bottom: 0; }

.confirme-suite li::before {
  content: counter(suite, decimal-leading-zero);
  position: absolute;
  left: 0;
  top: 1px;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 0.75rem;
  color: var(--color-gold-500);
}

/* Le filet qui relie les jalons — il s'arrête au dernier. */
.confirme-suite li:not(:last-child)::after {
  content: '';
  position: absolute;
  left: 8px;
  top: 22px;
  bottom: 6px;
  width: 1px;
  background: var(--color-rule);
}

.confirme-suite b {
  display: block;
  font-family: var(--font-sans);
  font-size: 0.9375rem;
  font-weight: 500;
  color: var(--color-ink-900);
}

.confirme-suite span {
  display: block;
  margin-top: 5px;
  font-family: var(--font-sans);
  font-size: 0.875rem;
  line-height: 1.55;
  color: var(--color-ink-500);
}

/* =============================================================================
   8b. MINI-CRM
   =============================================================================
   Un outil de travail, pas une vitrine. Trois principes le distinguent du reste
   du site :

     — La densité est une fonctionnalité. Un commercial qui traite vingt
       demandes veut vingt lignes à l'écran, pas six cartes aérées.
     — Rien ne bouge sans raison. Une animation dans un tableau consulté cent
       fois par jour devient une gêne au troisième passage. Les seuls mouvements
       conservés signalent un fait : une demande vient d'arriver, un panneau
       s'ouvre.
     — Le chiffre prime sur la couleur. Chaque pastille de score porte sa valeur
       écrite ; la couleur ne fait que redoubler l'information.
   ============================================================================= */

/* --- Écran de connexion --------------------------------------------------- */

.connexion {
  min-height: 100dvh;
  display: grid;
  place-items: center;
  padding: 40px 20px;
  background: var(--color-navy-900);
}

.connexion-boite { width: 100%; max-width: 25rem; }

.connexion-marque { margin: 0; }

.connexion-marque .logo { max-width: 100%; }

.connexion-marque span {
  display: block;
  margin-top: 12px;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--color-gold-300);
}

.connexion form {
  margin-top: 28px;
  padding: 26px;
  border: 1px solid var(--color-navy-700);
  border-radius: var(--radius-technique, 2px);
  background: color-mix(in srgb, var(--color-navy-700) 40%, transparent);
}

/* Sur fond sombre, les contrôles inversent leurs couleurs — mais gardent
   exactement la même géométrie et les mêmes transitions. */
.connexion .champ-label { color: var(--color-paper); }
.connexion .champ-aide { color: color-mix(in srgb, var(--color-paper) 56%, transparent); }

.connexion .champ-saisie {
  background: var(--color-navy-900);
  border-color: var(--color-navy-500);
  color: var(--color-paper);
}

.connexion .champ-saisie:focus {
  border-color: var(--color-gold-500);
  background: var(--color-navy-900);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-gold-500) 20%, transparent);
}

.connexion .champ-erreur { color: var(--color-paper); }

.connexion-demo {
  margin-top: 22px;
  padding: 18px 20px;
  border: 1px dashed var(--color-navy-500);
  border-radius: var(--radius-technique, 2px);
}

.connexion-demo p {
  margin: 0;
  font-family: var(--font-sans);
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--color-paper) 48%, transparent);
}

.connexion-demo ul { list-style: none; margin: 12px 0 0; padding: 0; display: grid; gap: 8px; }

.connexion-demo button {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 12px;
  width: 100%;
  padding: 8px 10px;
  border: 1px solid transparent;
  border-radius: var(--radius-technique, 2px);
  background: none;
  text-align: left;
  cursor: pointer;
  transition: border-color 200ms var(--e-doux), background-color 200ms var(--e-doux);
}

.connexion-demo button:hover {
  border-color: var(--color-navy-500);
  background: var(--color-navy-700);
}

.connexion-demo b {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: 400;
  color: var(--color-gold-300);
}

.connexion-demo span {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: color-mix(in srgb, var(--color-paper) 48%, transparent);
}

.connexion-note {
  margin: 16px 0 0;
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  line-height: 1.55;
  color: color-mix(in srgb, var(--color-paper) 52%, transparent);
}

/* --- Bandeau de mode démonstration ---------------------------------------- */

.crm-mode {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 6px 12px;
  padding: 9px 20px;
  background: color-mix(in srgb, var(--color-kraft-200) 70%, var(--color-paper));
  border-bottom: 1px solid var(--color-rule);
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  color: var(--color-ink-900);
  text-align: center;
}

.crm-mode button {
  border: 0;
  background: none;
  padding: 0;
  font: inherit;
  color: var(--color-navy-500);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}

.crm-mode button:hover { color: var(--color-gold-500); }

/* --- Bandeau d'indicateurs ------------------------------------------------ */

.kpi {
  border-bottom: 1px solid var(--color-rule);
  background: var(--color-paper);
}

.kpi-l {
  width: 100%;
  max-width: 1600px;
  margin-inline: auto;
  padding: 20px clamp(16px, 3vw, 40px);
  display: grid;
  gap: 20px clamp(20px, 3vw, 44px);
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}

.kpi-item { min-width: 0; }

.kpi-item dt {
  font-family: var(--font-sans);
  font-size: 0.625rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-ink-500);
}

.kpi-item dd {
  display: flex;
  align-items: baseline;
  gap: 6px;
  margin: 7px 0 0;
}

.kpi-item b {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: clamp(1.4rem, 1.1rem + 0.9vw, 1.9rem);
  font-weight: 500;
  line-height: 1;
  color: var(--color-navy-900);
}

.kpi-item em {
  font-style: normal;
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  color: var(--color-ink-500);
}

.kpi-note {
  margin: 6px 0 0;
  font-family: var(--font-sans);
  font-size: 0.6875rem;
  line-height: 1.4;
  color: var(--color-kraft-500);
}

/* Répartition par pôle : quatre barres proportionnelles. Elle remplace un
   camembert de quatre parts, qui aurait pris dix fois la place pour une
   information qu'on lit ici d'un coup d'œil. */
.kpi-poles {
  display: flex;
  align-items: flex-end;
  gap: 10px;
  margin-top: 8px;
  height: 34px;
}

.kpi-pole {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  flex: 1;
  min-width: 0;
}

.kpi-pole i {
  display: block;
  width: 100%;
  max-width: 22px;
  min-height: 2px;
  height: calc(var(--part, 0) * 22px + 2px);
  background: var(--teinte);
  border-radius: 1px;
  transition: height 620ms var(--e-sortie);
}

.kpi-pole span {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 0.75rem;
  color: var(--color-ink-900);
}

/* --- Filtres -------------------------------------------------------------- */

.filtres {
  border-bottom: 1px solid var(--color-rule);
  background: var(--fond-creux);
}

.filtres-l {
  width: 100%;
  max-width: 1600px;
  margin-inline: auto;
  padding: 14px clamp(16px, 3vw, 40px);
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 14px 20px;
}

.filtre { min-width: 0; }

.filtre > legend,
.filtre > .filtre-titre {
  padding: 0;
  font-family: var(--font-sans);
  font-size: 0.625rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-ink-500);
}

.filtre-jetons {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 7px;
}

/* Jeton de filtre. La coche est native et masquée : le clavier fonctionne
   exactement comme sur une case à cocher, parce que c'en est une. */
.jeton {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 6px 12px;
  border: 1px solid var(--color-rule);
  border-radius: var(--radius-technique, 2px);
  background: var(--color-paper);
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  color: var(--color-ink-900);
  cursor: pointer;
  white-space: nowrap;
  transition: border-color 200ms var(--e-doux), background-color 200ms var(--e-doux),
    color 200ms var(--e-doux);
}

.jeton:hover { border-color: var(--color-navy-500); }

.jeton input { position: absolute; opacity: 0; width: 1px; height: 1px; margin: 0; }

.jeton:has(input:checked) {
  background: var(--color-navy-900);
  border-color: var(--color-navy-900);
  color: var(--color-paper);
}

.jeton:has(input:focus-visible) { outline: 2px solid var(--color-gold-500); outline-offset: 2px; }

.jeton i {
  display: block;
  width: 3px;
  height: 13px;
  flex: 0 0 auto;
  background: var(--teinte, transparent);
  border-radius: 1px;
}

.filtre select,
.filtre input[type='search'] {
  margin-top: 7px;
  padding: 8px 12px;
  border: 1px solid var(--color-rule);
  border-radius: var(--radius-controle, 6px);
  background: var(--color-paper);
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  color: var(--color-ink-900);
  transition: border-color 200ms var(--e-doux), box-shadow 200ms var(--e-doux);
  -webkit-appearance: none;
  appearance: none;
}

.filtre select { padding-right: 34px; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' fill='none'%3E%3Cpath d='m1 1 4 4 4-4' stroke='%235a6472' stroke-width='1.4'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 12px center; cursor: pointer; }

.filtre input[type='search'] { width: 100%; }

.filtre select:focus,
.filtre input[type='search']:focus {
  outline: none;
  border-color: var(--color-navy-500);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-navy-500) 12%, transparent);
}

.filtre--recherche { flex: 1 1 15rem; }

.filtre input[type='range'] {
  margin-top: 12px;
  width: 9rem;
  accent-color: var(--color-gold-500);
}

/* --- Tableau des demandes -------------------------------------------------- */

.crm-corps {
  width: 100%;
  max-width: 1600px;
  margin-inline: auto;
  padding: 18px clamp(16px, 3vw, 40px) 60px;
}

.crm-compte {
  margin: 0 0 12px;
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  color: var(--color-ink-500);
}

.table-boite {
  border: 1px solid var(--color-rule);
  border-radius: var(--radius-technique, 2px);
  overflow-x: auto;
  background: var(--color-paper);
}

.table {
  width: 100%;
  min-width: 62rem;
  border-collapse: collapse;
  text-align: left;
}

/* L'en-tête reste visible pendant le défilement de la liste : sans lui, au
   bout de quinze lignes on ne sait plus quelle colonne on lit. */
.table thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  padding: 0;
  background: var(--fond-creux);
  border-bottom: 1px solid var(--color-rule);
  white-space: nowrap;
}

.table thead th > * {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
  padding: 11px 14px;
  border: 0;
  background: none;
  font-family: var(--font-sans);
  font-size: 0.625rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-ink-500);
  text-align: inherit;
}

.table thead button { cursor: pointer; transition: color 200ms var(--e-doux); }
.table thead button:hover { color: var(--color-navy-900); }
.table thead th[aria-sort] button { color: var(--color-navy-900); }

.table thead .fleche { opacity: 0.4; font-size: 0.5rem; transition: opacity 200ms; }
.table thead th[aria-sort] .fleche { opacity: 1; color: var(--color-gold-500); }

.table td {
  padding: 11px 14px;
  border-bottom: 1px solid var(--color-rule);
  font-family: var(--font-sans);
  font-size: 0.875rem;
  color: var(--color-ink-900);
  vertical-align: middle;
}

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

/* Les lignes se posent en cascade très courte — 22 ms de décalage, vingt
   lignes au plus, soit moins d'une demi-seconde du haut du tableau au bas de
   l'écran. Assez pour qu'on voie le tableau se remplir plutôt qu'apparaître,
   pas assez pour retarder qui que ce soit.

   Le décalage n'est posé que sur les vingt premières lignes (voir CRM.tsx) :
   au-delà, `--rang` vaut zéro et tout arrive ensemble. Une cascade sur deux
   cents lignes n'est plus une arrivée, c'est une attente. */
.table tbody tr {
  cursor: pointer;
  transition: background-color 160ms var(--e-doux);
  animation: ligne-pose 400ms var(--e-sortie) both;
  animation-delay: calc(var(--rang, 0) * 22ms);
}

@keyframes ligne-pose {
  from { opacity: 0; transform: translate3d(0, 5px, 0); }
  to { opacity: 1; transform: none; }
}

.table tbody tr:hover { background: var(--fond-creux); }

.table tbody tr[data-selection] {
  background: color-mix(in srgb, var(--color-kraft-200) 55%, var(--color-paper));
}

.table tbody tr:focus-visible {
  outline: 2px solid var(--color-gold-500);
  outline-offset: -2px;
}

.table .num {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.table .ref { font-weight: 500; color: var(--color-navy-900); }
.table .faible { color: var(--color-ink-500); }
.table .droite { text-align: right; }

/* Ligne arrivée en temps réel.

   C'est le moment fort de la démonstration : une demande envoyée depuis un
   téléphone apparaît sur l'écran projeté sans rechargement. Le liseré d'or
   entre franchement, tient trois secondes, puis s'efface — assez pour qu'on la
   voie arriver, pas assez pour que le tableau reste bariolé. */
.table tbody tr[data-fraiche] {
  animation: ligne-arrive 620ms var(--e-sortie) both;
}

.table tbody tr[data-fraiche] td:first-child {
  box-shadow: inset 3px 0 0 0 var(--color-gold-500);
  animation: liseré-part 3s var(--e-doux) forwards;
}

@keyframes ligne-arrive {
  from { opacity: 0; transform: translate3d(0, -8px, 0); background: color-mix(in srgb, var(--color-gold-300) 26%, var(--color-paper)); }
  to { opacity: 1; transform: none; }
}

@keyframes liseré-part {
  0%, 55% { box-shadow: inset 3px 0 0 0 var(--color-gold-500); }
  100% { box-shadow: inset 3px 0 0 0 transparent; }
}

/* --- Squelette de chargement ----------------------------------------------
   Il donne la forme du tableau avant les données. Un « Chargement… » centré
   fait attendre ; une silhouette fait patienter, ce qui n'est pas pareil. */

.squelette td > span {
  display: block;
  height: 11px;
  border-radius: 2px;
  background: linear-gradient(
    90deg,
    var(--color-rule) 0%,
    color-mix(in srgb, var(--color-rule) 40%, var(--color-paper)) 50%,
    var(--color-rule) 100%
  );
  background-size: 200% 100%;
  animation: squelette 1400ms linear infinite;
  animation-delay: calc(var(--rang, 0) * 90ms);
}

@keyframes squelette {
  from { background-position: 200% 0; }
  to { background-position: -200% 0; }
}

/* --- Vide ------------------------------------------------------------------ */

.crm-vide {
  padding: 52px 24px;
  border: 1px dashed var(--color-rule);
  border-radius: var(--radius-technique, 2px);
  text-align: center;
}

.crm-vide b {
  display: block;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.16rem;
  color: var(--color-navy-900);
}

.crm-vide span {
  display: block;
  margin-top: 8px;
  font-family: var(--font-sans);
  font-size: 0.9375rem;
  color: var(--color-ink-500);
}

/* --- Panneau latéral de fiche ---------------------------------------------
   Il glisse depuis la droite au-dessus du tableau, qui reste visible et en
   place derrière lui : le commercial ne perd pas sa position dans la liste. */

.voile {
  position: fixed;
  inset: 0;
  z-index: 60;
  background: color-mix(in srgb, var(--color-navy-900) 42%, transparent);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
  animation: voile-entre 320ms var(--e-doux) both;
  border: 0;
  padding: 0;
  cursor: default;
}

@keyframes voile-entre {
  from { opacity: 0; }
  to { opacity: 1; }
}

.panneau {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  z-index: 61;
  width: min(34rem, 100vw);
  display: flex;
  flex-direction: column;
  background: var(--color-paper);
  box-shadow: var(--ombre-panneau);
  animation: panneau-entre 420ms var(--e-sortie) both;
}

@keyframes panneau-entre {
  from { transform: translate3d(100%, 0, 0); }
  to { transform: none; }
}

.panneau-tete {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  padding: 18px 22px;
  border-bottom: 1px solid var(--color-rule);
  background: var(--color-paper);
}

.panneau-fermer {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border: 1px solid var(--color-rule);
  border-radius: var(--radius-controle, 6px);
  background: var(--color-paper);
  color: var(--color-ink-500);
  cursor: pointer;
  transition: border-color 200ms var(--e-doux), color 200ms var(--e-doux),
    background-color 200ms var(--e-doux);
}

.panneau-fermer:hover {
  border-color: var(--color-navy-500);
  color: var(--color-navy-900);
  background: var(--fond-creux);
}

.panneau-corps {
  flex: 1;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 22px;
}

/* La marge est portée par chaque bloc, le filet seulement par ceux qui suivent
   un autre bloc. Sans cela, le premier bloc — qui suit la plaque et non un
   bloc — se collerait à elle. */
.panneau-bloc { margin-top: 26px; }

.panneau-bloc + .panneau-bloc {
  padding-top: 22px;
  border-top: 1px solid var(--color-rule);
}

.panneau-bloc > h3 {
  margin: 0 0 12px;
  font-family: var(--font-sans);
  font-size: 0.625rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-ink-500);
}

.panneau-pied {
  padding: 16px 22px;
  border-top: 1px solid var(--color-rule);
  background: var(--fond-creux);
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

/* Pipeline de statuts : une file de jetons, l'étape courante remplie. */
.pipeline {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.pipeline button {
  padding: 7px 12px;
  border: 1px solid var(--color-rule);
  border-radius: var(--radius-technique, 2px);
  background: var(--color-paper);
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  color: var(--color-ink-900);
  cursor: pointer;
  transition: border-color 200ms var(--e-doux), background-color 200ms var(--e-doux),
    color 200ms var(--e-doux);
}

.pipeline button:hover { border-color: var(--color-navy-500); }

.pipeline button[aria-pressed='true'] {
  background: var(--color-navy-900);
  border-color: var(--color-navy-900);
  color: var(--color-paper);
}

/* Le statut perdu ne prend pas la couleur d'un succès. */
.pipeline button[aria-pressed='true'][data-perdue] {
  background: var(--color-ink-500);
  border-color: var(--color-ink-500);
}

/* =============================================================================
   8c. TABLEAU DE BORD
   =============================================================================
   La seconde vue du mini-CRM : ce que le tableau des demandes ne peut pas dire.

   Le tableau répond à « où en est CETTE demande ». Le tableau de bord répond à
   « où en est-on » — et ce sont deux questions qu'on ne pose pas au même
   moment de la journée.

   Le mouvement obéit ici à une règle plus stricte qu'ailleurs sur le site :
   chaque figure ne s'anime QU'UNE FOIS, au moment où elle entre dans l'écran,
   et jamais au survol ni au retour en arrière. Une barre qui repousse à chaque
   passage de souris rend un outil consulté vingt fois par jour épuisant.

   L'animation n'est pas décorative pour autant : une barre qui pousse depuis
   zéro montre son échelle, une courbe qui se trace montre son sens de lecture,
   un chiffre qui monte laisse voir son ordre de grandeur s'installer. C'est
   une seconde de pédagogie, payée une fois par ouverture d'écran.
   ============================================================================= */

/* --- Commutateur de vue ---------------------------------------------------
   Deux onglets, et le trait d'or qui glisse dessous. Le même trait que la page
   courante de la barre du site : c'est le signal « vous êtes ici » de la
   maison, et il ne veut dire que ça. */

/* La barre colle sous la barre d'application : on change de vue depuis
   n'importe quel point du défilement, sans remonter. */
.crm-vues-barre {
  position: sticky;
  top: 66px;
  z-index: 20;
  background: color-mix(in srgb, var(--color-paper) 92%, transparent);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--color-rule);
}

.crm-vues {
  display: flex;
  align-items: stretch;
  gap: 4px;
  width: 100%;
  max-width: 1600px;
  margin-inline: auto;
  padding: 0 clamp(16px, 3vw, 40px);
}

.crm-vues button {
  position: relative;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 13px 16px 12px;
  border: 0;
  background: none;
  font-family: var(--font-sans);
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--color-ink-500);
  cursor: pointer;
  transition: color 220ms var(--e-doux);
}

.crm-vues button::after {
  content: '';
  position: absolute;
  left: 12px;
  right: 12px;
  bottom: 0;
  height: 2px;
  border-radius: 1px;
  background: var(--color-gold-500);
  transform: scaleX(0);
  transform-origin: center;
  transition: transform 320ms var(--e-sortie);
}

.crm-vues button:hover { color: var(--color-navy-900); }

.crm-vues button[aria-selected='true'] { color: var(--color-navy-900); }
.crm-vues button[aria-selected='true']::after { transform: scaleX(1); }

/* Le compteur de la liste : le nombre de demandes que l'onglet ouvrira. */
.crm-vues button .val {
  padding: 2px 7px;
  border-radius: 999px;
  background: var(--fond-creux);
  font-size: 0.6875rem;
  color: var(--color-ink-500);
  transition: background-color 220ms var(--e-doux), color 220ms var(--e-doux);
}

.crm-vues button[aria-selected='true'] .val {
  background: var(--color-navy-900);
  color: var(--color-paper);
}

/* --- Cadre ---------------------------------------------------------------- */

.tb { display: grid; gap: clamp(16px, 2vw, 22px); }

.tb-perimetre {
  margin: 0;
  padding: 11px 15px;
  border-left: 3px solid var(--color-gold-500);
  background: var(--fond-creux);
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  color: var(--color-ink-900);
}

/* --- Tuiles de chiffres ---------------------------------------------------
   Huit chiffres, trois niveaux chacun : ce que c'est, combien, et par rapport
   à quoi. Le troisième niveau est le plus important — un chiffre sans
   comparaison n'est pas un indicateur, c'est une décoration. */

/* Huit tuiles, et un nombre de colonnes qui DIVISE huit : 1, 2 ou 4.
   `auto-fit` en donnerait cinq ou sept selon la largeur, et la dernière rangée
   laisserait une cellule vide — sur une grille dont le fond porte les filets,
   une cellule vide n'est pas un blanc, c'est un aplat de filet en plein milieu
   du tableau de bord. */
.tb-tuiles {
  display: grid;
  gap: 1px;
  grid-template-columns: minmax(0, 1fr);
  /* Le fond porte les filets : une gouttière de 1 px sur fond de filet donne
     des séparateurs parfaits, y compris au repli, sans une seule bordure
     conditionnelle. */
  background: var(--color-rule);
  border: 1px solid var(--color-rule);
  border-radius: var(--radius-technique, 2px);
  overflow: hidden;
}

@media (width >= 30rem) {
  .tb-tuiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (width >= 62rem) {
  .tb-tuiles { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

.tb-tuile {
  position: relative;
  padding: 15px 17px 16px;
  background: var(--color-paper);
  min-width: 0;
  animation: tuile-entre 520ms var(--e-sortie) both;
  animation-delay: calc(var(--rang, 0) * 45ms);
  transition: background-color 200ms var(--e-doux);
}

.tb-tuiles .tb-tuile:nth-child(1) { --rang: 0; }
.tb-tuiles .tb-tuile:nth-child(2) { --rang: 1; }
.tb-tuiles .tb-tuile:nth-child(3) { --rang: 2; }
.tb-tuiles .tb-tuile:nth-child(4) { --rang: 3; }
.tb-tuiles .tb-tuile:nth-child(5) { --rang: 4; }
.tb-tuiles .tb-tuile:nth-child(6) { --rang: 5; }
.tb-tuiles .tb-tuile:nth-child(7) { --rang: 6; }
.tb-tuiles .tb-tuile:nth-child(8) { --rang: 7; }

.tb-tuile:hover { background: color-mix(in srgb, var(--fond-creux) 55%, var(--color-paper)); }

@keyframes tuile-entre {
  from { opacity: 0; transform: translate3d(0, 10px, 0); }
  to { opacity: 1; transform: none; }
}

.tb-tuile-libelle {
  margin: 0;
  font-family: var(--font-sans);
  font-size: 0.625rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-ink-500);
}

.tb-tuile-valeur {
  display: flex;
  align-items: baseline;
  gap: 4px;
  margin: 9px 0 0;
  font-size: clamp(1.45rem, 1.1rem + 1vw, 1.85rem);
  font-weight: 500;
  line-height: 1;
  color: var(--color-navy-900);
  font-variant-numeric: tabular-nums;
}

.tb-tuile-valeur em {
  font-style: normal;
  font-size: 0.8125rem;
  font-weight: 400;
  color: var(--color-ink-500);
}

.tb-tuile-note {
  display: block;
  margin-top: 9px;
  font-family: var(--font-sans);
  font-size: 0.6875rem;
  line-height: 1.45;
  color: var(--color-kraft-500);
}

/* --- Variation ------------------------------------------------------------
   L'or récompense le bon sens de variation, qui n'est pas toujours la hausse :
   un délai de réponse qui grimpe est une mauvaise nouvelle. La flèche porte le
   sens, la couleur porte le jugement, et les deux sont indépendantes. */

.tb-var {
  display: inline-flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px 6px;
  margin-top: 9px;
  font-family: var(--font-sans);
  font-size: 0.75rem;
  color: var(--color-ink-500);
}

.tb-var i { font-style: normal; font-size: 0.8125rem; line-height: 1; }
.tb-var .val { font-weight: 500; color: var(--color-ink-900); }
.tb-var em { font-style: normal; font-size: 0.6875rem; color: var(--color-kraft-500); }

.tb-var[data-bon='oui'] i,
.tb-var[data-bon='oui'] .val { color: var(--color-gold-500); }

.tb-var[data-bon='non'] i,
.tb-var[data-bon='non'] .val { color: var(--color-ink-500); }

.tb-var--neutre { font-size: 0.6875rem; color: var(--color-kraft-500); }

/* --- Étincelle ------------------------------------------------------------
   Une courbe de la taille d'un mot : elle ne répond qu'à « ça monte ou ça
   descend ». Sans axe, sans valeur — le chiffre au-dessus porte le reste. */

.tb-etincelle {
  display: block;
  width: 100%;
  height: 26px;
  margin-top: 11px;
  overflow: visible;
}

.tb-etincelle path { fill: none; stroke: var(--color-navy-500); stroke-width: 1.5; }

/* Le sélecteur porte la balise, comme celui du dessus : sans elle il perdrait
   la spécificité, l'aire hériterait du trait, et le segment de fermeture —
   celui qui redescend au ras du bord droit — se dessinerait en pleine page. */
path.tb-etincelle-aire {
  fill: color-mix(in srgb, var(--color-navy-500) 14%, transparent);
  stroke: none;
}

/* --- Grille de cartes ------------------------------------------------------ */

/* Chaque carte prend sa propre hauteur. Étirées à la plus haute de leur
   rangée — le comportement par défaut de la grille — l'entonnoir imposerait sa
   taille à l'anneau et aux opportunités, qui traîneraient chacune deux cents
   pixels de vide sous leur dernière ligne. */
.tb-grille {
  display: grid;
  align-items: start;
  gap: clamp(16px, 2vw, 22px);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 22rem), 1fr));
}

.tb-carte {
  min-width: 0;
  padding: clamp(16px, 1.8vw, 22px);
  border: 1px solid var(--color-rule);
  border-radius: var(--radius-technique, 2px);
  background: var(--color-paper);
  opacity: 0;
  transform: translate3d(0, 16px, 0);
  transition: opacity 560ms var(--e-sortie), transform 560ms var(--e-sortie),
    box-shadow 260ms var(--e-doux);
}

.tb-carte[data-entre] { opacity: 1; transform: none; }
.tb-carte:hover { box-shadow: var(--ombre-pose); }

.tb-carte--large { grid-column: 1 / -1; }

.tb-carte-tete {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 18px;
}

.tb-carte-tete h3 {
  margin: 0;
  font-family: var(--font-sans);
  font-size: 0.625rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-ink-500);
}

.tb-note {
  margin: 16px 0 0;
  padding-top: 12px;
  border-top: 1px solid var(--color-rule);
  font-family: var(--font-sans);
  font-size: 0.6875rem;
  line-height: 1.5;
  color: var(--color-kraft-500);
}

.tb-lien {
  border: 0;
  background: none;
  padding: 0;
  flex: 0 0 auto;
  font-family: var(--font-sans);
  font-size: 0.75rem;
  color: var(--color-navy-500);
  text-decoration: underline;
  text-decoration-color: var(--color-rule);
  text-underline-offset: 4px;
  cursor: pointer;
  transition: color 200ms var(--e-doux), text-decoration-color 200ms var(--e-doux);
}

.tb-lien:hover { color: var(--color-gold-500); text-decoration-color: var(--color-gold-500); }

/* =============================================================================
   COURBE
   =============================================================================
   Le tracé vit dans un viewBox de 100 × 100 étiré par preserveAspectRatio,
   donc en pourcentage pur : aucune mesure JavaScript, aucun observateur de
   redimensionnement. `vector-effect: non-scaling-stroke` empêche l'étirement
   d'écraser le trait, et les points sont en HTML par-dessus — c'est la seule
   façon qu'ils restent ronds et portent une infobulle.
   ============================================================================= */

/* La gouttière des valeurs, à gauche du tracé. C'est le côté où l'on commence
   à lire, et une échelle qu'on lit après la courbe ne sert plus à rien. */
.tb-courbe { --gouttiere: 36px; }

.tb-plot {
  position: relative;
  height: clamp(150px, 20vw, 210px);
  margin: 6px 0 0;
}

/* Le tracé et ses points vivent en retrait de la gouttière ; les repères, eux,
   traversent toute la boîte. Deux niveaux, parce qu'un pourcentage de position
   absolue se calcule sur la boîte de rembourrage — un simple `padding-left`
   sur `.tb-plot` décalerait les points d'une gouttière. */
.tb-plot-zone { position: relative; height: 100%; margin-left: var(--gouttiere); }

.tb-svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
}

.tb-aire {
  opacity: 0;
  transform: translate3d(0, 10%, 0);
  transition: opacity 700ms var(--e-doux) 220ms, transform 700ms var(--e-sortie) 220ms;
}

.tb-carte[data-entre] .tb-aire { opacity: 1; transform: none; }

.tb-ligne {
  stroke: var(--color-navy-900);
  stroke-width: 2;
  stroke-linejoin: round;
  stroke-linecap: round;
  /* pathLength = 1 : un seul jeu de valeurs suffit quelle que soit la
     longueur réelle du tracé. */
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
}

.tb-carte[data-entre] .tb-ligne {
  stroke-dashoffset: 0;
  transition: stroke-dashoffset 1100ms var(--e-sortie) 120ms;
}

/* Repères horizontaux : zéro, moitié, sommet. Trois, pas davantage — au-delà,
   la grille devient un motif et cesse d'aider à lire. */
.tb-repere {
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(var(--part) * 100%);
  display: flex;
  align-items: center;
  gap: 8px;
  pointer-events: none;
}

.tb-repere b {
  flex: 0 0 var(--gouttiere);
  padding-right: 9px;
  text-align: right;
  font-size: 0.625rem;
  font-weight: 400;
  color: var(--color-ink-500);
  transform: translateY(-0.5px);
}

.tb-repere i {
  flex: 1;
  height: 1px;
  background: var(--color-rule);
}

/* --- Points --------------------------------------------------------------- */

.tb-pt {
  position: absolute;
  left: var(--x);
  bottom: var(--y);
  width: 34px;
  height: 34px;
  margin: 0 0 -17px -17px;
  display: grid;
  place-items: center;
  opacity: 0;
  transform: scale(0.4);
}

.tb-carte[data-entre] .tb-pt {
  opacity: 1;
  transform: none;
  transition: opacity 300ms var(--e-doux), transform 380ms var(--e-ressort);
  transition-delay: calc(420ms + var(--rang, 0) * 62ms);
}

.tb-pt i {
  display: block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--color-paper);
  border: 2px solid var(--color-navy-900);
  transition: transform 200ms var(--e-ressort), background-color 200ms var(--e-doux);
}

/* Une semaine sans demande n'est pas un trou dans la courbe : c'est un fait,
   et il porte son propre point, creux. */
.tb-pt[data-vide] i { border-color: var(--color-rule); }

.tb-pt:hover i {
  transform: scale(1.45);
  background: var(--color-gold-500);
  border-color: var(--color-gold-500);
}

/* --- Infobulle ------------------------------------------------------------ */

.tb-bulle {
  position: absolute;
  bottom: calc(100% - 2px);
  left: 50%;
  z-index: 3;
  display: grid;
  gap: 2px;
  width: max-content;
  max-width: 15rem;
  padding: 9px 12px;
  border-radius: var(--radius-technique, 2px);
  background: var(--color-navy-900);
  color: var(--color-paper);
  font-family: var(--font-sans);
  font-size: 0.75rem;
  line-height: 1.4;
  pointer-events: none;
  opacity: 0;
  transform: translate(-50%, 4px);
  transition: opacity 180ms var(--e-doux), transform 220ms var(--e-sortie);
}

.tb-pt:hover .tb-bulle { opacity: 1; transform: translate(-50%, 0); }

.tb-bulle b { font-weight: 500; }
.tb-bulle .val { font-size: 0.6875rem; color: var(--color-gold-300); }

/* Les deux bulles de bord se rabattent vers l'intérieur : sinon la première et
   la dernière sortent du cadre et sont coupées. */
.tb-pt:first-of-type .tb-bulle { left: 0; transform: translate(0, 4px); }
.tb-pt:first-of-type:hover .tb-bulle { transform: translate(0, 0); }
.tb-pt:last-of-type .tb-bulle { left: auto; right: 0; transform: translate(0, 4px); }
.tb-pt:last-of-type:hover .tb-bulle { transform: translate(0, 0); }

/* --- Axe des abscisses ---------------------------------------------------- */

.tb-axe {
  display: flex;
  justify-content: space-between;
  margin: 12px 0 0 var(--gouttiere);
  padding-top: 9px;
  border-top: 1px solid var(--color-rule);
  font-size: 0.625rem;
  color: var(--color-ink-500);
}

/* Sous 34 rem, dix étiquettes de date se chevauchent : on n'en garde qu'une
   sur deux. Le tracé, lui, ne perd rien. */
@media (width < 34rem) {
  .tb-axe span:nth-child(even) { display: none; }
}

/* =============================================================================
   ENTONNOIR
   ============================================================================= */

.tb-entonnoir ol { list-style: none; margin: 0; padding: 0; display: grid; gap: 14px; }

.tb-ent-tete {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 6px;
}

.tb-ent-tete b {
  font-family: var(--font-sans);
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--color-ink-900);
}

.tb-passage {
  flex: 0 0 auto;
  padding: 2px 7px;
  border: 1px solid var(--color-rule);
  border-radius: var(--radius-technique, 2px);
  font-size: 0.6875rem;
  color: var(--color-ink-500);
  cursor: help;
}

.tb-ent-barre {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  height: 22px;
}

.tb-ent-barre i {
  display: block;
  height: 100%;
  flex: 1;
  min-width: 0;
  border-radius: 1px;
  background: var(--color-navy-900);
  transform: scaleX(0);
  transform-origin: left;
}

.tb-carte[data-entre] .tb-ent-barre i {
  transform: scaleX(var(--part));
  transition: transform 720ms var(--e-sortie);
  transition-delay: calc(var(--rang, 0) * 90ms);
}

/* Le compteur reste posé au bord gauche : il ne suit pas la barre, sinon il
   danserait à chaque changement de filtre. */
.tb-ent-barre .val {
  position: absolute;
  left: 10px;
  font-size: 0.8125rem;
  font-weight: 500;
  color: var(--color-paper);
  mix-blend-mode: difference;
  pointer-events: none;
}

.tb-entonnoir p {
  margin: 5px 0 0;
  font-size: 0.6875rem;
  color: var(--color-ink-500);
}

/* La sortie du parcours ne prend pas la couleur d'une étape franchie. */
.tb-entonnoir li[data-sortie] { padding-top: 12px; border-top: 1px dashed var(--color-rule); }
.tb-entonnoir li[data-sortie] .tb-ent-barre i { background: var(--color-ink-500); opacity: 0.55; }

/* =============================================================================
   ANNEAU
   ============================================================================= */

.tb-anneau {
  display: grid;
  gap: 20px;
  align-items: center;
  grid-template-columns: minmax(0, 132px) minmax(0, 1fr);
}

@media (width < 26rem) {
  .tb-anneau { grid-template-columns: minmax(0, 1fr); justify-items: center; }
}

.tb-anneau-dessin { position: relative; width: 100%; aspect-ratio: 1; max-width: 132px; }

.tb-anneau-dessin svg {
  width: 100%;
  height: 100%;
  /* Départ à midi, là où l'œil commence à lire un cadran. */
  transform: rotate(-90deg);
}

.tb-anneau-fond {
  fill: none;
  stroke: var(--color-rule);
  stroke-width: 11;
  opacity: 0.5;
}

/* L'arc pousse de zéro à sa part : `stroke-dasharray` est une liste de
   longueurs, et les listes de même cardinalité s'interpolent. C'est ce qui
   permet d'animer une part de camembert sans une ligne de JavaScript. */
.tb-anneau-arc {
  fill: none;
  stroke-width: 11;
  stroke-dasharray: 0 var(--perimetre);
  stroke-dashoffset: var(--decalage);
}

.tb-carte[data-entre] .tb-anneau-arc {
  stroke-dasharray: var(--arc) var(--perimetre);
  transition: stroke-dasharray 820ms var(--e-sortie);
  transition-delay: calc(var(--rang, 0) * 130ms);
}

.tb-anneau-centre {
  position: absolute;
  inset: 0;
  display: grid;
  place-content: center;
  text-align: center;
  pointer-events: none;
}

.tb-anneau-centre b {
  display: block;
  font-size: 1.5rem;
  font-weight: 500;
  line-height: 1;
  color: var(--color-navy-900);
}

.tb-anneau-centre span {
  display: block;
  margin-top: 4px;
  font-family: var(--font-sans);
  font-size: 0.625rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-ink-500);
}

.tb-legende { list-style: none; margin: 0; padding: 0; display: grid; gap: 9px; }

.tb-legende li {
  display: flex;
  align-items: center;
  gap: 9px;
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  color: var(--color-ink-900);
  opacity: 0;
  transform: translate3d(6px, 0, 0);
}

.tb-carte[data-entre] .tb-legende li {
  opacity: 1;
  transform: none;
  transition: opacity 420ms var(--e-doux), transform 420ms var(--e-sortie);
  transition-delay: calc(180ms + var(--rang, 0) * 90ms);
}

.tb-legende i { flex: 0 0 auto; width: 3px; height: 15px; border-radius: 1px; }
.tb-legende b { font-weight: 400; flex: 1; min-width: 0; }
.tb-legende .val { font-weight: 500; }
.tb-legende-part {
  flex: 0 0 auto;
  min-width: 4ch;
  text-align: right;
  white-space: nowrap;
  color: var(--color-ink-500);
  font-weight: 400;
}

/* =============================================================================
   BARRES HORIZONTALES
   ============================================================================= */

.tb-barres ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 15px; }

.tb-barre-tete {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 6px;
}

.tb-barre-tete b {
  font-family: var(--font-sans);
  font-size: 0.875rem;
  font-weight: 400;
  color: var(--color-ink-900);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.tb-barre-tete .val {
  flex: 0 0 auto;
  font-size: 0.8125rem;
  font-weight: 500;
  color: var(--color-navy-900);
}

.tb-barre-piste {
  height: 6px;
  border-radius: 3px;
  background: var(--fond-creux);
  overflow: hidden;
}

.tb-barre-piste i {
  display: block;
  height: 100%;
  background: var(--color-navy-500);
  transform: scaleX(0);
  transform-origin: left;
}

.tb-carte[data-entre] .tb-barre-piste i {
  transform: scaleX(var(--part));
  transition: transform 700ms var(--e-sortie);
  transition-delay: calc(var(--rang, 0) * 80ms);
}

.tb-barres p {
  margin: 6px 0 0;
  font-family: var(--font-sans);
  font-size: 0.6875rem;
  color: var(--color-ink-500);
}

/* =============================================================================
   HISTOGRAMME DES SCORES
   ============================================================================= */

.tb-histo-barres {
  display: flex;
  align-items: flex-end;
  gap: 5px;
  height: 132px;
}

.tb-histo-col {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  gap: 5px;
  flex: 1;
  min-width: 0;
  height: 100%;
}

.tb-histo-col i {
  display: block;
  width: 100%;
  border-radius: 1px 1px 0 0;
  background: var(--teinte);
  height: 2px;
  opacity: 0.85;
  transition: opacity 200ms var(--e-doux);
}

.tb-carte[data-entre] .tb-histo-col i {
  height: calc(var(--part) * 92px + 2px);
  transition: height 660ms var(--e-sortie), opacity 200ms var(--e-doux);
  transition-delay: calc(var(--rang, 0) * 50ms), 0ms;
}

.tb-histo-col:hover i { opacity: 1; }

.tb-histo-valeur {
  font-size: 0.6875rem;
  color: var(--color-ink-900);
  min-height: 1em;
}

.tb-histo-borne {
  font-size: 0.625rem;
  color: var(--color-ink-500);
  padding-top: 5px;
  border-top: 1px solid var(--color-rule);
  width: 100%;
  text-align: center;
}

/* =============================================================================
   LISTES D'ACTION — urgences et opportunités
   =============================================================================
   Les deux seuls blocs cliquables du tableau de bord. Ils ouvrent la fiche, au
   même endroit et de la même façon qu'une ligne du tableau : c'est le geste que
   le commercial connaît déjà.
   ============================================================================= */

.tb-urgences,
.tb-opportunites {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 1px;
  background: var(--color-rule);
  border: 1px solid var(--color-rule);
  border-radius: var(--radius-technique, 2px);
  overflow: hidden;
}

.tb-urgences button,
.tb-opportunites button {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 11px 14px;
  border: 0;
  background: var(--color-paper);
  text-align: left;
  cursor: pointer;
  opacity: 0;
  transform: translate3d(0, 6px, 0);
  transition: background-color 160ms var(--e-doux);
}

.tb-carte[data-entre] .tb-urgences button,
.tb-carte[data-entre] .tb-opportunites button {
  opacity: 1;
  transform: none;
  transition: opacity 380ms var(--e-doux), transform 380ms var(--e-sortie),
    background-color 160ms var(--e-doux);
  transition-delay: calc(var(--rang, 0) * 55ms), calc(var(--rang, 0) * 55ms), 0ms;
}

.tb-urgences button:hover,
.tb-opportunites button:hover { background: var(--fond-creux); }

.tb-urgences button:focus-visible,
.tb-opportunites button:focus-visible { outline: 2px solid var(--color-gold-500); outline-offset: -2px; }

/* Le liseré d'or : cette demande attend une action, pas une lecture. */
.tb-urgences li { position: relative; }

.tb-urgences li::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 3px;
  z-index: 1;
  background: var(--color-gold-500);
  pointer-events: none;
}

.tb-urgences button { padding-left: 17px; }

.tb-urgence-ref {
  flex: 0 0 auto;
  font-size: 0.75rem;
  font-weight: 500;
  color: var(--color-navy-900);
}

.tb-urgence-nom,
.tb-opp-nom { flex: 1; min-width: 0; }

.tb-urgence-nom b,
.tb-opp-nom b {
  display: block;
  font-family: var(--font-sans);
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--color-ink-900);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.tb-urgence-nom em,
.tb-opp-nom em {
  display: block;
  margin-top: 2px;
  font-style: normal;
  font-family: var(--font-sans);
  font-size: 0.75rem;
  color: var(--color-ink-500);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.tb-urgence-statut { flex: 0 0 auto; color: var(--color-ink-500); white-space: nowrap; }

.tb-urgence-age {
  flex: 0 0 auto;
  font-size: 0.75rem;
  color: var(--color-kraft-500);
  white-space: nowrap;
}

.tb-opp-valeur {
  flex: 0 0 auto;
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--color-navy-900);
  white-space: nowrap;
}

/* Sous 40 rem, la ligne d'urgence se replie : le motif et le statut passent
   sous le nom plutôt que de le comprimer à trois caractères. */
@media (width < 40rem) {
  .tb-urgences button { flex-wrap: wrap; gap: 8px 10px; }
  .tb-urgence-nom { flex: 1 1 100%; order: 3; }
}

/* --- Silhouette de chargement ---------------------------------------------- */

.squelette-bloc {
  background: linear-gradient(
    90deg,
    var(--fond-creux) 0%,
    color-mix(in srgb, var(--color-rule) 45%, var(--color-paper)) 50%,
    var(--fond-creux) 100%
  );
  background-size: 200% 100%;
  animation: squelette 1500ms linear infinite;
  animation-delay: calc(var(--rang, 0) * 90ms);
  min-height: 104px;
  opacity: 1;
  transform: none;
}

/* =============================================================================
   9. MOUVEMENT RÉDUIT
   ============================================================================= */

@media (prefers-reduced-motion: reduce) {
  .app *,
  .app *::before,
  .app *::after {
    animation-duration: 0.01ms !important;
    animation-delay: 0ms !important;
    transition-duration: 0.01ms !important;
  }

  .etape,
  .etape-grille > *,
  .fichiers li,
  .recap .plaque-spec { animation: none !important; opacity: 1 !important; transform: none !important; }

  .confirme-sceau circle,
  .confirme-sceau path { stroke-dashoffset: 0; animation: none !important; }

  .choix-carte:active { transform: none; }
  .rail-jalon[data-etat='faite'] .rail-pastille:hover { transform: none; }

  /* Le panneau et le voile n'arrivent plus en glissant, mais ils arrivent :
     les supprimer ferait apparaître la fiche sans qu'on sache d'où. */
  .panneau, .voile { animation: none !important; }

  /* La ligne qui vient d'arriver garde son liseré, figé : c'est une
     information, pas une décoration. Elle ne clignote simplement plus. */
  .table tbody tr[data-fraiche] { animation: none !important; }
  .table tbody tr[data-fraiche] td:first-child {
    animation: none !important;
    box-shadow: inset 3px 0 0 0 var(--color-gold-500);
  }

  .squelette td > span { animation: none !important; }

  /* --- Tableau de bord ---------------------------------------------------
     Rien ne se trace, rien ne pousse, rien ne monte : tout est déjà là. Ce
     n'est pas une version dégradée — les chiffres, les longueurs de barre et
     les tracés sont rigoureusement les mêmes. `useCompteur` et `useEntree`
     posent d'ailleurs leur valeur finale d'emblée côté JavaScript ; ces règles
     ne font que couvrir la part CSS. */

  .table tbody tr,
  .tb-tuile,
  .squelette-bloc { animation: none !important; }

  .tb-carte { opacity: 1 !important; transform: none !important; }

  .tb-ligne { stroke-dashoffset: 0 !important; }
  .tb-aire { opacity: 1 !important; transform: none !important; }
  .tb-pt { opacity: 1 !important; transform: none !important; }
  .tb-anneau-arc { stroke-dasharray: var(--arc) var(--perimetre) !important; }
  .tb-legende li { opacity: 1 !important; transform: none !important; }
  .tb-ent-barre i,
  .tb-barre-piste i { transform: scaleX(var(--part)) !important; }
  .tb-histo-col i { height: calc(var(--part) * 92px + 2px) !important; }

  .tb-urgences button,
  .tb-opportunites button { opacity: 1 !important; transform: none !important; }

  /* L'infobulle de la courbe reste : c'est de l'information au survol, pas une
     animation. Elle apparaît simplement sans fondu. */
  .tb-pt:hover i { transform: none; }
}
