/* =============================================================
   Socle · Sélecteur (.sc-select)
   Choix d'une valeur dans une liste fermée. Le panneau appartient au
   système de design et non au système d'exploitation : c'est du HTML
   ordinaire, donc il se style, se groupe et porte une seconde ligne.
   Câblé par js/components/select.js (initSelects).
   Anatomie : .sc-select > __trigger (> __value) + __panel
              (> __list > __group-label, __option
                (> __option-text > __option-label, __option-help))
   Le panneau est masqué par [hidden].
   Tailles : --s, --l (défaut : m)

   Le <select> natif a été retiré du système sur décision de Benjamin,
   le 02/09/2026. Ce qu'il en coûte est écrit sur la page de doc, pour
   que la décision reste lisible : sur mobile, le panneau du téléphone
   ne s'ouvre plus, et ce composant demande son module JavaScript.
   ============================================================= */

.sc-select {
  position: relative;
  display: block;
}

/* ---- Déclencheur ----
   Un <button>, et c'est ce qui rend le reste possible : il accepte les
   pseudo-éléments et les enfants, là où un <select> refuse les deux.
   Sa surface reste celle d'un champ de saisie, hauteur, épaules et
   filet compris : fermé, un sélecteur et un champ sont deux contrôles
   de la même famille et doivent le montrer. */

.sc-select__trigger {
  /* Tokens de composant : tailles et états ne font que réassigner ceci. */
  --_select-height: var(--control-height-m);
  --_select-padding-x: var(--field-padding);
  --_select-font-size: var(--text-body-size);
  /* Le filet ordinaire, comme le champ : les deux se posent côte à côte
     dans un formulaire et ne peuvent pas peser différemment. */
  /* Même retrait qu'un bouton : au repos, ce qui commande est en
     second plan. La valeur retenue, elle, remonte à l'encre pleine,
     parce que c'est une donnée et non une prise : voir plus bas. */
  --_select-text: var(--color-text-muted);
  --_select-border: var(--color-border);
  --_select-border-hover: var(--color-border-strong);

  /* Positionné pour son chevron : le pseudo-élément se cale sur le
     déclencheur et non sur l'enveloppe, qui pourrait porter autre chose. */
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--gap-s);
  width: 100%;
  height: var(--_select-height);
  padding-inline-start: var(--_select-padding-x);
  /* Réserve la place du chevron à droite du texte, comme le natif. */
  padding-inline-end: calc(var(--_select-padding-x) + var(--space-20));
  background: var(--color-bg-surface);
  border: var(--border-hairline) solid var(--_select-border);
  border-radius: var(--radius-control);
  color: var(--color-text);
  font-family: var(--font-body);
  font-size: var(--_select-font-size);
  line-height: var(--leading-normal);
  text-align: start;
  cursor: pointer;
  transition:
    border-color var(--motion-fast),
    background-color var(--motion-fast),
    box-shadow var(--motion-fast);
}

/* La valeur occupe la place et se tronque : une option longue ne
   doit pas pousser le contrôle hors de sa colonne. */
/* La valeur retenue est une donnée, pas une prise : elle remonte à
   l'encre pleine pendant que le reste du déclencheur, son chevron
   compris, reste en retrait. C'est la même règle que partout ailleurs,
   ce qui commande s'efface, ce qui est dit se lit. */
