/* ============================================================================
   components.css — Briques réutilisables
   Boutons, champs, cartes, chips, segmented controls, steppers, tooltips…
   ============================================================================ */

/* ================================ ICÔNES ================================= */
/* Icônes Lucide rendues par partials/icons.php. Elles héritent de la couleur
   du texte et s'alignent correctement aussi bien en flux inline (dans un
   paragraphe) qu'en conteneur flex (badges, titres, boutons). */
.icon{
  display:inline-block;
  vertical-align:-0.18em;
  flex:0 0 auto;
}
/* Traits légèrement affinés sur les grandes tailles, pour rester élégant */
.icon[width="30"],.icon[width="46"]{ stroke-width:1.75; }

/* ================================ CARTES ================================= */
.card{
  background:var(--surface);
  border:1px solid var(--border-soft);
  border-radius:var(--r-lg);
  box-shadow:var(--shadow-sm);
  overflow:hidden;
}
.card--flush{ box-shadow:none; }
.pad{ padding:var(--sp-5); }
@media (max-width:640px){ .pad{ padding:var(--sp-4); } }

.phead{
  display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between; gap:var(--sp-3);
  padding:var(--sp-4) var(--sp-5);
  border-bottom:1px solid var(--border-soft);
  background:var(--surface-raised);
}
.phead h3{ font-size:var(--fs-lg); font-weight:800; letter-spacing:-.01em; }
.phead h4{ font-size:var(--fs-md); font-weight:700; }

.note{
  color:var(--muted);
  font-size:var(--fs-sm);
  line-height:1.5;
}

/* Les variantes de la note.
   -------------------------------------------------------------------------
   Elles étaient posées dans le balisage — `note note--ko` sur les messages
   d'erreur du devis rapide et de l'encaissement — mais AUCUNE règle ne leur
   correspondait. Un message d'erreur s'affichait donc en gris discret, à la
   même enseigne qu'une aide contextuelle : celui qui vient de se tromper de
   date ne voyait rien qui accroche l'œil.

   La couleur ne porte jamais seule le sens : c'est le TEXTE qui dit ce qui ne
   va pas. Le trait de gauche n'est qu'un repère de plus. */
.note--ko,
.note--ok,
.note--info{
  padding:10px 12px;
  border-radius:var(--r-md);
  border:1px solid;
  border-left-width:3px;
  font-weight:600;
}
/* Le trait de gauche prend la couleur PLEINE, pas un mélange pâle : mesuré,
   `color-mix(… 34%, --card)` tombait à 1,5:1 contre la page, soit un repère
   qu'on ne voit pas. Les couleurs pleines passent les 3:1 attendus d'un
   indicateur non textuel. Le liseré des trois autres côtés, lui, reste discret :
   il délimite, il n'informe pas. */
