/* --------------------------------------------------------------------------
   Composants : boutons, champs, cartes, dialogues, selecteur de quantite,
   messages. Remplace les 153 classes Bootstrap utilisees par l'ancien front.
   -------------------------------------------------------------------------- */

/* --- Boutons ------------------------------------------------------------ */

.bouton {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--espace-xs);
    min-height: var(--cible);
    padding: 0 var(--espace-s);
    border: 1px solid var(--bordure);
    border-radius: var(--rayon);
    background: var(--fond);
    color: var(--texte);
    font: inherit;
    text-decoration: none;
    cursor: pointer;
}

.bouton:hover:not(:disabled) {
    background: var(--fond-doux);
}

.bouton:disabled {
    opacity: 0.5;
    cursor: default;
}

.bouton-primaire {
    border-color: var(--marque);
    background: var(--marque);
    color: var(--marque-contraste);
}

.bouton-primaire:hover:not(:disabled) {
    background: color-mix(in srgb, var(--marque) 85%, black);
}

.bouton-discret {
    border-color: transparent;
    background: transparent;
}

.bouton-rond {
    /* Cible tactile pleine, meme si le glyphe est petit. */
    width: var(--cible);
    min-width: var(--cible);
    padding: 0;
    border-radius: 50%;
    font-size: 1.25rem;
    line-height: 1;
}

/* --- Champs ------------------------------------------------------------- */

.champ {
    display: block;
    margin-bottom: var(--espace-s);
}

.champ > span {
    display: block;
    margin-bottom: calc(var(--pas) * 1);
    font-size: 0.9rem;
    color: var(--texte-discret);
}

.champ input,
.stepper-valeur {
    width: 100%;
    min-height: var(--cible);
    padding: 0 var(--espace-xs);
    border: 1px solid var(--bordure);
    border-radius: var(--rayon);
    background: var(--fond);
    color: inherit;
    font: inherit;
    /* Jamais en dessous de 1rem : voir base.css. */
    font-size: 1rem;
}

.champ input:user-invalid {
    border-color: var(--echec);
}

.erreur {
    margin-bottom: var(--espace-xs);
    color: var(--echec);
    font-size: 0.9rem;
}

/* --- Carte produit ------------------------------------------------------ */

.produit {
    display: flex;
    flex-direction: column;
    gap: var(--espace-xs);
    padding: var(--espace-s);
    border: 1px solid var(--bordure);
    border-radius: var(--rayon);
    background: var(--fond);
    box-shadow: var(--ombre);
}

.produit-image {
    width: 100%;
    /* Carre, et l'image entiere dedans : l'ancien front la bornait dans une
       boite de 300 sur 300 sans la rogner. Un bocal photographie en hauteur
       perdait son etiquette avec object-fit: cover. */
    aspect-ratio: 1 / 1;
    object-fit: contain;
    border-radius: calc(var(--rayon) - 2px);
    background: var(--fond-doux);
}

.produit-infos {
    flex: 1;
}

.produit-nom {
    font-size: 1rem;
}

.produit-detail {
    color: var(--texte-discret);
    font-size: 0.85rem;
}

.produit-description {
    color: var(--texte-discret);
    font-size: 0.85rem;
    /* Les retours a la ligne des descriptions sont rendus par le CSS : le front
       actuel les convertissait en <br/> par remplacement de chaine, ce qui
       imposait de construire du HTML. */
    white-space: pre-line;
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.produit-achat {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--espace-xs);
}

.produit-prix {
    font-weight: 600;
}

/* Le prix du lot sous le prix unitaire : plus discret, sur sa propre ligne,
   parce qu'il complete le prix sans le remplacer. */
.produit-prix-lot {
    display: block;
    font-weight: normal;
    font-size: 0.8rem;
}

.produit-prix-lot:empty {
    display: none;
}

.recap-lot {
    display: block;
    font-weight: normal;
    font-size: 0.85rem;
}

/* --- Selecteur de quantite ---------------------------------------------- */

mp-stepper {
    display: inline-flex;
    align-items: center;
    gap: calc(var(--pas) * 1);
}

.stepper-valeur {
    width: 3.5rem;
    text-align: center;
    /* Les fleches natives sont trop petites pour une cible tactile. */
    appearance: textfield;
}

.stepper-valeur::-webkit-outer-spin-button,
.stepper-valeur::-webkit-inner-spin-button {
    appearance: none;
    margin: 0;
}

mp-stepper.stepper-actif .stepper-valeur {
    border-color: var(--marque);
    font-weight: 600;
}

/* --- Dialogues ---------------------------------------------------------- */

.dialogue {
    width: min(28rem, calc(100vw - 2 * var(--espace-m)));
    padding: 0;
    border: none;
    border-radius: var(--rayon);
    box-shadow: 0 10px 40px rgb(0 0 0 / 25%);
}

.dialogue::backdrop {
    background: rgb(0 0 0 / 40%);
}

.dialogue-corps {
    padding: var(--espace-m);
}

/* La version en ligne, pas la verticale : en dessous de cent cinquante pixels
   de large la signature de celle-ci n'est plus qu'une trame grise. */
.dialogue-marque {
    display: block;
    width: min(300px, 100%);
    height: auto;
    margin: 0 auto var(--espace-m);
}

.dialogue-actions {
    display: flex;
    justify-content: flex-end;
    gap: var(--espace-xs);
    margin-top: var(--espace-s);
}

/* « Voir le catalogue » ne partage pas le bord droit avec « Se connecter » :
   ce n'est pas une variante de l'action, c'est l'autre sortie. Il tient donc
   le bord gauche, en face d'elle, et l'espace entre les deux dit qu'on ne
   choisit pas entre deux nuances de la meme chose. */
.dialogue-actions .bouton-visiteur {
    margin-right: auto;
}

/* --- Messages ephemeres ------------------------------------------------- */

/* Les messages ephemeres se posent au-dessus de la barre de rappel quand elle
   est la : sinon le dernier message recouvre le bouton qui l'a declenche. */
body:has(.rappel:not([hidden])) .alertes {
    bottom: calc(var(--cible) + var(--espace-m) + var(--espace-s));
}

.alertes {
    position: fixed;
    left: 50%;
    bottom: var(--espace-m);
    z-index: 20;
    display: flex;
    flex-direction: column;
    gap: var(--espace-xs);
    width: min(30rem, calc(100vw - 2 * var(--espace-m)));
    transform: translateX(-50%);
    pointer-events: none;
}

.alerte {
    margin: 0;
    padding: var(--espace-xs) var(--espace-s);
    border-radius: var(--rayon);
    background: var(--texte);
    color: var(--fond);
    box-shadow: var(--ombre);
    opacity: 1;
    transition: opacity 0.4s ease;
}

.alerte-echec {
    background: var(--echec);
}

.alerte.disparait {
    opacity: 0;
}

@media (prefers-reduced-motion: reduce) {
    .alerte {
        transition: none;
    }
}

/* --- Rappel de commande -------------------------------------------------- */
/*
   Une barre au bas de la fenetre, a toutes les largeurs : ce qui est au panier
   ne doit jamais quitter l'ecran, et une commande oubliee au panier n'est pas
   commandee.

   Un rail lateral avait ete essaye : il montrait le detail des lignes, mais il
   prend une colonne entiere du catalogue en permanence pour une information
   qu'on ne consulte que par intermittence. La barre coute soixante pixels de
   hauteur, et rien de plus.
*/

