/*
 * composants.css — les briques d'interface communes a toute l'application.
 *
 * POURQUOI CE FICHIER EXISTE. Ces regles sont nees dans auth-vitrine.css, parce que l'ecran de
 * connexion a ete le premier refait. Ce sont pourtant des briques d'APPLICATION et non
 * d'authentification : un champ, un bouton, un message d'etat. Les laisser la obligeait le
 * tableau de bord a charger la mise en page d'un ecran de connexion pour obtenir un bouton.
 *
 * Il ne decide AUCUNE couleur : tout vient de tokens.css, qui doit etre charge avant lui.
 *
 * CE QU'IL CONTIENT, et ce qu'il ne contient pas :
 *   .champ, .champ-duo, .force    un champ de formulaire, sa paire, la force d'un mot de passe
 *   .bouton                       principal, secondaire, avec icone ou rouet d'attente
 *   .lien, .message               un lien textuel, une zone d'annonce
 * Tout ce qui porte un prefixe de page — .auth-*, .vitrine__*, .hub-* — reste dans la feuille
 * de sa page. C'est la ligne de partage, et elle se lit au nom de la classe.
 */

/* Visible par les lecteurs d'ecran, invisible a l'oeil.
 *
 * Pour une etiquette qu'un champ porte deja dans son aspect — la loupe d'une recherche dit ce
 * qu'on attend — mais qui doit exister pour qui ne voit pas la loupe. `display: none` et
 * `visibility: hidden` retireraient l'element de l'arbre d'accessibilite : c'est tout le
 * contraire de ce qu'on veut. */
.hors-ecran {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* Deux champs cote a cote : nom/prenom, mot de passe/confirmation. Une seule colonne sous
 * 420 px, ou deux champs de 180 px ne se remplissent plus au pouce. */
.champ-duo {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--espace-4);
}

@media (min-width: 420px) {
    .champ-duo { grid-template-columns: 1fr 1fr; }
}

/* --- Force du mot de passe ---------------------------------------------------------------
 * La barre et les criteres sont pilotes par checkPasswordStrength (shared-app.js), qui pose des
 * classes Tailwind : `bg-red-500` a `bg-green-500` sur la barre, `opacity-50` / `opacity-100` et
 * `text-green-600` sur chaque critere. Ces regles-ci ne fixent donc QUE la geometrie — donner
 * une couleur de fond a la barre ici la ferait lutter contre le JS. */
.force {
    display: flex;
    flex-direction: column;
    gap: var(--espace-3);
}

.force__piste {
    height: 4px;
    border-radius: var(--rayon-plein);
    background: var(--papier-enfonce);
    overflow: hidden;
}

.force__barre {
    height: 100%;
    width: 0;
    transition: width var(--duree-longue) var(--courbe);
}

.force__criteres {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--espace-1) var(--espace-3);
    margin: 0;
    padding: 0;
    list-style: none;
}

.force__critere {
    display: flex;
    align-items: center;
    gap: var(--espace-2);
    font-size: var(--taille-xs);
    color: var(--encre-attenuee);
}

.champ {
    display: flex;
    flex-direction: column;
    gap: var(--espace-2);
}

.champ__etiquette {
    font-size: var(--taille-sm);
    font-weight: var(--graisse-appuyee);
    color: var(--encre);
}

.champ__boite { position: relative; display: flex; }

.champ__saisie {
    width: 100%;
    padding: var(--espace-3) var(--espace-4);
    font-family: inherit;
    font-size: var(--taille-base);
    line-height: 1.4;
    color: var(--encre);
    background: var(--papier);
    border: 1px solid var(--trait);
    border-radius: var(--rayon-sm);
    transition: border-color var(--duree) var(--courbe),
                background-color var(--duree) var(--courbe);
}

/* Le bouton oeil se superpose : on reserve sa place plutot que de laisser le texte passer
 * dessous quand l'adresse est longue. */
.champ__saisie--avec-action { padding-right: var(--espace-12); }

.champ__saisie::placeholder { color: var(--encre-faible); }

.champ__saisie:hover:not(:disabled):not([aria-invalid="true"]) { border-color: var(--trait-fort); }

.champ__saisie:focus { outline: none; }

.champ__saisie:focus-visible {
    border-color: var(--accent);
    outline: var(--focus-epaisseur) solid var(--focus);
    outline-offset: 1px;
}

/* L'etat d'erreur se lit a la bordure ET au message : la couleur seule ne suffit pas, elle
 * n'existe pas pour qui ne la percoit pas. */
.champ__saisie[aria-invalid="true"] { border-color: var(--alerte); }

.champ__saisie:disabled {
    background: var(--papier-enfonce);
    color: var(--encre-faible);
    cursor: not-allowed;
}

/* --- Bouton afficher / masquer --------------------------------------------------------- */