.note--ko{
  color:var(--danger-text, #b91c1c);
  background:var(--danger-bg);
  border-color:color-mix(in oklab, var(--danger) 34%, var(--card));
  border-left-color:var(--danger);
}
.note--ok{
  color:var(--ok-text, #15803d);
  background:color-mix(in oklab, var(--ok) 12%, var(--card));
  border-color:color-mix(in oklab, var(--ok) 34%, var(--card));
  border-left-color:var(--ok);
}
/* Le texte est ancré sur --text, PAS sur l'accent du client.
   Mesuré à 1,71:1 quand il en dérivait : l'accent teinte à la fois le texte et
   le fond, si bien que les deux se rapprochent — et chez un client qui a choisi
   une couleur claire, le message devenait illisible. Une teinte légère suffit à
   rattacher la note à sa couleur sans dépendre d'elle pour être lue. */
.note--info{
  color:color-mix(in oklab, var(--text) 88%, var(--accent));
  background:var(--accent-soft-bg);
  border-color:color-mix(in oklab, var(--accent) 28%, var(--card));
  border-left-color:var(--accent);
}
.sep{
  border:0; height:1px;
  background:var(--border-soft);
  margin:var(--sp-6) 0;
}

/* =============================== BOUTONS ================================= */
/* Le bouton principal en dégradé, et non en aplat.
   Le dégradé part de l'accent du client éclairci vers son accent plein : deux
   nuances de SA couleur, jamais de la nôtre. Une variable --btn-bg reste en
   place pour les variantes qui repeignent le bouton (secondaire, danger) :
   elles gagnent, puisqu'elles écrasent `background` après. */
.btn{
  --btn-bg: var(--degrade-accent);
  --btn-fg: var(--accent-contrast);
  position:relative;
  display:inline-flex; align-items:center; justify-content:center; gap:var(--sp-2);
  padding:12px 20px;
  background:var(--btn-bg);
  color:var(--btn-fg);
  border:1px solid transparent;
  border-radius:var(--r-pill);
  font-family:var(--font-btn-family);
  font-size:var(--font-btn-size);
  font-weight:700;
  line-height:1.2;
  cursor:pointer;
  white-space:nowrap;
  overflow:hidden;
  isolation:isolate;
  box-shadow:var(--shadow-sm);
  transition:transform var(--dur-fast) var(--ease-spring),
             box-shadow var(--dur-med) var(--ease-out),
             filter var(--dur-fast) var(--ease-out);
}
.btn:hover{ filter:brightness(1.07); box-shadow:var(--shadow-pop); }
.btn:active{ transform:scale(.965); }
.btn:disabled,.btn[aria-disabled="true"]{
  opacity:.5; cursor:not-allowed; box-shadow:none; filter:none; transform:none;
}
.btn svg{ width:1.1em; height:1.1em; flex:0 0 auto; }

.btn.secondary,.btn--ghost{
  --btn-bg: transparent;
  --btn-fg: var(--muted-strong);
  border-color:var(--border-strong);
  box-shadow:none;
}
.btn.secondary:hover,.btn--ghost:hover{
  background:var(--surface-hover);
  color:var(--text);
  box-shadow:none;
  filter:none;
}
.btn--lg{ padding:16px 30px; font-size:calc(var(--font-btn-size) + 2px); }
.btn--block{ width:100%; }

/* Onde au clic (injectée par ux.js) */
.btn .ripple{
  position:absolute; border-radius:50%;
  background:currentColor; opacity:.28;
  transform:scale(0); pointer-events:none;
  animation:ripple var(--dur-slow) var(--ease-out) forwards;
}
@keyframes ripple{ to{ transform:scale(2.6); opacity:0; } }

/* État de chargement */
.btn.is-loading{ color:transparent !important; pointer-events:none; }
.btn.is-loading::after{
  content:""; position:absolute; inset:0; margin:auto;
  width:18px; height:18px; border-radius:50%;
  border:2px solid color-mix(in oklab, var(--btn-fg) 35%, transparent);
  border-top-color:var(--btn-fg);
  animation:spin .7s linear infinite;
}
@keyframes spin{ to{ transform:rotate(360deg); } }

/* Bouton icône (fermeture, suppression) */
.xbtn{
  display:inline-flex; align-items:center; justify-content:center;
  width:36px; height:36px; flex:0 0 auto;
  border-radius:var(--r-sm);
  border:1px solid var(--border-strong);
  background:var(--surface-sunken);
  color:var(--muted-strong);
  font-size:18px; line-height:1; cursor:pointer;
  transition:background var(--dur-fast), color var(--dur-fast), transform var(--dur-fast) var(--ease-spring);
}
.xbtn:hover{ background:var(--danger-bg); color:var(--danger); border-color:var(--danger); }
.xbtn:active{ transform:scale(.9); }

/* =============================== CHIPS =================================== */
.chip{
  display:inline-flex; align-items:center; gap:var(--sp-2);
  padding:8px 14px;
  background:var(--surface-sunken);
  border:1px solid var(--border-soft);
  border-radius:var(--r-pill);
  color:var(--muted-strong);
  font-size:var(--fs-sm); font-weight:600;
  cursor:pointer;
  transition:background var(--dur-fast), color var(--dur-fast),
             border-color var(--dur-fast), transform var(--dur-fast) var(--ease-spring);
}
.chip:hover{ background:var(--surface-hover); color:var(--text); }
.chip:active{ transform:scale(.96); }
.chip[aria-selected="true"],.chip.is-active{
  background:var(--accent);
  border-color:transparent;
  color:var(--accent-contrast);
  box-shadow:var(--shadow-pop);
}
/* Action principale d'une carte : même aplat que la puce sélectionnée, mais
   « is-active » et aria-selected disent « filtre choisi ». Une action n'est
   pas un état ; elle mérite sa classe plutôt que d'emprunter la leur. */
.chip--primaire{
  background:var(--accent);
  border-color:transparent;
  color:var(--accent-contrast);
  box-shadow:var(--shadow-pop);
}
.chip--primaire:hover{ background:var(--accent); color:var(--accent-contrast); filter:brightness(1.06); }

.chip--danger{ color:var(--danger); border-color:color-mix(in oklab, var(--danger) 40%, var(--border)); }
.chip--danger:hover{ background:var(--danger-bg); color:var(--danger); }
.chip--static{ cursor:default; }
.chip--static:hover{ background:var(--surface-sunken); color:var(--muted-strong); }

.tabs{ display:flex; flex-wrap:wrap; gap:var(--sp-2); }

/* ============================== CHAMPS =================================== */
.field{ display:flex; flex-direction:column; gap:6px; min-width:0; }
.field > label,.labelRow{
  display:flex; align-items:center; gap:6px;
  font-family:var(--font-title-family);
  font-size:var(--fs-md);
  font-weight:700;
  color:var(--muted-strong);
}
.labelText{ font-weight:700; }

/* Regroupements de champs (<fieldset>/<legend>) : le balisage sert aux
   technologies d'assistance, le rendu reste celui du bloc qui les porte.
   min-inline-size:0 est indispensable — un fieldset vaut min-content par
   défaut et ferait déborder les grilles. */
fieldset{ min-inline-size:0; border:0; margin:0; padding:0; }
legend{ display:block; width:100%; padding:0; float:none; }
.field__hint{ color:var(--faint); font-size:var(--fs-xs); }

input,select,textarea{
  width:100%;
  padding:12px 14px;
  background:var(--input-bg);
  color:var(--text);
  border:1.5px solid var(--border-soft);
  border-radius:var(--r-md);
  font-family:var(--font-body-family);
  font-size:var(--font-body-size);
  min-width:0;
  transition:border-color var(--dur-fast), box-shadow var(--dur-med) var(--ease-out),
             background var(--dur-fast);
}
input::placeholder,textarea::placeholder{ color:var(--placeholder); }
input:hover,select:hover,textarea:hover{ border-color:var(--border-strong); }
input:focus,select:focus,textarea:focus{
  outline:none;
  border-color:var(--accent);
  box-shadow:var(--shadow-glow);
  background:var(--surface);
}
textarea{ resize:vertical; min-height:104px; line-height:1.55; }
select{
  appearance:none;
  padding-right:40px;
  background-image:linear-gradient(45deg,transparent 50%,currentColor 50%),
                   linear-gradient(135deg,currentColor 50%,transparent 50%);
  background-position:calc(100% - 20px) 52%, calc(100% - 14px) 52%;
  background-size:6px 6px, 6px 6px;
  background-repeat:no-repeat;
  cursor:pointer;
}
input[type="color"]{ padding:4px; height:44px; cursor:pointer; }
input[type="file"]{ padding:9px 12px; cursor:pointer; }
input[readonly]{ background:var(--surface-sunken); color:var(--muted-strong); cursor:default; }

/* Champ avec icône à gauche */
.field__control{ position:relative; display:flex; align-items:center; }
.field__control .field__icon{
  position:absolute; left:14px;
  display:flex; color:var(--faint);
  pointer-events:none;
  transition:color var(--dur-fast);
}
.field__control:focus-within .field__icon{ color:var(--accent); }
.field__control:has(.field__icon) input{ padding-left:44px; }

/* Suffixe (unités : m, m³, km) */
.field__suffix{
  position:absolute; right:14px;
  color:var(--faint); font-size:var(--fs-sm); font-weight:700;
  pointer-events:none;
}
.field__control:has(.field__suffix) input{ padding-right:52px; }

/* La date du formulaire (partials/form.php) : « Choisir la date du
   déménagement » à la place du « jj/mm/aaaa » du navigateur, tant que le champ
   est vide et n'a pas le focus (ux.js, brancherAideDate). */
.dateChamp input[type="date"]{ cursor:pointer; }
.dateChamp__aide{
  position:absolute; left:15.5px; right:46px; top:50%; transform:translateY(-50%);
  color:var(--placeholder);
  font-family:var(--font-body-family); font-size:var(--font-body-size); line-height:1.2;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
  pointer-events:none;
}
.dateChamp:not(.est-vide) .dateChamp__aide,
.dateChamp:focus-within .dateChamp__aide{ display:none; }
.dateChamp.est-vide input[type="date"]:not(:focus){ color:transparent; }
.dateChamp.est-vide input[type="date"]:not(:focus)::-webkit-datetime-edit{ color:transparent; }

/* Validation */
.is-error{
  border-color:var(--danger) !important;
  box-shadow:0 0 0 4px color-mix(in oklab, var(--danger) 16%, transparent) !important;
  animation:shake 340ms var(--ease-out);
}
@keyframes shake{
  0%,100%{ transform:translateX(0); }
  20%{ transform:translateX(-6px); }
  40%{ transform:translateX(5px); }
  60%{ transform:translateX(-3px); }
  80%{ transform:translateX(2px); }
}
.error{
  display:flex; align-items:center; gap:6px;
  color:var(--danger);
  font-size:var(--fs-sm); font-weight:600;
  margin-top:4px;
  animation:slideDown var(--dur-med) var(--ease-out);
}
.error::before{
  content:""; flex:0 0 auto;
  width:15px; height:15px;
  background:currentColor;
  -webkit-mask:var(--icon-alert) center/contain no-repeat;
  mask:var(--icon-alert) center/contain no-repeat;
}
@keyframes slideDown{ from{ opacity:0; transform:translateY(-6px); } to{ opacity:1; transform:none; } }

.is-valid{ border-color:color-mix(in oklab, var(--ok) 55%, var(--border)) !important; }

/* ===================== SEGMENTED CONTROL (Oui / Non) ===================== */
/* Remplace visuellement un <select> conservé en .sr-only (voir ux.js) */
.segmented{
  display:inline-flex;
  padding:4px;
  gap:4px;
  background:var(--surface-sunken);
  border:1px solid var(--border-soft);
  border-radius:var(--r-pill);
  position:relative;
}
.segmented--block{ display:flex; width:100%; }
.segmented__opt{
  flex:1 1 0;
  display:inline-flex; align-items:center; justify-content:center; gap:6px;
  padding:9px 16px;
  border:0; background:transparent;
  color:var(--muted);
  border-radius:var(--r-pill);
  font-size:var(--fs-md); font-weight:700;
  cursor:pointer;
  white-space:nowrap;
  transition:color var(--dur-fast), transform var(--dur-fast) var(--ease-spring);
}
.segmented__opt:hover{ color:var(--text); }
.segmented__opt:active{ transform:scale(.95); }
.segmented__opt[aria-pressed="true"]{
  background:var(--accent);
  color:var(--accent-contrast);
  box-shadow:var(--shadow-sm);
  animation:pop var(--dur-med) var(--ease-spring);
}
@keyframes pop{ 0%{ transform:scale(.9); } 60%{ transform:scale(1.05); } 100%{ transform:scale(1); } }

/* ========================= STEPPER − / + ================================= */
.stepper{
  display:flex; align-items:center; gap:var(--sp-2);
  padding:4px;
  background:var(--input-bg);
  border:1.5px solid var(--border-soft);
  border-radius:var(--r-pill);
  transition:border-color var(--dur-fast), box-shadow var(--dur-med);
}
.stepper:focus-within{ border-color:var(--accent); box-shadow:var(--shadow-glow); }
.stepper__btn{
  width:36px; height:36px; flex:0 0 auto;
  display:inline-flex; align-items:center; justify-content:center;
  border:0; border-radius:50%;
  background:var(--surface);
  color:var(--muted-strong);
  font-size:20px; font-weight:700; line-height:1;
  cursor:pointer;
  box-shadow:var(--shadow-xs);
  transition:background var(--dur-fast), color var(--dur-fast),
             transform var(--dur-fast) var(--ease-spring);
}
.stepper__btn:hover{ background:var(--accent); color:var(--accent-contrast); }
.stepper__btn:active{ transform:scale(.85); }
.stepper__btn:disabled{ opacity:.35; cursor:not-allowed; transform:none; background:var(--surface); color:var(--muted); }
.stepper input{
  flex:1 1 auto; width:100%; min-width:48px;
  border:0; background:transparent; box-shadow:none !important;
  text-align:center;
  font-weight:800; font-variant-numeric:tabular-nums;
  padding:6px 2px;
  -moz-appearance:textfield;
}
.stepper input::-webkit-outer-spin-button,
.stepper input::-webkit-inner-spin-button{ -webkit-appearance:none; margin:0; }
.stepper input:focus{ outline:none; }
/* Petit sursaut quand la valeur change */
.stepper.is-bumped input{ animation:bump var(--dur-med) var(--ease-spring); }
@keyframes bump{ 0%{ transform:scale(1); } 45%{ transform:scale(1.22); } 100%{ transform:scale(1); } }

/* ============================= TOOLTIPS ================================== */
.help{
  display:inline-flex; align-items:center; justify-content:center;
  width:18px; height:18px; flex:0 0 auto;
  border-radius:50%;
  background:var(--surface-sunken);
  border:1px solid var(--border-soft);
  /* --muted tombait à 4,12:1 sur --surface-sunken avec certaines palettes
     clientes, sous le minimum de 4,5:1. --muted-strong tient 7,9:1 sur la
     palette par défaut et reste au-dessus du seuil sur les autres. */
  color:var(--muted-strong);
  font-size:11px; font-weight:800; line-height:1;
  cursor:help; user-select:none;
  transition:background var(--dur-fast), color var(--dur-fast);
}
.helpTip{ position:relative; display:inline-flex; }
.helpTip:hover .help,.helpTip:focus-within .help{ background:var(--accent); color:var(--accent-contrast); border-color:transparent; }
.helpBubble{
  position:absolute; left:50%; bottom:calc(100% + 10px);
  /* --tip-dx : le décalage posé par placerBulle() (app.js) quand la bulle,
     centrée sur son « ? », sortirait de l'écran ou du panneau. */
  transform:translate(calc(-50% + var(--tip-dx, 0px)),4px);
  /* Trois réglages qui font toute la lisibilité de ces bulles.

     La LARGEUR d'abord : à 280 px, une explication de deux phrases se brisait
     en trois lignes courtes. Quelques dizaines de pixels de plus, et la même
     phrase tient en deux lignes pleines — on lit une phrase, pas un empilement.

     L'INTERLIGNE ensuite : 1,45 sur du 13 px, c'est serré partout, et
     davantage encore ici. Du texte clair sur fond très sombre paraît déborder
     sur lui-même — l'œil a besoin de plus d'air dans ce sens que dans l'autre.

     Le RETRAIT enfin, pour que le texte ne touche pas le bord de la bulle. */
  width:max-content; max-width:min(340px, 78vw);
  padding:12px 15px;
  background:color-mix(in oklab, var(--text) 92%, black);
  color:color-mix(in oklab, var(--card) 96%, white);
  border-radius:var(--r-sm);
  box-shadow:var(--shadow-lg);
  font-size:var(--fs-sm); font-weight:500; line-height:1.6;
  opacity:0; pointer-events:none; z-index:60;
  transition:opacity var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out);
}
.helpBubble::after{
  content:""; position:absolute; top:100%; left:calc(50% - var(--tip-dx, 0px));
  transform:translateX(-50%);
  border:6px solid transparent;
  border-top-color:color-mix(in oklab, var(--text) 92%, black);
}
.helpTip:hover .helpBubble,
.helpTip:focus .helpBubble,
.helpTip:focus-within .helpBubble,
.helpTip.open .helpBubble,
.helpTip[data-open="true"] .helpBubble,
.helpTip[aria-expanded="true"] .helpBubble{ opacity:1; transform:translate(calc(-50% + var(--tip-dx, 0px)),0); }
/* Une bulle ne contient que du texte : elle ne capte jamais le clic. Ouverte
   sous son « ? », elle recouvre le champ — le clic doit l'atteindre, et le
   focus qui quitte le « ? » referme la bulle. */

/* Pas la place au-dessus (premier champ d'un panneau qui défile) : la bulle
   passe sous le « ? », la flèche pointe vers le haut. */
.helpTip--bas .helpBubble{ bottom:auto; top:calc(100% + 10px); }
.helpTip--bas .helpBubble::after{
  top:auto; bottom:100%;
  border-top-color:transparent;
  border-bottom-color:color-mix(in oklab, var(--text) 92%, black);
}

/* Sur petit écran, une bulle centrée sur son point d'ancrage sort de l'écran.
   On l'ancre alors sur toute la largeur du libellé plutôt que sur l'icône. */
@media (max-width:640px){
  .field > label,.labelRow{ position:relative; }
  .helpTip{ position:static; }
  .helpBubble{
    left:0; right:0;
    width:auto; max-width:none;
    /* Un libellé court (une pastille de couleur) ferait une bulle de 130 px :
       on garde de quoi lire une phrase, placerBulle() la ramène dans l'écran. */
    min-width:min(240px, calc(100vw - 48px));
    transform:translate(var(--tip-dx, 0px),4px);
  }
  .helpBubble::after{ left:auto; right:14px; transform:none; }
  .helpTip:hover .helpBubble,
  .helpTip:focus .helpBubble,
  .helpTip:focus-within .helpBubble,
  .helpTip.open .helpBubble,
  .helpTip[data-open="true"] .helpBubble,
  .helpTip[aria-expanded="true"] .helpBubble{ transform:translate(var(--tip-dx, 0px),0); }

  /* Dans un champ flottant, le « ? » reste positionné dans le champ : la
     bulle « sur toute la largeur du libellé » ne faisait donc que la largeur
     de l'icône — 30 px, une lettre par ligne. On la cale sur le bord droit
     du champ, et elle s'étend vers la gauche. */
  .flot > .helpTip .helpBubble{
    left:auto; right:-12px;
    width:max-content; max-width:calc(100vw - 48px);
  }
  .flot > .helpTip .helpBubble::after{ right:16px; }
}

[data-tip]{ position:relative; }
[data-tip]::after{
  content:attr(data-tip);
  position:absolute; left:50%; bottom:calc(100% + 8px);
  transform:translate(-50%,4px);
  width:max-content; max-width:min(300px,76vw);
  padding:9px 13px;
  background:color-mix(in oklab, var(--text) 92%, black);
  color:color-mix(in oklab, var(--card) 96%, white);
  border-radius:var(--r-sm);
  font-size:var(--fs-xs); font-weight:600; line-height:1.55;
  box-shadow:var(--shadow-lg);
  opacity:0; pointer-events:none; z-index:60;
  transition:opacity var(--dur-fast), transform var(--dur-fast);
}
[data-tip]:hover::after,[data-tip]:focus-visible::after{ opacity:1; transform:translate(-50%,0); }

/* Verrou premium.

   Surtout pas `opacity` : une opacité inférieure à 1 crée un contexte
   d'empilement, et l'infobulle « Réservé au plan Premium » — un ::after de ce
   même bouton — se retrouvait peinte à l'intérieur. Résultat : elle sortait
   délavée, le texte de la carte la traversait, et son z-index ne servait à
   rien puisqu'il ne s'appliquait plus qu'à l'intérieur du bouton.
   On grise donc explicitement. Les quatre boutons concernés (export CSV,
   édition de devis, et deux dans la configuration) y gagnent en plus un gris
   franc plutôt qu'une teinte pâlie. */
[data-premium-locked]{
  cursor:not-allowed !important;
  background:var(--surface-sunken) !important;
  border-color:var(--border-soft) !important;
  color:var(--muted) !important;
  box-shadow:none !important;
}
[data-premium-locked] svg,[data-premium-locked] img{ opacity:.7; }
[data-tip-premium]{ position:relative; }
[data-tip-premium][data-tip-open="1"]::after{
  content:attr(data-tip-premium);
  position:absolute; left:50%; top:calc(100% + 8px);
  transform:translateX(-50%);
  padding:8px 12px;
  background:var(--premium); color:var(--premium-ink);
  border-radius:var(--r-sm);
  font-size:var(--fs-xs); font-weight:800; white-space:nowrap;
  box-shadow:var(--shadow-lg); z-index:70;
}

/* ============================== TOASTS =================================== */
#toastMsg{
  position:fixed; left:50%; bottom:calc(24px + var(--barreBas-h, 0px));
  transform:translate(-50%,16px);
  display:flex; align-items:center; gap:var(--sp-2);
  max-width:min(440px,92vw);
  padding:13px 20px;
  background:color-mix(in oklab, var(--text) 93%, black);
  color:color-mix(in oklab, var(--card) 97%, white);
  border-radius:var(--r-pill);
  box-shadow:var(--shadow-lg);
  font-size:var(--fs-md); font-weight:600;
  text-align:center;
  opacity:0; pointer-events:none; z-index:9999;
  transition:opacity var(--dur-med) var(--ease-out), transform var(--dur-med) var(--ease-spring);
}
#toastMsg.is-visible{ transform:translate(-50%,0); }