.rappel {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 9;
    display: flex;
    align-items: center;
    gap: var(--espace-s);
    padding: var(--espace-xs) var(--espace-m);
    background: var(--fond);
    border-top: 1px solid var(--bordure);
    color: var(--texte);
    box-shadow: 0 -8px 24px -18px rgb(22 32 26 / 45%);

    --rappel-teinte: var(--alerte);
}

.rappel[data-etat="validee"] { --rappel-teinte: var(--succes); }
.rappel[data-etat="vide"] { --rappel-teinte: var(--texte-discret); }

.rappel-etat {
    display: flex;
    align-items: center;
    gap: var(--espace-xs);
    margin: 0;
    color: var(--rappel-teinte);
    font-weight: 700;
    font-size: 0.85rem;
}

.rappel-etat:empty { display: none; }

/* Rien ne se replie dans une barre : un libelle sur trois lignes la ferait
   grandir, et elle mange deja le bas de la page. */
.rappel-etat,
.rappel-compte,
.rappel-somme {
    white-space: nowrap;
}

.rappel-etat::before {
    content: "";
    width: 9px;
    height: 9px;
    border-radius: 999px;
    background: currentColor;
    flex: none;
}

.rappel-vide {
    flex: 1;
    margin: 0;
    color: var(--texte-discret);
    font-size: 0.9rem;
}

.rappel-pied {
    display: flex;
    align-items: center;
    gap: var(--espace-s);
    margin-left: auto;
}

/* Le nombre d'articles : dans une barre, c'est ce qui reste du detail des
   lignes. Il dit qu'on n'a pas oublie la moitie de sa commande. */
.rappel-compte {
    color: var(--texte-discret);
    font-size: 0.85rem;
    font-variant-numeric: tabular-nums;
}

.rappel-compte:empty { display: none; }

.rappel-total {
    display: flex;
    align-items: baseline;
    gap: var(--espace-xs);
    margin: 0;
    font-family: var(--police-titre);
    font-size: 1.35rem;
    letter-spacing: 0.01em;
}

/* Le mot « Total » ne sert a rien a cote d'un montant en gros, dans une barre
   qui ne contient que ca. Il reste pour les lecteurs d'ecran. */
.rappel-total > span:first-child {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

.rappel-somme { font-variant-numeric: tabular-nums; }

/* Panier vide : ni compte, ni total, ni boutons. Un « 0,00 € » n'apprend rien,
   et le rappel se contente alors de dire qu'il n'y a rien. */
.rappel[data-etat="vide"] .rappel-total { display: none; }

.rappel-voir,
.rappel-valider,
.rappel-aide { white-space: nowrap; }

/* Le bouton de validation s'agite au meme instant que l'icone du panier dans
   l'entete. Plus court et plus discret qu'elle : l'icone est petite et loin,
   ce bouton est large et deja sous les yeux -- lui donner la meme amplitude
   ferait sauter toute la barre.

   Trois pixels et un degre : assez pour que l'oeil l'attrape, trop peu pour
   qu'on ait a le suivre. */
@keyframes valider-secousse {
    0%, 100% { transform: translateX(0) rotate(0); }
    20%      { transform: translateX(-3px) rotate(-1.2deg); }
    45%      { transform: translateX(3px) rotate(1.2deg); }
    70%      { transform: translateX(-2px) rotate(-0.8deg); }
}

.valider-secousse {
    animation: valider-secousse 0.6s ease-out;
}

/* Le nombre de taches sur le bouton d'aide : la pastille de l'entete a la meme
   forme, et dit la meme chose -- il reste quelque chose a faire. */
.rappel-aide:not([data-libres="0"]) .pastille {
    background: var(--nuit);
    color: var(--orange);
}

/* La barre couvre le bas de la page : sans cette reserve, le dernier produit du
   catalogue reste inatteignable. */
.catalogue:has(.rappel:not([hidden])) {
    padding-bottom: calc(var(--cible) + var(--espace-l));
}

/* Sur un telephone, il ne reste que l'essentiel : l'etat, le montant et le
   geste. Le decompte et le raccourci vers le recapitulatif sautent -- ce dernier
   est deja dans l'entete, sur la pastille du panier. */
@media (max-width: 30rem) {
    .rappel {
        padding: var(--espace-xs) var(--espace-s);
        gap: var(--espace-xs);
    }

    .rappel-compte,
    .rappel-voir {
        display: none;
    }

    /* Deux boutons sur une barre de telephone : celui de l'aide se reduit a
       son pictogramme. Le libelle reste dans aria-label. */
    .rappel-aide {
        width: var(--cible);
        min-width: var(--cible);
        padding: 0;
        justify-content: center;
    }

    .rappel-aide-mot { display: none; }

    /* La pastille se pose sur le pictogramme faute de place a cote. */
    .rappel-aide .pastille {
        position: absolute;
        transform: translate(14px, -12px);
        font-size: 0.7rem;
        min-width: 1.3em;
    }

    .rappel-aide { position: relative; }

    .rappel-pied { gap: var(--espace-xs); }
    .rappel-total { font-size: 1.2rem; }
    .rappel-valider { padding: 0 var(--espace-s); }
}

/* Sur grand ecran, la barre se regroupe au centre plutot que de coller aux deux
   bords : le regard n'a pas a traverser tout l'ecran entre l'etat et le bouton. */
@media (min-width: 62rem) {
    .rappel {
        justify-content: center;
    }

    .rappel-vide { flex: none; }

    .rappel-pied {
        margin-left: var(--espace-l);
    }
}

/* --- Recapitulatif de commande ------------------------------------------ */

.panier {
    max-width: 50rem;
    margin: 0 auto;
    padding: var(--espace-m);
}

.panier-entete {
    margin-bottom: var(--espace-m);
}

.tableau {
    width: 100%;
    border-collapse: collapse;
    background: var(--fond);
    border-radius: var(--rayon);
    overflow: hidden;
    box-shadow: var(--ombre);
}

.tableau th,
.tableau td {
    padding: var(--espace-xs) var(--espace-s);
    border-bottom: 1px solid var(--bordure);
    text-align: left;
    font-weight: normal;
}

.tableau thead th {
    color: var(--texte-discret);
    font-size: 0.85rem;
    font-weight: 600;
}

.tableau .nombre {
    text-align: right;
    white-space: nowrap;
}

.tableau tfoot th,
.tableau tfoot td {
    border-bottom: none;
    font-weight: 600;
}

.recap-nom {
    font-weight: 500;
}

/* Sur mobile, la colonne du prix unitaire est la moins utile. */
@media (max-width: 30rem) {
    .recap th:nth-child(2),
    .recap td:nth-child(2) {
        display: none;
    }

    .tableau th,
    .tableau td {
        padding: var(--espace-xs);
    }
}

.etat-commande {
    margin: var(--espace-s) 0;
    color: var(--texte-discret);
}

.etat-commande.etat-valide {
    color: var(--succes);
    font-weight: 600;
}

.panier-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--espace-xs);
    justify-content: flex-end;
}