.champ__action {
    position: absolute;
    top: 50%;
    right: var(--espace-2);
    transform: translateY(-50%);
    display: grid;
    place-items: center;
    width: 32px;
    height: 32px;
    padding: 0;
    border: 0;
    border-radius: var(--rayon-xs);
    background: transparent;
    color: var(--encre-attenuee);
    cursor: pointer;
    transition: color var(--duree) var(--courbe), background-color var(--duree) var(--courbe);
}

.champ__action:hover:not(:disabled) {
    color: var(--encre);
    background: var(--papier-enfonce);
}

.champ__action:disabled { cursor: not-allowed; opacity: .5; }

.champ__action svg { width: 17px; height: 17px; }

/* --- Message d'erreur du champ ---------------------------------------------------------
 * Il occupe une region aria-live : le lecteur d'ecran l'annonce sans deplacer le focus, donc
 * sans interrompre la saisie. Vide, il ne prend aucune place. */
.champ__erreur {
    margin: 0;
    color: var(--alerte);
    font-size: var(--taille-sm);
    line-height: 1.4;
}

.champ__erreur:empty { display: none; }

/* ==========================================================================================
 * 4. Boutons
 * ========================================================================================== */

.bouton {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--espace-2);
    width: 100%;
    padding: var(--espace-3) var(--espace-4);
    min-height: 44px; /* cible tactile confortable */
    font-family: inherit;
    font-size: var(--taille-base);
    font-weight: var(--graisse-appuyee);
    border: 1px solid transparent;
    border-radius: var(--rayon-sm);
    cursor: pointer;
    transition: background-color var(--duree) var(--courbe),
                border-color var(--duree) var(--courbe),
                color var(--duree) var(--courbe),
                transform var(--duree-courte) var(--courbe);
}

.bouton:disabled { cursor: not-allowed; }

.bouton--principal {
    background: var(--accent);
    color: var(--accent-contraste);
}

.bouton--principal:hover:not(:disabled) { background: var(--accent-fort); }
.bouton--principal:active:not(:disabled) { transform: translateY(1px); }

.bouton--principal:disabled {
    background: var(--papier-enfonce);
    color: var(--encre-faible);
}

/* Pendant l'envoi le bouton reste colore : le griser ferait croire a un refus. Il est
 * simplement inerte, et aria-busy le dit a qui n'en voit pas la couleur. */
.bouton--principal[aria-busy="true"]:disabled {
    background: var(--accent);
    color: var(--accent-contraste);
    opacity: .78;
}

.bouton--secondaire {
    background: var(--papier);
    color: var(--encre);
    border-color: var(--trait);
}

.bouton--secondaire:hover:not(:disabled) {
    border-color: var(--trait-fort);
    background: var(--papier-enfonce);
}

.bouton--secondaire:disabled {
    color: var(--encre-faible);
    background: var(--papier-enfonce);
    border-color: var(--trait);
}

.bouton__icone { width: 18px; height: 18px; flex: none; }

/* Le rond de chargement n'existe que pendant l'envoi. */
.bouton__rouet {
    width: 16px;
    height: 16px;
    flex: none;
    border: 2px solid currentColor;
    border-top-color: transparent;
    border-radius: var(--rayon-plein);
    animation: rouet 620ms linear infinite;
}

@keyframes rouet { to { transform: rotate(360deg); } }

.lien {
    color: var(--accent);
    font-size: var(--taille-sm);
    font-weight: var(--graisse-moyenne);
    text-decoration: none;
    background: none;
    border: 0;
    padding: 0;
    font-family: inherit;
    cursor: pointer;
}

.lien:hover { text-decoration: underline; }

/* Message global : erreur serveur, compte bloque, succes. */
.message {
    display: flex;
    align-items: flex-start;
    gap: var(--espace-2);
    padding: var(--espace-3) var(--espace-4);
    border-radius: var(--rayon-sm);
    font-size: var(--taille-sm);
    line-height: 1.45;
}

.message:empty { display: none; }

.message--alerte {
    background: var(--alerte-voile);
    color: var(--alerte);
    border: 1px solid color-mix(in srgb, var(--alerte) 28%, transparent);
}

.message--succes {
    background: var(--succes-voile);
    color: var(--succes);
    border: 1px solid color-mix(in srgb, var(--succes) 28%, transparent);
}

/* ==========================================================================================
 * Mouvement reduit
 * ==========================================================================================
 * Les transitions des composants s'arretent, comme celles des pages. Une transition de couleur
 * ne declenche pas de nausee, mais la regle est plus lisible si elle ne souffre pas d'exception :
 * « moins d'animations » veut dire moins d'animations.
 */
@media (prefers-reduced-motion: reduce) {
    .bouton__rouet { animation: none; }

    .bouton, .champ__saisie, .champ__action { transition: none; }
}