/* ====================== AUTOCOMPLETE D'ADRESSES ========================== */
.ac-host{ position:relative; }
.ac-list{
  position:absolute; left:0; right:0; top:calc(100% + 6px);
  z-index:1000;
  max-height:280px; overflow:auto;
  padding:6px;
  background:var(--surface);
  border:1px solid var(--border-soft);
  border-radius:var(--r-md);
  box-shadow:var(--shadow-lg);
  display:none;
  animation:acIn var(--dur-fast) var(--ease-out);
}
@keyframes acIn{ from{ opacity:0; transform:translateY(-6px); } to{ opacity:1; transform:none; } }
.ac-list .ac-item,.ac-list button{
  display:flex; align-items:center; gap:var(--sp-2);
  width:100%; text-align:left;
  padding:11px 12px;
  border:0; border-radius:var(--r-sm);
  background:transparent; color:var(--text);
  font-size:var(--fs-md);
  cursor:pointer;
  transition:background var(--dur-fast);
}
/* Épingle Lucide « map-pin », en masque pour hériter de la couleur du texte */
.ac-list .ac-item::before,.ac-list button::before{
  content:""; flex:0 0 auto;
  width:16px; height:16px;
  background:currentColor; opacity:.55;
  -webkit-mask:var(--icon-map-pin) center/contain no-repeat;
  mask:var(--icon-map-pin) center/contain no-repeat;
}
.ac-list .ac-item:hover,.ac-list button:hover,
.ac-list .ac-item:focus-visible,.ac-list button:focus-visible{
  background:var(--accent-14); outline:none;
}