/* Pleine largeur sur telephone : des boutons de largeurs differentes alignes a
   droite se lisent mal, et la cible est plus facile a atteindre. */
@media (max-width: 30rem) {
    .panier-actions .bouton {
        flex: 1 1 100%;
    }
}

/* --- Carte produit : la zone cliquable ---------------------------------- */

/* Le lien est le conteneur de l'image et du texte, plutot qu'un display:
   contents : celui-ci a longtemps sorti les liens de l'arbre d'accessibilite. */
.produit-lien {
    display: flex;
    flex-direction: column;
    gap: var(--espace-xs);
    flex: 1;
    color: inherit;
    text-decoration: none;
}

.produit:hover .produit-nom {
    text-decoration: underline;
}

/* --- Fiches produit et producteur --------------------------------------- */

.fiche {
    max-width: 50rem;
    margin: 0 auto;
    padding: var(--espace-m);
}

.fiche-retour {
    margin-bottom: var(--espace-s);
}

.fiche-titre {
    margin-bottom: 0;
}

.fiche-titre-producteur {
    text-transform: uppercase;
}

.fiche-categorie {
    margin-bottom: var(--espace-m);
}

.fiche-corps {
    display: grid;
    grid-template-columns: minmax(12rem, 18rem) 1fr;
    gap: var(--espace-m);
    align-items: start;
}

@media (max-width: 40rem) {
    .fiche-corps {
        grid-template-columns: 1fr;
    }
}

.fiche-visuel {
    position: relative;
}

.fiche-image {
    width: 100%;
    border-radius: var(--rayon);
    background: var(--fond-doux);
}

.fiche-bio {
    position: absolute;
    top: var(--espace-xs);
    left: var(--espace-xs);
}

.fiche-description {
    /* Les retours a la ligne sont rendus par le CSS, pas par des <br> injectes. */
    white-space: pre-line;
}

.fiche-producteur {
    margin: var(--espace-s) 0;
}

.fiche-prix {
    margin-bottom: 0;
    font-size: 1.5rem;
    font-weight: 600;
}

.fiche-prix .discret {
    font-size: 1rem;
    font-weight: normal;
}

.fiche-achat {
    display: flex;
    align-items: center;
    gap: var(--espace-xs);
    margin-top: var(--espace-s);
}

.fiche-contact {
    display: flex;
    flex-direction: column;
    gap: calc(var(--pas) * 1);
    margin-top: var(--espace-s);
    padding-left: var(--espace-s);
    border-left: 2px solid var(--bordure);
    font-style: normal;
}

.fiche-sous-titre {
    margin-top: var(--espace-l);
}

.liste-liens {
    margin: 0;
    padding-left: var(--espace-m);
}

.liste-liens li {
    margin-bottom: calc(var(--pas) * 1);
}

/* --- Retour visuel du panier -------------------------------------------- */

/* Un ajout doit se voir : la pastille est dans l'entete, la carte cliquee peut
   etre tout en bas de page. */
@keyframes pastille-bouge {
    0%   { transform: scale(1); }
    35%  { transform: scale(1.45); }
    100% { transform: scale(1); }
}

/* Confirmation discrete que la commande est bien partie au serveur. */
@keyframes pastille-enregistree {
    0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--marque) 60%, transparent); }
    100% { box-shadow: 0 0 0 0.6rem transparent; }
}

.pastille {
    position: relative;
    transition: opacity 0.2s ease;
}

.pastille-bouge {
    animation: pastille-bouge 0.35s ease;
}

/* Le halo de confirmation est porte par un pseudo-element : deux classes qui
   declarent toutes deux la propriete raccourcie animation ne peuvent pas
   coexister, la derniere declaree gagnant. Or les deux se chevauchent. */
.pastille::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
    pointer-events: none;
}

.pastille-enregistree::after {
    animation: pastille-enregistree 0.6s ease-out;
}

/* Pendant l'envoi : la pastille s'attenue, sans bloquer l'interface. */
.pastille-en-cours {
    opacity: 0.55;
}

/* --- Etats du panier ----------------------------------------------------- */

/* Une commande a valider n'est pas une commande : le serveur ne la retient pas
   dans les bons de distribution. La couleur d'alerte le dit en permanence, le
   rappel periodique le redit a qui a quitte la page des yeux. */
.pastille[data-etat="a-valider"] {
    background: var(--alerte);
    color: var(--alerte-contraste);
}

.pastille[data-etat="validee"] {
    background: var(--succes);
    color: var(--marque-contraste);
}

/* La coche marque l'etat sans effacer le nombre d'articles. */
.pastille[data-etat="validee"]::before {
    content: "✓";
    margin-right: 0.15em;
    font-weight: 700;
}

/* Rappel : une onde qui part de la pastille, plus lente et plus ample que la
   confirmation d'enregistrement, pour ne pas se confondre avec elle. */
@keyframes pastille-rappel {
    0%   { transform: scale(1); box-shadow: 0 0 0 0 color-mix(in srgb, var(--alerte) 70%, transparent); }
    30%  { transform: scale(1.2); }
    60%  { transform: scale(1); }
    100% { transform: scale(1); box-shadow: 0 0 0 0.9rem transparent; }
}

.pastille-rappel {
    animation: pastille-rappel 1.1s ease-out;
}

/* L'icone s'agite avec la pastille, comme le bouton du panier de l'ancien
   front. Cinq pixels de part et d'autre : l'amplitude d'origine, assez pour
   attirer l'oeil sans faire sauter l'entete. */
@keyframes panier-secousse {
    0%, 100% { transform: translateX(0); }
    20%      { transform: translateX(-5px); }
    40%      { transform: translateX(5px); }
    60%      { transform: translateX(-5px); }
    80%      { transform: translateX(5px); }
}

.panier-secousse .icone {
    animation: panier-secousse 0.5s ease;
}

/* Le rappel penche le panier plutot que de le secouer : plus lent, plus
   ample, il se distingue de l'accuse de reception d'un ajout. */
@keyframes panier-penche {
    0%, 100% { transform: rotate(0); }
    15%      { transform: rotate(-9deg); }
    45%      { transform: rotate(7deg); }
    70%      { transform: rotate(-4deg); }
}

.panier-penche .icone {
    animation: panier-penche 1.1s ease-out;
    transform-origin: 50% 70%;
}

/* Un fond, et pas seulement un mouvement : le mouvement passe, le fond reste.
   Qui arrive sur la page apres coup doit voir qu'une action est attendue. */
[data-action="ouvrir-panier"][data-etat="a-valider"] {
    background: color-mix(in srgb, var(--alerte) 14%, transparent);
    border-color: color-mix(in srgb, var(--alerte) 45%, transparent);
    color: var(--alerte);
}

[data-action="ouvrir-panier"][data-etat="validee"] {
    background: color-mix(in srgb, var(--succes) 12%, transparent);
    border-color: color-mix(in srgb, var(--succes) 35%, transparent);
}

/* Au rappel, le fond s'affirme puis retombe : la couleur porte le message
   quand le regard est ailleurs, l'a-coup le ramene. */