.sc-select__value {
  color: var(--color-text);
  flex: 1;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* Aucune valeur retenue : le texte est une invite, pas une réponse. */
.sc-select__trigger[data-placeholder="true"] .sc-select__value {
  color: var(--color-text-muted);
}

/* ---- Chevron ----
   Posé sur le déclencheur et non sur l'enveloppe, contrairement au
   natif : un <button> accepte les pseudo-éléments là où un <select>
   les refuse. C'est ce qui lui permet de pivoter à l'ouverture. */

.sc-select__trigger::after {
  content: "";
  position: absolute;
  inset-inline-end: var(--_select-padding-x);
  inset-block-start: 50%;
  width: var(--space-6);
  height: var(--space-6);
  border-inline-end: var(--icon-stroke-width) solid currentColor;
  border-block-end: var(--icon-stroke-width) solid currentColor;
  transform: translateY(-70%) rotate(45deg);
  transform-origin: center;
  color: var(--color-text-muted);
  transition: transform var(--motion-fast);
  pointer-events: none;
}

.sc-select__trigger[aria-expanded="true"]::after {
  transform: translateY(-30%) rotate(-135deg);
}

/* ---- États du déclencheur ---- */

.sc-select__trigger:hover:not(:disabled),
.sc-select__trigger[data-state="hover"] {
  border-color: var(--_select-border-hover);
}

/* Le même focus qu'un champ : le filet prend l'accent et l'anneau vient
   se coller dessus. Un déclencheur de sélecteur est un champ du point de
   vue de qui remplit un formulaire, quoi qu'en dise son balisage.
   Vaut aussi pour la simulation de la doc. */
.sc-select__trigger:focus-visible,
.sc-select__trigger[data-state="focus"] {
  --_select-border: var(--color-accent);

  /* Réécrit en clair, et pas seulement par le jeton privé : la règle de
     survol pose border-color en direct, et une propriété directe bat
     toujours la réassignation d'une variable, quelle que soit la
     spécificité. Sans cette ligne, un champ survolé et focalisé gardait
     le filet du survol. */
  border-color: var(--_select-border);
  /* L'anneau se pose exactement SUR le filet plutôt qu'à côté : un
     décalage négatif d'une épaisseur de trait les fait coïncider, et
     l'arête reste à un pixel au lieu de doubler. C'est la seule façon
     d'avoir un focus qui ne grossit pas la boîte tout en gardant un
     outline, la seule chose que le mode contrastes forcés sait encore
     afficher quand il efface les couleurs. */
  outline: var(--border-hairline) solid var(--color-focus-ring);
  outline-offset: calc(var(--border-hairline) * -1);
  box-shadow: var(--elevation-raised);
}

.sc-select__trigger[aria-invalid="true"] {
  --_select-border: var(--color-danger);
  --_select-border-hover: var(--color-danger-hover);
}

.sc-select__trigger:disabled,
.sc-select__trigger[data-disabled] {
  background: var(--color-bg-subtle);
  opacity: var(--opacity-disabled);
  cursor: not-allowed;
}

/* ---- Panneau ----
   Mêmes surface, filet, rayon et ombre que le menu : deux panneaux
   qui s'ouvrent sous un déclencheur n'ont pas à se ressembler de
   loin et à différer de près. */

.sc-select__panel {
  /* Dimensions structurelles. La hauteur : au-delà, la liste défile
     plutôt que de sortir de l'écran. Réassignable en style inline sur
     une liste courte que l'on veut voir entière.
     La largeur : le panneau part de celle du déclencheur et ne descend
     jamais sous un minimum de lecture. Épousé au déclencheur, il
     coupait les mots dès que le champ était étroit, une option finissait
     collée à sa coche et un intertitre de groupe passait sur deux
     lignes. Ce que l'on choisit est plus long que le résumé qu'on en
     lit ensuite : le panneau n'a aucune raison de tenir dans la même
     boîte. */
  --_select-panel-max-height: 15rem;
  --_select-panel-min-width: 13rem;

  position: absolute;
  inset-block-start: calc(100% + var(--gap-2xs));
  inset-inline-start: 0;
  inset-inline-end: auto;
  min-inline-size: max(100%, var(--_select-panel-min-width));
  /* Il ne sort pas de la fenêtre pour autant. */
  max-inline-size: calc(100vw - var(--padding-3xl) * 2);
  z-index: var(--z-dropdown);
  max-height: var(--_select-panel-max-height);
  overflow-y: auto;
  padding: var(--padding-xs);
  background: var(--color-bg-surface);
  border: var(--border-hairline) solid var(--color-border);
  border-radius: var(--radius-surface);
  box-shadow: var(--elevation-floating);
}

/* Le panneau s'ouvre vers le haut quand le bas manque de place.
   Le module pose l'attribut, le CSS ne fait que le suivre. */
.sc-select__panel[data-placement="top"] {
  inset-block-start: auto;
  inset-block-end: calc(100% + var(--gap-2xs));
}

/* Plus large que son déclencheur et trop près du bord : il s'ancre par
   son bord de fin au lieu de sortir de l'écran. Le module pose
   l'attribut, le CSS ne fait que le suivre, comme pour le placement
   vertical. */
.sc-select__panel[data-align="end"] {
  inset-inline-start: auto;
  inset-inline-end: 0;
}

.sc-select__panel[hidden] {
  display: none;
}

/* Aucun écart entre deux options : une liste est une liste, pas une
   pile de pastilles. L'écart faisait flotter chaque ligne dans son
   propre cadre et cassait le balayage vertical du regard. */
.sc-select__list {
  display: flex;
  flex-direction: column;
  gap: 0;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* ---- Intitulé de groupe ----
   Même traitement que .sc-menu__label : c'est le même objet, un
   intertitre dans un panneau. */

.sc-select__group-label {
  padding: var(--padding-xs) var(--padding-s) var(--padding-2xs);
  color: var(--color-text-muted);
  font-size: var(--text-caption-size);
  font-weight: var(--text-label-weight);
}

/* ---- Options ---- */

.sc-select__option {
  /* Tokens de composant : les états ne font que réassigner ceci. */
  --_option-bg: transparent;
  --_option-text: var(--color-text);
  /* Gouttière de la coche, réservée sur toutes les options : sans
     elle, choisir une option décalerait le texte de toutes les autres. */
  --_option-mark-size: var(--space-16);

  display: flex;
  align-items: flex-start;
  gap: var(--gap-s);
  /* Une ligne de liste n'est jamais plus courte qu'une commande : elle
     se vise à la souris comme un bouton, et sur un écran tactile elle
     est la cible. Le plancher vient donc de la hauteur de contrôle
     plutôt que d'un rembourrage deviné, ce qui la fait suivre la
     densité du système sans qu'on ait à y revenir.
     Une option à deux lignes dépasse ce plancher, c'est le but. */
  min-block-size: var(--control-height-m);
  padding: var(--padding-m) var(--padding-l);
  border-radius: var(--radius-control);
  background: var(--_option-bg);
  color: var(--_option-text);
  font-size: var(--text-label-size);
  line-height: var(--leading-snug);
  cursor: pointer;
  transition:
    background-color var(--motion-fast),
    color var(--motion-fast);
}

.sc-select__option-text {
  display: flex;
  flex-direction: column;
  gap: var(--gap-2xs);
  flex: 1;
  min-width: 0;
}

.sc-select__option-label {
  font-weight: var(--text-body-weight);
}

/* La seconde ligne : ce qu'un <option> natif ne sait pas porter,
   et la raison d'être de ce composant. */
.sc-select__option-help {
  color: var(--color-text-muted);
  font-size: var(--text-caption-size);
  line-height: var(--leading-snug);
}

/* ---- Coche ----
   Le même dessin que .sc-checkbox, au même clip-path : une marque de
   sélection ne doit pas changer de forme d'un composant à l'autre. */

.sc-select__option::after {
  content: "";
  flex: none;
  width: var(--_option-mark-size);
  height: var(--_option-mark-size);
  background: currentColor;
  clip-path: polygon(14% 45%, 4% 55%, 40% 88%, 96% 21%, 86% 12%, 38% 68%);
  transform: scale(0);
  transition: transform var(--motion-fast);
}

/* Une coche, et rien d'autre. Le texte ne change ni de couleur ni de
   graisse : la marque dit déjà ce qui est retenu, et teinter le libellé
   par-dessus le fait passer pour un lien au milieu d'une liste qui n'en
   contient pas. */
.sc-select__option[aria-selected="true"]::after {
  color: var(--color-accent-text);
  transform: scale(1);
}

/* ---- États d'une option ----
   L'option survolée à la souris et l'option parcourue au clavier
   portent exactement le même rendu, parce que ce sont le même index :
   le module déplace aria-activedescendant au passage du pointeur.
   Deux rendus distincts obligeraient à décider lequel gagne quand la
   souris traverse la liste pendant qu'on navigue aux flèches, et toutes
   les réponses à cette question sont mauvaises.
   Le clavier ne déplace pas le focus, il déplace
   aria-activedescendant, d'où l'attribut plutôt qu'une
   pseudo-classe. */

.sc-select__option:hover:not([aria-disabled="true"]),
.sc-select__option[data-active="true"],
.sc-select__option[data-state="hover"] {
  --_option-bg: var(--color-bg-hover);
}

.sc-select__option:active:not([aria-disabled="true"]) {
  --_option-bg: var(--color-bg-active);
}

/* L'option parcourue au clavier porte en plus un liseré : sans lui,
   rien ne distingue « la souris passe ici » de « c'est ici que
   Entrée va valider ». */

.sc-select__option[aria-disabled="true"] {
  opacity: var(--opacity-disabled);
  cursor: not-allowed;
}

/* ---- Liste vide ----
   Un panneau qui n'a rien à montrer le dit, plutôt que de s'ouvrir
   sur du vide. */

.sc-select__empty {
  padding: var(--padding-s);
  color: var(--color-text-muted);
  font-size: var(--text-label-size);
  text-align: center;
}

/* ---- Tailles ----
   Les mêmes hauteurs que le natif, les boutons et les champs. */

.sc-select--s .sc-select__trigger {
  --_select-height: var(--control-height-s);
  --_select-padding-x: var(--field-padding-s);
  --_select-font-size: var(--text-body-s-size);
}

.sc-select--l .sc-select__trigger {
  --_select-height: var(--control-height-l);
  --_select-padding-x: var(--field-padding-l);
}

/* ---- Le store ----
   Le même geste que le menu, et c'est une réponse de Vibecoded plutôt
   qu'un choix : « exactement le store des menus, mêmes durées et mêmes
   délais par ligne ». Les images-clés vivent dans css/base.css, parce
   que le menu les emploie aussi et qu'aucun des deux composants ne doit
   dépendre de l'autre.
   base.css annule ces animations sous prefers-reduced-motion. */

.sc-select__panel {
  --_store-lift: 3px;
  --_line-lift: 4px;
  --_line-base: 40ms;
  --_line-step: 14ms;
  --_fold-step: 9ms;
}

.sc-select__panel.is-unfolding {
  animation: sc-store-unfold var(--motion-enter) both;
}

/* Le panneau est encore dans le document pendant sa fermeture et
   intercepterait le clic suivant. */
.sc-select__panel.is-folding {
  animation: sc-store-fold var(--motion-leave) both;
  pointer-events: none;
}

.sc-select__panel.is-unfolding .sc-select__option {
  animation: sc-store-line var(--motion-base) both;
  animation-delay: calc(var(--_line-base) + 5 * var(--_line-step));
}

.sc-select__panel.is-unfolding .sc-select__option:nth-child(1) { animation-delay: var(--_line-base); }
.sc-select__panel.is-unfolding .sc-select__option:nth-child(2) { animation-delay: calc(var(--_line-base) + 1 * var(--_line-step)); }
.sc-select__panel.is-unfolding .sc-select__option:nth-child(3) { animation-delay: calc(var(--_line-base) + 2 * var(--_line-step)); }
.sc-select__panel.is-unfolding .sc-select__option:nth-child(4) { animation-delay: calc(var(--_line-base) + 3 * var(--_line-step)); }
.sc-select__panel.is-unfolding .sc-select__option:nth-child(5) { animation-delay: calc(var(--_line-base) + 4 * var(--_line-step)); }

/* À la sortie, les lignes du bas partent les premières. */
.sc-select__panel.is-folding .sc-select__option {
  animation: sc-store-line var(--motion-leave) reverse both;
  animation-delay: calc(4 * var(--_fold-step));
}

.sc-select__panel.is-folding .sc-select__option:nth-last-child(1) { animation-delay: calc(0 * var(--_fold-step)); }
.sc-select__panel.is-folding .sc-select__option:nth-last-child(2) { animation-delay: var(--_fold-step); }
.sc-select__panel.is-folding .sc-select__option:nth-last-child(3) { animation-delay: calc(2 * var(--_fold-step)); }
.sc-select__panel.is-folding .sc-select__option:nth-last-child(4) { animation-delay: calc(3 * var(--_fold-step)); }