/* ============================== LISTES =================================== */
.list{ display:flex; flex-direction:column; gap:var(--sp-2); }
.rowItem{
  display:grid; grid-template-columns:1fr auto;
  align-items:center; gap:var(--sp-3);
  padding:var(--sp-3) var(--sp-4);
  background:var(--surface-raised);
  border:1px solid var(--border-soft);
  border-radius:var(--r-md);
  transition:border-color var(--dur-fast), transform var(--dur-fast);
}
.rowItem:hover{ border-color:var(--border-strong); }
.rowItem > *{ min-width:0; }
.rowItem .actions{ display:flex; gap:6px; justify-content:flex-end; flex-wrap:wrap; }
@media (max-width:620px){
  .rowItem{ grid-template-columns:1fr; }
  .rowItem .actions{ justify-content:stretch; }
}

/* Bouton de suppression compact (contrat hérité du panel admin).
   Le contrat est la classe ou l'attribut data-action, jamais le libellé :
   button[title*="Supprimer"] attrapait aussi #brandLogoClear, qui porte déjà
   son icône. Résultat, deux croix dans le même bouton et la palette danger
   imposée à un bouton qui ne la demandait pas. */
.btn-delete,[data-action="delete"]{
  display:inline-flex; align-items:center; justify-content:center;
  width:34px; height:34px; min-width:34px; padding:0;
  border-radius:var(--r-sm);
  background:var(--danger-bg);
  color:var(--danger);
  border:1px solid color-mix(in oklab, var(--danger) 45%, transparent);
  cursor:pointer; font-size:0; line-height:1;
  transition:background var(--dur-fast), transform var(--dur-fast) var(--ease-spring);
}
.btn-delete::before,[data-action="delete"]::before{
  content:"✕"; font-size:15px; font-weight:900;
}
.btn-delete:hover,[data-action="delete"]:hover{ background:var(--danger); color:#fff; }
.btn-delete:active,[data-action="delete"]:active{ transform:scale(.88); }
[data-action="delete"] span{ display:none; }

/* ============================== BADGES =================================== */
.badge{
  display:inline-flex; align-items:center; gap:5px;
  padding:4px 10px;
  border-radius:var(--r-pill);
  background:var(--surface-sunken);
  color:var(--muted-strong);
  font-size:var(--fs-xs); font-weight:700;
  white-space:nowrap;
}
.badge--ok{ background:var(--ok-bg); color:var(--ok); }
.badge--warn{ background:var(--warn-bg); color:var(--warn); }
.badge--accent{ background:var(--accent-14); color:var(--accent-strong); }

/* Pastille Premium — or, avec un reflet qui balaie lentement.
   L'or est une couleur fixe, volontairement : elle ne suit pas la palette du
   client. Une distinction d'abonnement doit se reconnaitre du premier coup
   d'oeil, quelle que soit la charte, et se confondrait avec le reste si elle
   empruntait la couleur d'accent. */
.badge--or{
  position:relative;
  overflow:hidden;
  background:linear-gradient(135deg,
    color-mix(in oklab, var(--premium) 30%, var(--card)),
    color-mix(in oklab, var(--premium) 16%, var(--card)));
  /* 72% d or donnait 3,47:1 sur son propre fond : joli, illisible. En tirant
     vers --text on gagne le contraste sans perdre la teinte doree, et ca marche
     dans les deux sens puisque --text suit le theme du client. */
  color:color-mix(in oklab, var(--premium) 42%, var(--text));
  border:1px solid color-mix(in oklab, var(--premium) 42%, transparent);
}
.badge--or::after{
  content:"";
  position:absolute; inset:0;
  background:linear-gradient(105deg,
    transparent 38%,
    color-mix(in oklab, var(--premium) 55%, white) 50%,
    transparent 62%);
  transform:translateX(-120%);
  animation:refletOr 4.5s var(--ease-smooth) infinite;
  pointer-events:none;
}
@keyframes refletOr{
  0%, 62% { transform:translateX(-120%); }
  100%    { transform:translateX(120%); }
}
/* Le reflet est un ornement : il disparait pour qui a demande moins d'animations. */
@media (prefers-reduced-motion: reduce){
  .badge--or::after{ animation:none; opacity:0; }
}

/* Dernier recours de la copie du lien d'acceptation : quand ni le
   presse-papiers moderne ni execCommand n'ont abouti, on affiche le lien
   lui-même, déjà sélectionné. Il remplace le bouton à sa place exacte, d'où
   la largeur souple : il doit tenir dans une rangée d'actions sans la casser. */
.lienBrut{
  flex:1 1 240px; min-width:0;
  padding:7px 11px;
  background:var(--surface-sunken);
  border:1px solid var(--border-soft);
  border-radius:var(--r-pill);
  color:var(--muted-strong);
  font-size:var(--fs-xs);
  font-family:ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

/* ===========================================================================
   CHAMP À ÉTIQUETTE FLOTTANTE
   ---------------------------------------------------------------------------
   L'étiquette vit DANS le champ et monte dès qu'on écrit. Deux raisons de
   préférer ça à un libellé posé au-dessus : le champ reste identifiable une
   fois rempli — un formulaire long relu de haut en bas ne devient pas une
   colonne de valeurs sans nom — et le bloc gagne une ligne, ce qui compte sur
   sept étapes.

   L'ordre du balisage est INVERSÉ à dessein : l'input vient AVANT son
   étiquette. C'est ce qui permet à `:not(:placeholder-shown) + label` de
   remonter l'étiquette sans une ligne de JavaScript. D'où le
   `placeholder=" "` — une espace, pas une chaîne vide : sans elle, le champ
   serait toujours considéré comme « placeholder affiché » et l'étiquette ne
   bougerait jamais.

   Le `for` reste posé, l'ordre visuel ne change pas pour un lecteur d'écran :
   il annonce l'étiquette avec le champ, comme avant.
   =========================================================================== */
.flot{ position:relative; }

.flot > input,
.flot > textarea{
  padding:23px 14px 9px;          /* la place de l'étiquette en haut */
}
.flot > textarea{ padding-top:26px; min-height:104px; }

.flot > label{
  position:absolute; left:15px; top:15px;
  margin:0; padding:0 2px;
  max-width:calc(100% - 46px);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
  color:var(--placeholder);
  font-family:var(--font-body-family);
  font-size:var(--font-body-size);
  font-weight:500;
  pointer-events:none;
  transition:top var(--dur-fast) var(--ease-out),
             font-size var(--dur-fast) var(--ease-out),
             color var(--dur-fast);
}

/* L'étiquette monte : au focus, ou dès qu'il y a quelque chose dans le champ. */
.flot > input:focus + label,
.flot > input:not(:placeholder-shown) + label,
.flot > textarea:focus + label,
.flot > textarea:not(:placeholder-shown) + label{
  top:7px;
  font-size:var(--fs-xs);
  font-weight:700;
  letter-spacing:.02em;
  color:var(--accent);
}
/* Champ rempli mais pas actif : l'étiquette reste haute, en gris —
   l'accent est réservé à ce qui a le focus. */
.flot > input:not(:placeholder-shown):not(:focus) + label,
.flot > textarea:not(:placeholder-shown):not(:focus) + label{
  color:var(--muted);
}

/* La bulle d'aide prend l'emplacement d'icône, à droite du champ. */
.flot > .helpTip{
  position:absolute; right:12px; top:50%;
  transform:translateY(-50%);
  z-index:2;
}
/* La place de l'icône est réservée sur TOUS les champs flottants, qu'ils
   portent une aide ou non. La régler au cas par cas demanderait `:has()`, que
   les navigateurs un peu anciens ignorent — et l'ignorer voudrait dire, ici,
   du texte qui passe sous le point d'interrogation. Quarante-quatre pixels de
   marge à droite ne se voient pas ; du texte tronqué, si. */
.flot > input,
.flot > textarea{ padding-right:44px; }

/* Sur un textarea, l'aide se cale en haut : le champ fait cent pixels, une
   icône centrée verticalement y flotterait sans rapport avec rien. */
.flot--zone > .helpTip{ top:16px; transform:none; }

/* Chaque « ? » d'un champ flottant forme sa propre pile (z-index:2) : une
   bulle ouverte vers le bas passait sous le « ? » du champ suivant. Celui
   dont la bulle est ouverte monte au-dessus des autres. */
.flot > .helpTip:hover,
.flot > .helpTip:focus-within,
.flot > .helpTip[aria-expanded="true"]{ z-index:61; }

/* Le bandeau « mentions manquantes » (lib/societe.php).
   Il hérite de .note--ko pour la couleur, mais il porte une liste et un bouton :
   il lui faut de l'air là où une note ordinaire n'est qu'une phrase. */
.societeAlerte{ margin-bottom:var(--sp-5); }
.societeAlerte strong{
  display:flex; align-items:center; gap:8px;
  margin-bottom:6px; font-size:var(--fs-md);
}
.societeAlerte p{ margin:0 0 8px; font-weight:400; }
.societeAlerte ul{ margin:0 0 12px; padding-left:20px; }
.societeAlerte li{ margin-bottom:2px; }
.societeAlerte .btn{ margin-top:2px; }

/* ===========================================================================
   LES PHOTOS D'ACCES, COTE DEMENAGEUR
   ===========================================================================
   Elles arrivent dans la fiche d'une demande. Assez grandes pour juger d'un
   acces au premier coup d'oeil, cliquables pour la vraie taille : c'est en
   agrandissant qu'on voit si le canape passe dans l'ascenseur.
   =========================================================================== */

.leadPhotos{ margin:0 0 var(--sp-4) 0; }
.leadPhotos__grille + .leadPhotos__titre{ margin-top:var(--sp-3); }
/* L'inventaire par pièce d'une demande (leads.js, inventaireBloc). */
.leadInventaire{ margin:0 0 var(--sp-4) 0; }
.leadInventaire ul{ margin:0; padding:0; list-style:none; display:grid; gap:var(--sp-2); }
.leadInventaire li{
  padding:var(--sp-2) var(--sp-3);
  background:var(--surface-sunken);
  border-radius:var(--r-sm);
  font-size:var(--fs-sm); line-height:1.5;
}
.leadInventaire__m3{ color:var(--muted); font-size:var(--fs-xs); font-weight:700; }
.leadInventaire__qte{ color:var(--muted-strong); font-weight:700; white-space:nowrap; }

.leadPhotos__titre{
  margin:0 0 var(--sp-2) 0;
  font-size:.86rem; font-weight:600;
}
.leadPhotos__note{ font-weight:400; color:var(--muted); }

.leadPhotos__grille{
  display:grid;
  grid-template-columns:repeat(auto-fill, minmax(140px, 1fr));
  gap:var(--sp-2);
}

.leadPhoto{
  display:flex; flex-direction:column; gap:4px;
  text-decoration:none; color:inherit;
}
.leadPhoto img{
  display:block; width:100%; height:104px;
  object-fit:cover; object-position:center;
  border:1px solid var(--border); border-radius:8px;
  background:var(--surface-sunken);
}
.leadPhoto span{
  font-size:.76rem; line-height:1.3; color:var(--muted);
}
.leadPhoto:hover img{ border-color:var(--border-strong); }

/* ===========================================================================
   L'ETAT COMMERCIAL D'UNE DEMANDE
   ===========================================================================
   Trois etats se deduisent de la donnee — un devis edite, une acceptation —
   et deux demandent une decision : « a rappeler » et « perdue ». Seuls ces
   deux-la sont proposes : offrir « Gagnee » laisserait croire qu'on peut
   declarer une vente que le client n'a pas signee.
   =========================================================================== */

.chip--select{
  /* `flex:0 0 auto` et une largeur bornee : dans une rangee d'actions en
     flex-wrap, un <select> sans contrainte prend toute la place restante et
     renvoie « Supprimer » a la ligne suivante. */
  flex:0 0 auto;
  width:auto; min-width:132px; max-width:180px;
  padding:5px 10px;
  border:1px solid var(--border);
  border-radius:999px;
  background:var(--surface);
  font:inherit; font-size:.82rem;
  cursor:pointer;
}
.chip--select:disabled{ opacity:.55; cursor:progress; }

.badge--info{  background:#e8f0fe; border-color:#c3d6f7; color:#1a4d8f; }
.badge--muted{ background:var(--surface-sunken); border-color:var(--border); color:var(--muted); }

/* La pastille precede le montant dans l'en-tete : on lit l'etat avant le
   chiffre, parce que c'est lui qui dit s'il y a quelque chose a faire. */
.lead__total .badge{ margin-right:var(--sp-2); }

/* Une liste de lignes « quelque chose a faire » : le quoi a gauche, le geste
   a droite. Sert aux devis sans reponse du tableau de bord. */
.listeSimple{ list-style:none; margin:0; padding:0; }
.listeSimple li{
  display:flex; align-items:center; justify-content:space-between;
  gap:var(--sp-3);
  padding:var(--sp-3) 0;
  border-bottom:1px solid var(--border-soft);
}
.listeSimple li:last-child{ border-bottom:0; }
@media (max-width:560px){
  .listeSimple li{ flex-direction:column; align-items:flex-start; gap:var(--sp-2); }
}