@keyframes panier-fond {
    0%   { background-color: color-mix(in srgb, var(--alerte) 14%, transparent); }
    35%  { background-color: color-mix(in srgb, var(--alerte) 55%, transparent); }
    100% { background-color: color-mix(in srgb, var(--alerte) 14%, transparent); }
}

.panier-penche {
    animation: panier-fond 1.1s ease-out;
}

@media (prefers-reduced-motion: reduce) {
    .pastille-bouge,
    .pastille-rappel,
    .pastille-enregistree::after,
    .panier-secousse .icone,
    .panier-penche .icone,
    .panier-penche,
    .valider-secousse {
        animation: none;
    }

    .pastille {
        transition: none;
    }
}

/* --- Administration ------------------------------------------------------ */

.admin {
    max-width: 70rem;
    margin: 0 auto;
    padding: var(--espace-m);
}

.admin-entete {
    display: flex;
    flex-wrap: wrap;
    gap: var(--espace-s);
    align-items: center;
    justify-content: space-between;
    margin-bottom: var(--espace-m);
}

.admin-entete h1 {
    margin: 0;
}

.tableau .actions {
    display: flex;
    flex-wrap: wrap;
    gap: calc(var(--pas) * 1);
    justify-content: flex-end;
}

.tableau .actions .bouton {
    min-height: 2.25rem;
    padding: 0 var(--espace-xs);
    font-size: 0.9rem;
}

/* L'etat se lit d'un coup d'oeil dans une liste longue. */
.etiquette {
    display: inline-block;
    padding: 0.1em 0.6em;
    border-radius: 999px;
    background: var(--fond-doux);
    border: 1px solid var(--bordure);
    font-size: 0.85rem;
    white-space: nowrap;
}

.etiquette[data-statut="1"] {
    border-color: var(--marque);
    background: color-mix(in srgb, var(--marque) 12%, white);
    color: color-mix(in srgb, var(--marque) 80%, black);
    font-weight: 600;
}

.etiquette[data-statut="3"] {
    color: var(--texte-discret);
}

/* Sur telephone, le lieu et les horaires passent apres l'essentiel. */
@media (max-width: 45rem) {
    .admin .tableau th:nth-child(2),
    .admin .tableau td:nth-child(2),
    .admin .tableau th:nth-child(3),
    .admin .tableau td:nth-child(3) {
        display: none;
    }
}

.note {
    margin-top: var(--espace-s);
    font-size: 0.85rem;
}

/* --- Reglage a bascule ---------------------------------------------------- */

/* Un interrupteur plutot qu'une case a cocher : le reglage prend effet au clic,
   sans formulaire ni bouton d'enregistrement, et c'est ce qu'annonce la forme
   d'un interrupteur. La case native reste dessous, invisible mais bien la : elle
   porte le focus, le clavier et l'etat pour les technologies d'assistance. */
.reglage {
    margin-bottom: var(--espace-m);
    padding: var(--espace-s);
    border: 1px solid var(--bordure);
    border-radius: var(--rayon);
    background: var(--fond);
}

.interrupteur {
    display: flex;
    align-items: center;
    gap: var(--espace-xs);
    min-height: var(--cible);
    cursor: pointer;
}

/* Retiree du flux mais pas du DOM : display:none la sortirait de l'ordre de
   tabulation et lui oterait le focus. */
.interrupteur input {
    position: absolute;
    width: 1px;
    height: 1px;
    opacity: 0;
}

.interrupteur-piste {
    position: relative;
    flex: none;
    width: 2.75rem;
    height: 1.5rem;
    border: 1px solid var(--bordure);
    border-radius: 999px;
    background: var(--fond-doux);
    transition: background-color 0.15s ease-out;
}

.interrupteur-piste::after {
    content: "";
    position: absolute;
    top: 2px;
    left: 2px;
    width: 1.125rem;
    height: 1.125rem;
    border-radius: 50%;
    background: var(--texte-discret);
    transition: transform 0.15s ease-out, background-color 0.15s ease-out;
}

.interrupteur input:checked + .interrupteur-piste {
    background: var(--marque);
    border-color: var(--marque);
}

.interrupteur input:checked + .interrupteur-piste::after {
    transform: translateX(1.25rem);
    background: var(--marque-contraste);
}

.interrupteur input:focus-visible + .interrupteur-piste {
    outline: 2px solid var(--focus);
    outline-offset: 2px;
}

.interrupteur input:disabled + .interrupteur-piste {
    opacity: 0.5;
}

@media (prefers-reduced-motion: reduce) {
    .interrupteur-piste,
    .interrupteur-piste::after {
        transition: none;
    }
}

/* --- Le visiteur -------------------------------------------------------- */

/* L'orange de la charte : la couleur de ce qu'on propose, distincte du vert de
   l'action courante. Deux boutons de meme couleur dans un dialogue, ce sont deux
   appels de meme poids, et l'on ne sait plus lequel est le chemin normal.

   Le texte est en vert nuit et non en blanc : blanc sur cet orange ne donne que
   3,3:1, sous le seuil d'un texte courant, quand le vert nuit tient 5,2:1. Meme
   regle que le bouton « Aider ». */
.bouton-visiteur {
    border-color: var(--orange);
    background: var(--orange);
    color: var(--nuit);
    font-weight: 600;
}

.bouton-visiteur:hover:not(:disabled) {
    background: color-mix(in srgb, var(--orange) 88%, black);
    border-color: color-mix(in srgb, var(--orange) 88%, black);
}

/* Meme place que la barre de rappel d'un membre -- ce qui compte reste sous les
   yeux -- mais un composant a part : celle-la est branchee sur le panier, et un
   visiteur n'en a pas. */
.visiteur-barre {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 9;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-wrap: wrap;
    gap: var(--espace-s);
    padding: var(--espace-xs) var(--espace-m);
    background: var(--fond);
    border-top: 1px solid var(--bordure);
    box-shadow: 0 -2px 12px rgb(0 0 0 / 8%);
}

.visiteur-message {
    margin: 0;
}

/* Les messages ephemeres se posent au-dessus, comme ils le font au-dessus de la
   barre de rappel : sinon le dernier message recouvre le bouton. */
body:has(.visiteur-barre:not([hidden])) .alertes {
    bottom: calc(var(--cible) + var(--espace-m) + var(--espace-s));
}

@media (max-width: 30rem) {
    .visiteur-message {
        font-size: 0.9rem;
    }
}

/* --- Ecran des parametres ----------------------------------------------- */

/* Les reglages sont groupes par ce qu'ils concernent, et non alignes a la
   file : l'apercu parle des membres en attente, la vitrine de gens qui n'ont pas
   de compte du tout. Un titre par groupe suffit a le dire. */
.parametres-titre {
    margin: var(--espace-l) 0 var(--espace-xs);
    font-size: 1.1rem;
}

.parametres-titre:first-of-type {
    margin-top: var(--espace-m);
}

.parametres-intro {
    margin: 0 0 var(--espace-s);
    max-width: 46rem;
}

/* Un reglage que la vitrine fermee rend sans effet : eteint plutot que retire.
   Le retirer ferait sauter la mise en page a chaque bascule, et cacherait a
   l'administrateur ce qu'il obtiendra en ouvrant. */
.reglage-sans-effet {
    opacity: 0.55;
}

/* --- Fiches a corriger --------------------------------------------------- */

.fiches-a-corriger {
    margin: 0 0 var(--espace-m);
    padding: 0;
    list-style: none;
    display: grid;
    gap: var(--espace-xs);
}

.fiche-a-corriger {
    padding: var(--espace-s);
    border: 1px solid var(--bordure);
    border-radius: var(--rayon);
    background: var(--fond);
}

.fiche-a-corriger-entete {
    display: flex;
    align-items: center;
    gap: var(--espace-xs);
    flex-wrap: wrap;
}

/* Ce qu'on retirerait, mot pour mot : la seule facon de decider en connaissance
   de cause. En police a chasse fixe, parce que ce sont des adresses et des
   numeros, et qu'on veut les lire caractere par caractere. */
.fiche-a-corriger-trouve {
    margin: var(--espace-xs) 0;
    font-family: Consolas, "SF Mono", Menlo, monospace;
    font-size: 0.85rem;
    color: var(--texte-discret);
    overflow-wrap: anywhere;
}

.fiche-a-corriger-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--espace-xs);
}

.fiche-a-corriger-actions .bouton {
    min-height: 2.25rem;
    padding: 0 var(--espace-xs);
    font-size: 0.9rem;
}

/* --- Les familles d'actions d'une distribution -------------------------- */

/* La rangee en aligne trois : ce qui prepare la distribution -- ses produits,
   ses taches --, les documents qu'on en sort -- bon de commande producteur, bon
   de distribution --, puis ce qui la modifie ou la fait avancer. Quatre boutons
   gris a la file se lisent comme une seule famille ; un ecart ouvre chacune.
   Quatre fois le pas ordinaire de la rangee : assez pour se voir, pas assez
   pour trouer la ligne. Les transitions n'en ont pas besoin, leur teinte les
   detache deja. */
.actions [data-debut-groupe] {
    margin-left: var(--espace-s);
}

/* --- Les transitions d'une distribution --------------------------------- */

/* Une teinte, pas un aplat. Ces quatre boutons voisinent avec six autres sur la
   meme ligne : un bouton plein en ferait l'action principale de l'ecran, alors
   qu'ils sont au meme rang que « Produits » ou « Modifier ».
   La couleur dit ou va la distribution -- vert quand elle prend les commandes,
   ambre quand elles s'arretent, rouge quand elle quitte la liste courante -- et
   non si l'action est risquee : toutes les quatre se defont.
   Meme recette que le rappel du panier : 14 % de fond, 45 % de bordure, et la
   couleur pleine pour le texte, qui est ce qui doit rester lisible. Les seuils
   sont mesures par parcours-admin.mjs, sur les styles calcules. */
.actions [data-action="open"],
.actions [data-action="reopen"] {
    background: color-mix(in srgb, var(--marque) 14%, transparent);
    border-color: color-mix(in srgb, var(--marque) 45%, transparent);
    color: var(--marque);
}

/* L'ambre de la charte tombe a 4,31 sur sa propre teinte -- mesure, pas estime.
   Le libelle est donc assombri, du meme geste que `.bouton-primaire:hover` :
   c'est la meme couleur, plus foncee, et elle repasse a 5,8. */
.actions [data-action="close"] {
    background: color-mix(in srgb, var(--alerte) 14%, transparent);
    border-color: color-mix(in srgb, var(--alerte) 45%, transparent);
    color: color-mix(in srgb, var(--alerte) 85%, black);
}

.actions [data-action="archive"] {
    background: color-mix(in srgb, var(--echec) 14%, transparent);
    border-color: color-mix(in srgb, var(--echec) 45%, transparent);
    color: var(--echec);
}

/* Le survol appuie la meme teinte. Sans ces regles, `.bouton:hover` -- plus
   specifique -- remettrait le fond neutre et la couleur disparaitrait au
   moment precis ou l'on vise le bouton. */
.actions [data-action="open"]:hover:not(:disabled),
.actions [data-action="reopen"]:hover:not(:disabled) {
    background: color-mix(in srgb, var(--marque) 24%, transparent);
}

.actions [data-action="close"]:hover:not(:disabled) {
    background: color-mix(in srgb, var(--alerte) 24%, transparent);
}

.actions [data-action="archive"]:hover:not(:disabled) {
    background: color-mix(in srgb, var(--echec) 24%, transparent);
}

/* --- Choix des produits d'une distribution ------------------------------ */

.admin-actions {
    display: flex;
    align-items: center;
    gap: var(--espace-s);
}

.groupes {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
    gap: var(--espace-s);
    align-items: start;
}

.groupe {
    margin: 0;
    padding: var(--espace-s);
    border: 1px solid var(--bordure);
    border-radius: var(--rayon);
    background: var(--fond);
}

.groupe-titre {
    padding: 0 var(--espace-xs);
    font-weight: 600;
}

/* Replie, le groupe n'est plus que son titre : le cadre gardait sous le nom une
   bande vide, qui donnait a chaque carte l'air d'un champ a moitie rendu. */
.groupe:has(> .choix-liste[hidden]) {
    padding-top: 0;
    padding-bottom: 0;
}

/* La case du producteur est un .choix, donc une boite flex de niveau bloc : le
   bouton passait a la ligne, et chaque groupe replie gardait une bande vide. */
.groupe-titre .choix {
    display: inline-flex;
    vertical-align: middle;
}

/* Trente-neuf producteurs et quatre cents produits : les listes sont repliees,
   et c'est le compte du groupe qui dit ou sont les produits retenus. */
.groupe-bascule {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    vertical-align: middle;
    margin-left: var(--espace-xs);
    padding: 0;
    /* La cible tactile reste entiere : le chevron ne fait que vingt pixels. */
    min-width: var(--cible);
    min-height: var(--cible);
    border: 1px solid transparent;
    border-radius: var(--rayon);
    background: none;
    /* L'orange de la charte : la couleur de ce qui attend un geste. Il ne
       conviendrait pas a un paragraphe, il convient a un element graphique. */
    color: var(--orange);
    cursor: pointer;
}

.groupe-bascule:hover {
    border-color: var(--orange);
}

.groupe-bascule svg {
    width: 20px;
    height: 20px;
    fill: none;
    stroke: currentColor;
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
    transition: transform 0.15s ease;
}

/* Le chevron pointe vers le bas quand il reste a ouvrir, vers le haut quand il
   ne reste qu'a replier. */
.groupe-bascule[aria-expanded="true"] svg {
    transform: rotate(180deg);
}

@media (prefers-reduced-motion: reduce) {
    .groupe-bascule svg { transition: none; }
}

.choix-liste {
    margin: var(--espace-xs) 0 0;
    padding: 0;
    list-style: none;
}

/* La ligne entiere est cliquable : une case a cocher seule est une cible
   minuscule sur telephone. */
.choix {
    display: flex;
    align-items: center;
    gap: var(--espace-xs);
    min-height: 2.25rem;
    padding: 0 var(--espace-xs);
    border-radius: calc(var(--rayon) - 2px);
    cursor: pointer;
}

.choix:hover {
    background: var(--fond-doux);
}

.choix input {
    width: 1.1rem;
    height: 1.1rem;
    flex: none;
    accent-color: var(--marque);
}

.choix-nom {
    flex: 1;
}

.choix-masque .choix-nom {
    color: var(--texte-discret);
    font-style: italic;
}

/* --- Administration : navigation et membres ----------------------------- */

.admin-nav {
    display: flex;
    gap: var(--espace-m);
    margin-bottom: var(--espace-m);
    border-bottom: 1px solid var(--bordure);
}

.admin-nav a {
    padding: var(--espace-xs) 0;
    color: var(--texte-discret);
    text-decoration: none;
    border-bottom: 2px solid transparent;
}

.admin-nav a:hover {
    color: var(--texte);
    border-bottom-color: var(--bordure);
}

/* --- Barre d'action des ecrans d'administration -------------------------- */
/*
   Meme parti que le rappel de commande du catalogue : ce qui compte reste au
   bas de la fenetre. Sur le choix des produits d'une distribution, le compte
   des produits retenus et l'enregistrement etaient dans l'entete, hors de vue
   des la premiere avancee dans quatre cents cases.
*/
.barre-action {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 9;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--espace-m);
    padding: var(--espace-xs) var(--espace-m);
    background: var(--fond);
    border-top: 1px solid var(--bordure);
    box-shadow: 0 -8px 24px -18px rgb(22 32 26 / 45%);
}

/* Rien ne se replie dans une barre : un libelle sur deux lignes la ferait
   grandir, et elle mange deja le bas de la page. */
.barre-action .discret {
    white-space: nowrap;
}

/* La barre couvre le bas de la page : sans cette reserve, le dernier groupe
   reste inatteignable. */
.admin-avec-barre {
    padding-bottom: calc(var(--cible) + var(--espace-l));
}

/* Les archives tiennent deux listes sans rapport l'une avec l'autre. Les
   onglets reprennent la barre d'administration : meme repere, meme geste. */
.onglets {
    display: flex;
    gap: var(--espace-m);
    margin-bottom: var(--espace-m);
    border-bottom: 1px solid var(--bordure);
}

.onglet {
    min-height: var(--cible);
    padding: var(--espace-xs) 0;
    border: 0;
    border-bottom: 2px solid transparent;
    background: none;
    color: var(--texte-discret);
    font: inherit;
    cursor: pointer;
}

.onglet:hover {
    color: var(--texte);
    border-bottom-color: var(--bordure);
}

.onglet[aria-selected="true"] {
    color: var(--texte);
    border-bottom-color: var(--marque);
}

.pagination {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--espace-s);
    margin-top: var(--espace-s);
}

/* La date de la demande suit l'etiquette sans la bousculer : sur telephone elle
   passe a la ligne plutot que d'etirer la colonne. */
.membre-demande {
    display: inline-block;
    margin-left: var(--espace-xs);
    font-size: 0.85rem;
}

.champ-recherche {
    margin-bottom: 0;
    min-width: 16rem;
}

.bouton-danger {
    border-color: var(--echec);
    background: var(--echec);
    color: var(--fond);
}

.bouton-danger:hover:not(:disabled) {
    background: color-mix(in srgb, var(--echec) 85%, black);
}

.etiquette[data-membre="0"] {
    border-color: var(--echec);
    background: color-mix(in srgb, var(--echec) 10%, white);
    color: color-mix(in srgb, var(--echec) 85%, black);
    font-weight: 600;
}

/* Sur telephone, l'adresse et le telephone passent apres l'essentiel. */
@media (max-width: 45rem) {
    .admin .tableau th:nth-child(3),
    .admin .tableau td:nth-child(3) {
        display: none;
    }
}

/* --- Taches benevoles ---------------------------------------------------- */

.taches {
    margin: 0;
    padding: 0;
    list-style: none;
    background: var(--fond);
    border: 1px solid var(--bordure);
    border-radius: var(--rayon);
    box-shadow: var(--ombre);
}

.tache {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--espace-xs);
    padding: var(--espace-xs) var(--espace-s);
    border-bottom: 1px solid var(--bordure);
}

.tache:last-child {
    border-bottom: none;
}

.tache-libelle,
.tache-champ {
    flex: 1;
    min-width: 12rem;
}

.tache-champ {
    min-height: var(--cible);
    padding: 0 var(--espace-xs);
    border: 1px solid var(--bordure);
    border-radius: var(--rayon);
    font: inherit;
    font-size: 1rem;
}

.tache-mienne {
    background: color-mix(in srgb, var(--marque) 8%, white);
}

.ajout-tache {
    display: flex;
    align-items: flex-end;
    gap: var(--espace-xs);
    margin-top: var(--espace-s);
}

/* Le champ porte la liste de completion : il lui faut un contenant positionne,
   sinon la liste pousserait le bouton « Ajouter » vers le bas a chaque frappe. */
.ajout-champ {
    position: relative;
    flex: 1;
    min-width: 0;
}

.suggestions {
    position: absolute;
    z-index: 20;
    top: calc(100% + var(--pas));
    left: 0;
    right: 0;
    margin: 0;
    padding: 0;
    list-style: none;
    max-height: 16rem;
    overflow-y: auto;
    background: var(--fond);
    border: 1px solid var(--marque);
    border-radius: var(--rayon);
    box-shadow: var(--ombre);
}

.suggestion-choix {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--espace-s);
    width: 100%;
    min-height: var(--cible);
    padding: var(--espace-xs) var(--espace-s);
    border: 0;
    border-bottom: 1px solid var(--bordure);
    background: none;
    color: inherit;
    font: inherit;
    text-align: left;
    cursor: pointer;
}

.suggestion:last-child .suggestion-choix {
    border-bottom: 0;
}

.suggestion-choix:hover,
.suggestion[aria-selected="true"] .suggestion-choix {
    background: color-mix(in srgb, var(--marque) 10%, var(--fond));
}

/* --- Nuage des taches deja employees ------------------------------------- */

.nuage-taches {
    margin-top: var(--espace-m);
}

.nuage-titre {
    margin: 0 0 var(--espace-xs);
    color: var(--texte-discret);
    font-size: 0.9rem;
}

.nuage-pastilles {
    display: flex;
    flex-wrap: wrap;
    gap: var(--espace-xs);
}

/* Les libelles vont jusqu'a soixante-quatre caracteres : une pastille doit
   pouvoir se replier sur deux lignes plutot que deborder de l'ecran. */
.pastille-tache {
    max-width: 100%;
    padding-top: var(--espace-xs);
    padding-bottom: var(--espace-xs);
    text-align: left;
    white-space: normal;
}

.pastille-tache::before {
    content: "+";
    margin-right: 0.4em;
    font-weight: 700;
    color: var(--marque);
}

.pastille-tache:hover::before {
    color: inherit;
}

/* Sous 640 px, une pastille de trente-huit caracteres prend sa ligne entiere :
   les quinze feraient quinze rangees, soit un nuage plus haut que la liste des
   taches qu'il sert. On n'en montre plus que dix -- les autres restent
   atteignables par la completion, qui cherche dans la liste entiere. */
@media (max-width: 640px) {
    .pastille-tache:nth-child(n + 11) {
        display: none;
    }
}

/* --- Commandes passees --------------------------------------------------- */

.commandes-passees {
    margin-top: var(--espace-l);
    padding-top: var(--espace-m);
    border-top: 1px solid var(--bordure);
}

.commandes-passees h2 {
    margin: 0 0 var(--espace-s);
    font-size: 1.1rem;
}

.commandes-passees ul {
    list-style: none;
    margin: 0;
    padding: 0;
}

/* --- Filtres par categorie ----------------------------------------------- */

.filtres {
    margin-bottom: var(--espace-m);
}

.filtres-rang {
    display: flex;
    flex-wrap: wrap;
    gap: var(--espace-xs);
}

.filtres-sous {
    margin-top: var(--espace-xs);
    padding-top: var(--espace-xs);
    border-top: 1px dashed var(--bordure);
}

.filtre {
    min-height: var(--cible);
    padding: 0 var(--espace-s);
    border: 1px solid var(--bordure);
    border-radius: 999px;
    background: var(--fond);
    color: inherit;
    font: inherit;
    cursor: pointer;
}

.filtre:hover {
    border-color: var(--marque);
}

.filtre[aria-pressed="true"] {
    background: var(--marque);
    border-color: var(--marque);
    color: var(--marque-contraste);
}

.filtre-compte {
    opacity: 0.6;
    margin-left: 0.3em;
}

.filtre[aria-pressed="true"] .filtre-compte {
    opacity: 0.8;
}

/* Sur mobile, les filtres defilent horizontalement plutot que d'occuper
   plusieurs lignes et de repousser les produits hors de l'ecran. */
@media (max-width: 640px) {
    .filtres-rang {
        flex-wrap: nowrap;
        overflow-x: auto;
        padding-bottom: var(--espace-xs);
        scrollbar-width: thin;
    }

    .filtre {
        flex: 0 0 auto;
    }
}

/* Une liste longue dans un dialogue defile a l'interieur du dialogue, pas de la
   page : sans cela le dialogue depasse l'ecran et ses boutons deviennent
   inatteignables. */
.tableau-defilant {
    max-height: 50vh;
    overflow-y: auto;
    margin-bottom: var(--espace-s);
}

/* --- Administration du catalogue ----------------------------------------- */

.catalogue-producteur {
    margin-bottom: var(--espace-s);
    border: 1px solid var(--bordure);
    border-radius: var(--rayon);
    background: var(--fond);
}

.catalogue-producteur > summary {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--espace-s);
    min-height: var(--cible);
    padding: var(--espace-s) var(--espace-m);
    cursor: pointer;
}

.catalogue-producteur > summary:focus-visible {
    outline: 2px solid var(--marque);
    outline-offset: -2px;
}

.catalogue-producteur-nom {
    font-weight: 600;
}

.catalogue-producteur-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--espace-xs);
    padding: 0 var(--espace-m) var(--espace-s);
}

.catalogue-producteur .tableau {
    margin: 0;
}

/* L'archivage est mis a l'ecart des actions courantes : il n'a pas le meme
   poids que corriger une description, et se trouver sous le doigt qui vise le
   bouton voisin n'est pas la place d'un geste qui vide un catalogue. */
.catalogue-producteur-actions [data-action="archiver-producteur"] {
    margin-left: auto;
}

/* Un dialogue de formulaire est plus large : trois champs par ligne y tiennent
   sur un ecran de bureau, un seul sur telephone. */
.dialogue-large {
    width: min(44rem, calc(100vw - 2 * var(--espace-m)));
}

.champ-groupe {
    display: flex;
    flex-wrap: wrap;
    gap: var(--espace-s);
}

.champ-groupe > .champ {
    flex: 1 1 10rem;
}

.champ-groupe > .choix {
    flex: 0 0 auto;
}

.dialogue-corps textarea,
.dialogue-corps select {
    width: 100%;
    min-height: var(--cible);
    padding: var(--espace-xs);
    border: 1px solid var(--bordure);
    border-radius: var(--rayon);
    background: var(--fond);
    color: inherit;
    font: inherit;
    /* 1rem au minimum : en dessous, iOS zoome sur le champ actif. */
    font-size: 1rem;
}

.dialogue-corps textarea {
    resize: vertical;
}

.dialogue-corps input[type="file"] {
    font: inherit;
    font-size: 1rem;
}

/* --- Ecrans publics : inscription, mot de passe oublie -------------------- */

.formulaire-public {
    max-width: 28rem;
    margin: 0 auto;
}

.formulaire-public h1 {
    margin-bottom: var(--espace-xs);
}

.formulaire-public .discret {
    margin-bottom: var(--espace-m);
}

.dialogue-liens {
    display: flex;
    justify-content: space-between;
    gap: var(--espace-s);
    margin: var(--espace-s) 0 0;
    font-size: 0.9rem;
}

/* Les lignes rejetees d'un import : une liste courte, defilante si le fichier
   etait mauvais de bout en bout. */
.liste-rejets {
    max-height: 40vh;
    overflow-y: auto;
    margin: 0 0 var(--espace-s);
    padding-left: var(--espace-m);
    font-size: 0.9rem;
}

.dialogue-corps code {
    font-size: 0.85em;
    word-break: break-word;
}

/* --- Vignettes du catalogue d'administration ----------------------------- */

.vignette {
    width: 40px;
    height: 40px;
    flex: 0 0 auto;
    object-fit: cover;
    border-radius: var(--rayon);
    background: var(--fond-doux);
}

.produit-avec-vignette {
    display: flex;
    align-items: center;
    gap: var(--espace-s);
}

.catalogue-producteur > summary .vignette {
    width: 32px;
    height: 32px;
}

.catalogue-producteur-contact {
    margin: 0 var(--espace-m) var(--espace-s);
}

/* La description dans sa propre carte : elle vient d'ailleurs -- c'est le texte
   du producteur, pas une donnee de gestion -- et le cadre le montre sans avoir
   a l'ecrire. Fond doux plutot que blanc : la fiche est deja sur du blanc. */
.catalogue-producteur-description {
    margin: 0 var(--espace-m) var(--espace-s);
    padding: var(--espace-s) var(--espace-m);
    border: 1px solid var(--bordure);
    border-left: 3px solid var(--marque);
    border-radius: var(--rayon);
    background: var(--fond-doux);
}

.catalogue-producteur-description:empty {
    /* Sans texte, la carte se reduit a une ligne : elle reste visible, mais ne
       prend pas la place d'une description. */
    color: var(--texte-discret);
    font-style: italic;
}

.catalogue-producteur-description:empty::before {
    content: 'Aucune description.';
}

.catalogue-producteur-edition {
    margin: 0 var(--espace-m) var(--espace-s);
}

.catalogue-producteur-edition textarea {
    width: 100%;
    padding: var(--espace-xs);
    border: 1px solid var(--bordure);
    border-radius: var(--rayon);
    background: var(--fond);
    color: inherit;
    font: inherit;
    /* 1rem au minimum : en dessous, iOS zoome sur le champ actif. */
    font-size: 1rem;
    resize: vertical;
}

/* Les coordonnees du producteur : sur une ligne quand la place le permet, en
   colonne sur telephone, ou une cible tactile ne doit pas en toucher une autre. */
.catalogue-producteur-contact {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--espace-xs) var(--espace-s);
    font-style: normal;
}

.catalogue-producteur-contact a {
    min-height: var(--cible);
    display: inline-flex;
    align-items: center;
}

/* Une coordonnee dans un tableau : la cible tactile occupe la cellule, sans
   quoi il faut viser le texte lui-meme. */
.tableau td > a[href^="mailto:"],
.tableau td > a[href^="tel:"] {
    display: inline-flex;
    align-items: center;
    min-height: var(--cible);
}

/* --- Annonce de la distribution ------------------------------------------ */

/* L'information que l'adherent vient chercher : quand, ou, et jusqu'a quand
   commander. Elle etait reduite a une ligne grise sous un titre. */
.annonce {
    margin-bottom: var(--espace-m);
    padding: var(--espace-m);
    border: 1px solid var(--bordure);
    border-left: 4px solid var(--marque);
    border-radius: var(--rayon);
    background: var(--fond);
    box-shadow: var(--ombre);
}

.annonce p {
    margin: 0;
}

.annonce-distribution {
    font-size: 1.25rem;
    line-height: 1.4;
}

.annonce-distribution strong {
    color: var(--marque);
}

.annonce-commande {
    margin-top: var(--espace-xs) !important;
    color: var(--texte-discret);
}

@media (max-width: 640px) {
    .annonce-distribution {
        font-size: 1.1rem;
    }
}

/* --- Appel a l'aide, dans le bandeau ------------------------------------- */

.annonce-aide {
    margin-top: var(--espace-s) !important;
}

/* Le meme appel en tete de l'ecran de commande : il y a sa propre marge, le
   bloc d'annonce du catalogue ne l'entourant pas ici. */
.panier-entete .annonce-aide {
    margin-bottom: 0 !important;
}

/* L'appel a l'aide prend l'orange de la charte : « ce qui attend un geste ».
   Il se distingue ainsi du vert de la commande -- deux appels de couleurs
   differentes ne se disputent pas, ils se repartissent.

   Le texte y est en vert nuit et non en blanc : blanc sur cet orange ne donne
   que 3,3:1, sous le seuil d'un texte courant, tandis que le vert nuit tient
   5,2:1. C'est la couleur du texte qui cede, pas celle de la marque. */
.bouton-aide[data-libres]:not([data-libres="0"]) {
    background: var(--orange);
    border-color: var(--orange);
    color: var(--nuit);
}

.bouton-aide[data-libres]:not([data-libres="0"]):hover:not(:disabled) {
    background: color-mix(in srgb, var(--orange) 88%, black);
    border-color: color-mix(in srgb, var(--orange) 88%, black);
}

.bouton-aide {
    display: inline-flex;
    align-items: center;
    gap: var(--espace-xs);
}

.bouton-aide svg {
    width: 20px;
    height: 20px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.8;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* --- Plusieurs distributions ouvertes ------------------------------------
 *
 * Rien ne s'affiche tant qu'une seule est ouverte : le bandeau garde alors la
 * place qu'il a toujours eue. A partir de deux, une ligne discrete sous
 * l'annonce, et un rail qu'elle deplie -- deux rendez-vous a la fois, chevrons
 * au-dela. Les libelles vont jusqu'a quarante-six caracteres dans la base : ils
 * ont besoin de toute la largeur, d'ou la liste plutot que des onglets.
 */
.autres {
    margin-top: calc(var(--espace-m) * -1 + var(--espace-xs));
    margin-bottom: var(--espace-m);
}

.autres-ligne {
    display: flex;
    align-items: center;
    gap: var(--espace-xs);
    flex-wrap: wrap;
    padding: var(--espace-xs) var(--espace-s);
    border-radius: var(--rayon);
    font-size: 0.94rem;
    color: var(--texte-discret);
}

/* Une autre commande ouverte dont le panier est vide : c'est le seul cas ou
   l'oubli coute quelque chose, et le seul ou la ligne se fait remarquer. */
.autres-ligne[data-ton="alerte"] {
    background: color-mix(in srgb, var(--alerte) 12%, transparent);
    color: color-mix(in srgb, var(--alerte) 85%, black);
}

.autres-lien {
    font: inherit;
    font-weight: 700;
    background: none;
    border: none;
    padding: 0;
    margin-left: auto;
    color: var(--marque);
    text-decoration: underline;
    text-underline-offset: 3px;
    cursor: pointer;
    min-height: var(--cible);
}

.autres-rail {
    display: flex;
    align-items: center;
    gap: var(--espace-xs);
    margin-top: var(--espace-xs);
}

.autres-cartes {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--espace-xs);
    flex: 1;
    min-width: 0;
}

/* Sur un telephone, la rangee devient une colonne et les chevrons se rangent
   au-dessus et en dessous : deux libelles longs ne tiennent pas cote a cote. */
@media (max-width: 640px) {
    .autres-rail { flex-direction: column; align-items: stretch; }
    .autres-cartes { grid-template-columns: 1fr; }
    .autres-chevron { align-self: center; }
}

.autres-chevron {
    flex: none;
    width: var(--cible);
    height: var(--cible);
    display: grid;
    place-items: center;
    border: 1px solid var(--bordure);
    border-radius: 50%;
    background: var(--fond);
    color: var(--texte);
    cursor: pointer;
}

.autres-chevron svg {
    width: 18px;
    height: 18px;
    fill: none;
    stroke: currentColor;
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.autres-chevron[disabled] { opacity: 0.35; cursor: default; }

.choix-distribution {
    display: flex;
    align-items: flex-start;
    gap: var(--espace-xs);
    text-align: left;
    font: inherit;
    color: var(--texte);
    background: var(--fond);
    border: 1px solid var(--bordure);
    border-left: 4px solid var(--bordure);
    border-radius: var(--rayon);
    padding: var(--espace-s);
    cursor: pointer;
    min-width: 0;
}

.choix-distribution[data-courante="true"] {
    border-left-color: var(--marque);
    box-shadow: var(--ombre);
    cursor: default;
}

/* Le rond plein dit laquelle est montree sans reposer sur la seule couleur du
   liesere -- il reste lisible pour qui ne distingue pas le vert. */
.choix-marqueur {
    flex: none;
    width: 17px;
    height: 17px;
    margin-top: 3px;
    border: 2px solid var(--bordure);
    border-radius: 50%;
    display: grid;
    place-items: center;
}

.choix-distribution[data-courante="true"] .choix-marqueur { border-color: var(--marque); }

.choix-distribution[data-courante="true"] .choix-marqueur::after {
    content: "";
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: var(--marque);
}

.choix-corps {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

.choix-quand { font-weight: 700; line-height: 1.25; }
.choix-ou { font-size: 0.88rem; color: var(--texte-discret); }

.choix-etat {
    margin-top: var(--pas);
    align-self: flex-start;
    font-size: 0.78rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    font-weight: 700;
    padding: 2px 8px;
    border-radius: 999px;
    background: var(--fond-doux);
    color: var(--texte-discret);
}

.choix-distribution[data-courante="true"] .choix-etat {
    background: color-mix(in srgb, var(--marque) 13%, transparent);
    color: var(--marque);
}
