/**
 * ui.css : kit UI commun de Colleta (admin, bilan, recover). count.css et
 * live.css ne la chargent pas. tokens.css doit etre charge AVANT, en <link>.
 * Nommage : prefixe .ui-, modificateurs en --, etats en is-.
 */

/* Pas de @import de police ici : ce serait une requete serielle. Les <link>
   sont poses par Chassis::head(), tableaux asset_polices et asset_tokens. */

/* ================================================================ *
 * 1. TOKENS
 * ================================================================ */

:root {
	/* ---- Couleurs surchargeables par tenant (orgs.branding_json) ----
	   La cle stockee reste « vert » (compat branding existant), mais le
	   jeton CSS qu'elle ecrit s'appelle --action. Le reste (--accent,
	   --fond, --carte, --trait, --texte...) vient de tokens.css, commun
	   aux trois pages publiques. */
	/* La seule facon d'ecrire dans la famille du rose, 9,78:1 sur blanc.
	   Non surchargeable par un tenant : tokensCss() ne le pose jamais et il ne
	   derive pas de --accent, car il dit un etat et non la marque. */
	--accent-encre: #5A2793;
	/* Le voile du rose, fond des etats qui portent --accent-encre : 8,33:1. */
	--accent-voile: #F5E8FF;
	--trait: #DCE8EA;
	/* --erreur DIVERGE de count.css (#C0264B) : « Supprimer » ne doit jamais
	   ressembler a « Enregistrer », donc jamais valoir --accent. Prune :
	   11,37:1 sur blanc, 9,38:1 sur le voile d'erreur. Reste propre a cette
	   feuille plutot qu'unifiee dans tokens.css. */
	--erreur: #6D0E56;

	/* ---- Couleurs derivees, calculees par colleta_ui_tokens_css() ---- */
	/* Repli servi quand tokensCss() n'emet rien, chez un tenant sans branding.
	   Une encre sombre, pas du blanc : sur le rose #EA80FC le blanc rend
	   2,34:1. C'est la valeur que Branding::sur() calcule, et
	   tests/kits-couleurs-test.php compare les deux. */
	--sur-accent: #0F2229;    /* texte pose sur --accent */
	--sur-action: #ffffff;      /* texte pose sur --action */

	/* ---- Couleurs fixes, jamais surchargees ----
	   Un avertissement doit rester lisible comme un avertissement, meme chez
	   un tenant dont l'accent serait deja orange. */
	/* 7,79:1 sur blanc et 6,93:1 sur le fond de ses propres alertes. Un
	   avertissement et une attente se disent de la meme encre. */
	--avertissement: #8A3A12;
	/* Un bleu franc et jamais surcharge, a 6,31:1. Il ne doit pas valoir
	   --action : l'information se confondrait avec le succes, et --action est
	   surchargeable par tenant alors que --info ne l'est pas. */
	--info: #1D4ED8;

	/* Le focus ne prend plus la couleur de la structure : un anneau clair
	   double d'un anneau vif reste visible sur un fond sombre comme sur un
	   fond clair, ce qu'une couleur unique ne garantit pas. */
	--focus: #1B7F91;
	/* Jeton distinct de --focus, et c'est une OMBRE, pas une couleur. Les
	   regles qui ecrivent « box-shadow:var(--focus-anneau) » posent aussi
	   « outline:none » : leur donner une couleur rend la declaration invalide
	   et supprime tout repere de focus au clavier. */
	--focus-anneau: 0 0 0 3px #FFFFFF, 0 0 0 6px var(--focus);
	/* Du teal vif au rose. Le chiffre vedette est le seul endroit ou un degrade
	   peint du TEXTE : partir du teal lui donne un ancrage sombre, deux roses
	   y tomberaient a 2,3:1. */
	--degrade: linear-gradient(90deg, var(--focus), var(--accent));

	/* ---- Typographie ----
	   Six tailles, deux graisses, un interligne par taille, une taille d'icone
	   par palier. Une valeur hors de ces listes est une valeur a justifier.
	   Une seule famille pour le texte et les titres : la hierarchie se dit par
	   la taille. --police, --t-mini a --t-page viennent de tokens.css (memes
	   valeurs, dupliquees dans live.css et count.css) ; Branding::tokensCss()
	   surcharge --police chez un tenant. */
	/* Meme famille que le texte, ce n'est pas un oubli. Le jeton reste declare
	   parce que des regles le lisent et que Branding ne le pose jamais. */
	--police-titre: var(--police);
	/* Chiffres vedettes seulement : une manuscrite ne se lit pas en
	   paragraphe. Voir nerko.css. */
	--police-chiffre: "Nerko One", "Marker Felt", "Segoe Print", "Chalkboard SE", cursive;
	--taille-base: 15px;

	/* Un interligne par taille : il se resserre quand la taille monte. */
	--lh-mini:  1.35;
	--lh-petit: 1.4;
	--lh-texte: 1.4;
	--lh-fort:  1.3;
	--lh-titre: 1.2;
	--lh-page:  1.15;
	--lh-nu:    1;     /* chiffre vedette, ou l'interligne est du vide */

	/* Une etiquette en capitales sans interlettrage se lit comme un bloc. */
	--ls-capitale: .09em;
	--ls-normal:   0;

	/* Cinq tailles d'icone, une par palier de texte. Prefixe --ic-t- et pas
	   --ic- : les jetons --ic-<nom> plus bas portent des DESSINS en data-URI. */
	--ic-t-mini:  14px;  /* dans un texte a --t-mini */
	--ic-t-petit: 16px;  /* dans un texte a --t-petit */
	--ic-t-texte: 18px;  /* dans un texte a --t-texte */
	--ic-t-fort:  20px;  /* dans un texte a --t-fort */
	--ic-t-seul:  24px;  /* seule dans un bouton, sans texte a cote */

	/* ---- Deux familles de plus, pour le document bilan ----
	   La page admin du bilan embarque le document public (.bl-*) sous le chrome
	   admin (.ui-*) : sa prose dense et ses chiffres d'affichage sont nommes ici
	   pour que bilan.css les lise au lieu de coder ses tailles en dur. */
	--t-corps-lu: 14px;                      /* prose dense du document */
	--t-display-3: clamp(30px, 4vw, 42px);   /* chiffre de section */
	--t-display-2: clamp(36px, 6vw, 56px);   /* chiffre de projection */
	--t-display-1: clamp(56px, 11vw, 104px); /* total vedette du bilan */

	/* ---- Formes ----
	   --rayon a --ombre-forte viennent de tokens.css (memes valeurs que
	   live.css et count.css). Ombres dedoublees : une ombre de contact
	   courte sous une ombre de portee longue, teintees de l'encre teal. */

	/* ---- Rythme ----
	   Les six --espace-N sont l'echelle brute. */
	--espace-1: 4px;
	--espace-2: 8px;
	--espace-3: 12px;
	--espace-4: 16px;
	--espace-5: 24px;
	--espace-6: 32px;

	/* L'ecart dit la parente. Ces quatre jetons nomment l'intention et pointent
	   sur l'echelle brute, ils ne la doublent pas. */
	--v-colle:  var(--espace-2);  /*  8px : un libelle et son controle */
	--v-dedans: var(--espace-4);  /* 16px : deux elements d'un meme bloc */
	--v-entre:  var(--espace-5);  /* 24px : deux blocs voisins */
	--v-danger: var(--espace-6);  /* 32px : avant la zone de danger */

	--largeur-contenu: 1180px;
	--largeur-nav: 240px;
	--transition: .18s ease;

	/* ---- Les jetons du chassis de la maquette ---- */
	--h-barre: 60px;          /* hauteur de la barre haute */
	/* Controle de la barre haute : 14 px d'air dessus et dessous, sans quoi
	   le champ et le selecteur remplissent la barre au lieu d'y poser. */
	--h-ctrl-barre: 32px;
	--l-menu: 228px;          /* largeur du menu lateral sombre */
	--r-s: var(--rayon-petit);
	--ep-bord: 1.5px;         /* epaisseur du contour d'un controle */
	/* Le teal profond du menu lateral. */
	--action-fonce: #08323C;
	--sur-fonce: #D9F0F2;     /* texte pose sur le teal profond */
	/* Les paliers d'icone de la maquette, un par palier de texte. */
	--ic-mini: var(--ic-t-mini);
	--ic-petit: var(--ic-t-petit);
	--ic-texte: var(--ic-t-texte);
	--ic-fort: var(--ic-t-fort);
	--ic-seul: 24px;
	/* Alias de police : la maquette nomme --f-titre et --f-texte, le
	   produit --police-titre et --police. Meme valeur, deux vocabulaires. */
	--f-titre: var(--police-titre);
	--f-texte: var(--police);

	/* Un controle fait 48 px, et c'est une HAUTEUR, pas un plancher : un
	   min-height se fait depasser par son contenu et multiplie les gabarits.
	   48 px couvre le minimum tactile de 44 px. */
	--h-ctrl: 48px;
	--h-ctrl-s: 36px;         /* controle dense : filtre, bouton de ligne */
	/* La grille de comptage reste a 50 px, hors de cette echelle : un faux clic
	   y fausse un comptage. Voir count.css. */
	--cible: var(--h-ctrl);   /* ancien nom, garde pour les regles qui le lisent */

	/* Un marqueur de booleen fait 22 px : case, radio et bille d'interrupteur
	   sont le meme objet a l'oeil. La piste est DERIVEE, jamais posee a la main. */
	--marq: 22px;
	--r-marq: 6px;            /* rayon d'une case a cocher */
	--ep-marq: 4px;           /* jeu entre la bille et le bord de la piste */
	--piste-l: calc(var(--marq) * 2 + var(--ep-marq) * 2);  /* 52px */
	--piste-h: calc(var(--marq) + var(--ep-marq) * 2);      /* 30px */

	/* Icones : masques SVG en data-URI, sans couleur. */
	--ic-catalogue: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M3.5 12.5V4.5a1 1 0 0 1 1-1h8l8 8a1.5 1.5 0 0 1 0 2.1l-6.9 6.9a1.5 1.5 0 0 1-2.1 0l-8-8Z"/><circle cx="7.75" cy="7.75" r="1.4"/></svg>');
	--ic-magasins: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M3.5 9.5v10h17v-10"/><path d="M2.5 9.5 4.2 4.5h15.6l1.7 5a3 3 0 0 1-5.6 1.6 3 3 0 0 1-5.8 0 3 3 0 0 1-5.6-1.6Z"/><path d="M10 19.5v-5h4v5"/></svg>');
	--ic-configurer: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M4 7h6M14 7h6M4 12h11M19 12h1M4 17h3M11 17h9"/><circle cx="12" cy="7" r="2"/><circle cx="17" cy="12" r="2"/><circle cx="9" cy="17" r="2"/></svg>');
	--ic-direct: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M12 21s7-6.2 7-11a7 7 0 1 0-14 0c0 4.8 7 11 7 11Z"/><circle cx="12" cy="10" r="2.5"/></svg>');
	--ic-bilan: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M4 20h16"/><path d="M7 20v-5.5M12 20V8.5M17 20v-9"/></svg>');
	--ic-envoyer: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M20.5 3.5 2.8 10.2a.5.5 0 0 0 0 .9l7 2.8 2.8 7a.5.5 0 0 0 .9 0Z"/><path d="M20.5 3.5 9.8 13.9"/></svg>');
	--ic-info: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="8.5"/><path d="M12 11.2v5"/><circle cx="12" cy="7.9" r="1.05" fill="black" stroke="none"/></svg>');
	--ic-succes: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="8.5"/><path d="m8.2 12.2 2.6 2.6 5-5.2"/></svg>');
	--ic-erreur: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="8.5"/><path d="m9.2 9.2 5.6 5.6M14.8 9.2l-5.6 5.6"/></svg>');
	--ic-avertissement: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3.8 21 19.2a1.3 1.3 0 0 1-1.1 2H4.1a1.3 1.3 0 0 1-1.1-2Z"/><path d="M12 9.5v4.5"/><circle cx="12" cy="17.3" r="1.05" fill="black" stroke="none"/></svg>');
	--ic-attente: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="8.5"/><path d="M12 7.2V12l3.2 2"/></svg>');
	--ic-externe: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M13.5 4.5h6v6"/><path d="m19.5 4.5-8 8"/><path d="M18 14.5v4a1.5 1.5 0 0 1-1.5 1.5h-11A1.5 1.5 0 0 1 4 18.5v-11A1.5 1.5 0 0 1 5.5 6h4"/></svg>');
	--ic-importer: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3.5v11"/><path d="m8 10.5 4 4 4-4"/><path d="M4 16.5v2.5a1.5 1.5 0 0 0 1.5 1.5h13a1.5 1.5 0 0 0 1.5-1.5v-2.5"/></svg>');
	--ic-exporter: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M12 14.5v-11"/><path d="m8 7.5 4-4 4 4"/><path d="M4 16.5v2.5a1.5 1.5 0 0 0 1.5 1.5h13a1.5 1.5 0 0 0 1.5-1.5v-2.5"/></svg>');
	--ic-ajouter: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="8.5"/><path d="M12 8.5v7M8.5 12h7"/></svg>');
	--ic-copier: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><rect x="8.5" y="8.5" width="12" height="12" rx="1.8"/><path d="M5.5 15.5h-.5a1.5 1.5 0 0 1-1.5-1.5V5a1.5 1.5 0 0 1 1.5-1.5h9A1.5 1.5 0 0 1 15.5 5v.5"/></svg>');
	--ic-generer: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="m10 3.5 1.6 4.4 4.4 1.6-4.4 1.6L10 15.5 8.4 11.1 4 9.5l4.4-1.6Z"/><path d="M17.5 14.5l.8 2.2 2.2.8-2.2.8-.8 2.2-.8-2.2-2.2-.8 2.2-.8Z"/></svg>');

	/* ---- Les noms de jetons de la maquette, en alias ----
	   La maquette nomme --c-*, --e*, --k-*, le produit --fond, --espace-*,
	   --carte. L'alias garde une seule source de verite par couleur. */
	--c-papier: var(--carte);
	--c-surface: var(--fond);
	--c-surface-2: var(--action-pale);
	--c-voile: var(--action-clair);
	--c-encre: var(--texte);
	--c-doux: var(--doux);
	--c-sur-fonce: var(--sur-fonce);
	--c-action: var(--action);
	--c-action-fonce: var(--action-fonce);
	--c-action-vif: var(--focus);
	--c-action-voile: var(--action-clair);
	--c-accent: var(--accent);
	--c-accent-vif: var(--accent-clair);
	--c-accent-encre: var(--accent-encre);
	--c-accent-contraste: var(--sur-accent);
	--c-accent-voile: var(--accent-voile);
	--c-trait: var(--trait);
	--c-bord: var(--bord-controle);
	--c-piste: #B4C4CA;
	/* Les etats. --c-alerte porte l'avertissement, --c-erreur l'erreur : deux
	   roles distincts, deux encres. */
	--c-succes: #1DE9B6;
	--c-succes-encre: #0C725E;
	--c-succes-voile: #D8FBF2;
	--c-alerte: #FF1744;
	--c-alerte-encre: var(--avertissement);
	--c-alerte-voile: #FFE3E9;
	--c-erreur: var(--erreur);
	--c-erreur-encre: var(--erreur);
	--c-erreur-voile: #F7E4F2;
	--c-anis: #B7CE1F;
	--c-anis-fonce: #9E9D24;
	--c-anis-encre: var(--texte);
	--c-anis-voile: #F6F8DA;
	--c-mauve: #D500F9;
	--c-mauve-fonce: #78008C;
	--c-mauve-voile: #FBE6FF;

	/* Rembourrage sous les noms de la maquette. */
	--e1: var(--espace-1);
	--e2: var(--espace-2);
	--e3: var(--espace-3);
	--e4: var(--espace-4);
	--e5: var(--espace-5);
	--e6: 36px;

	/* Rayons et polices sous les noms de la maquette. */
	--r: var(--rayon);
	--r-pilule: var(--rayon-pilule);
	--f-chiffre: var(--police-chiffre);
	--f-code: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
	--ombre-f: var(--ombre-forte);

	/* Le contenant. */
	--k-fond: var(--c-papier);
	--k-trait: 1px solid var(--c-trait);
	--k-rayon: var(--r);
	--k-retrait: var(--e4);
	--k-retrait-serre: var(--e3) var(--e4);

	/* Les deux largeurs de lecture. */
	--l-lecture: 640px;
	--l-formulaire: 860px;

	--u-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M6 9l6 6 6-6' fill='none' stroke='%230E5464' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}

/* ================================================================ *
 * 2. RESET ET BASE
 * ================================================================ */

* { box-sizing: border-box; }

html { background: var(--fond); }

body {
	margin: 0;
	background: var(--fond);
	color: var(--texte);
	font: var(--taille-base)/var(--lh-texte) var(--police);
	-webkit-text-size-adjust: 100%;
	/* Chiffres a chasse fixe : sans cela, le « 1 » de Poppins est plus etroit
	   que le « 8 » et une colonne de nombres danse d'une ligne a l'autre. */
	font-variant-numeric: tabular-nums;
}

img { max-width: 100%; height: auto; }

[hidden] { display: none !important; }

/* h1-h3 seuls passent en --police-titre. h4 reste sur --police, plus bas :
   il joue un role de sous-titre discret en capitales.
   Le titre est en encre, pas en teal : le teal est la couleur de l'action, un
   titre teal se lirait comme un lien. */
.ui-mode-admin h1, .ui-mode-public h1 { font-family: var(--police-titre); font-size: var(--t-page); font-weight: var(--g-fort); color: var(--c-encre, var(--encre)); margin: 0 0 var(--espace-3); }
/* Les interlignes de titre, sans quoi h1 herite le 1,45 du corps de texte. Un
   titre s'ecrit serre pour se lire comme un bloc et non comme un paragraphe. */
.ui-mode-admin h1, .ui-mode-public h1 { line-height: 1.15; }
.ui-mode-admin h2, .ui-mode-public h2 { line-height: 1.2; }
.ui-mode-admin h3, .ui-mode-public h3 { line-height: 1.3; }
/* Un h2 nu est un titre de 22 px, sans marge : la grille de la page et le gap
   des blocs portent le rythme. Ce sont ses PARENTS (.bloc-tete, .feuille-tete,
   le summary d'un depliant) qui en font une etiquette en capitales. */
.ui-mode-admin h2, .ui-mode-public h2 { font-family: var(--police-titre); font-size: var(--t-titre); font-weight: var(--g-fort); line-height: var(--lh-titre); letter-spacing: var(--ls-normal); color: var(--c-encre); margin: 0; }
.ui-mode-admin h3, .ui-mode-public h3 { font-family: var(--police-titre); font-size: var(--t-fort); font-weight: var(--g-fort); margin: var(--espace-4) 0 var(--espace-2); }

/* Borne aux deux modes : la page de comptage ne charge que count.css.
   :not(.aide) : un <p class="aide"> reste au petit corps de .aide (ligne
   3790 plus bas), sinon un element + une classe l'emporte a tort sur une
   classe seule, peu importe l'ordre des regles dans ce fichier. */
.ui-mode-admin p:not(.aide), .ui-mode-public p:not(.aide) { font-size: var(--t-texte); }

/* Le h4 se distingue de son h3 par la CASSE et la couleur, jamais par une
   taille qui inverserait le rang. Meme traitement que .ui-card > legend et
   .ui-panel-titre, qui jouent le meme role de sous-titre de bloc. */
.ui-mode-admin h4, .ui-mode-public h4 {
	font-size: var(--t-petit);
	font-weight: var(--g-fort);
	text-transform: uppercase;
	letter-spacing: .5px;
	color: var(--action);
	margin: var(--espace-4) 0 var(--espace-2);
}

/* Les liens DE CONTENU prennent le teal. Tout composant qui pose lui-meme un
   fond ou une couleur (bouton, badge, onglet, pastille de filtre, ligne « A
   faire ») DOIT etre exclu ici, sinon son libelle rend teal sur teal, soit un
   contraste de 1:1. tests/contraste-css-test.php le verifie un par un.
   .ui-ong est la vraie classe des onglets de collecte (kit_onglets(),
   templates/kit/onglets.html.twig). */
.ui-contenu a:not(.btn):not(.ui-badge):not(.ui-ong):not(.fpil):not(.lien-mini):not(.afaire-l) { color: var(--action); }

/* Masquage visuel sans masquage pour les lecteurs d'ecran. */
.ui-sr {
	position: absolute;
	width: 1px; height: 1px;
	padding: 0; margin: -1px;
	overflow: hidden;
	clip: rect(0 0 0 0);
	white-space: nowrap;
	border: 0;
}

/* Focus visible partout, jamais outline:none sans remplacement. La regle de
   base couvre tout ce qu'une enumeration de selecteurs oublierait ;
   l'enumeration dessous documente les surfaces qui doivent le porter. */
:focus-visible {
	outline: 3px solid var(--focus);
	outline-offset: 2px;
}

.btn:focus-visible,
.menu-a:focus-visible,
.menu .ui-nav-groupe a:focus-visible,
.ui-pagination a:focus-visible,
.ui-seg button:focus-visible,
.ui-evitement:focus,
[data-ui-copier]:focus-visible {
	outline: 3px solid var(--focus);
	outline-offset: 2px;
}

/* Lien d'evitement : premier element focalisable de chaque page. */
.ui-evitement {
	position: absolute;
	left: -9999px;
	top: 0;
	z-index: 100;
	background: var(--carte);
	color: var(--action);
	font-weight: var(--g-fort);
	padding: 12px 18px;
	border-radius: 0 0 var(--rayon-petit) 0;
	box-shadow: var(--ombre-forte);
}
.ui-evitement:focus { left: 0; }

/* ================================================================ *
 * 3. BOUTONS
 * ================================================================ *
 * Le style visuel (.btn, .btn--*) vit dans la section « SYSTEME DE LA
 * MAQUETTE » en fin de feuille : c'est elle la reference, portee le
 * 23 septembre 2026. Ce qui reste ici est la MECANIQUE, jamais recouverte
 * par la maquette : etat desactive, chargement, et les groupes/voisinages
 * qui donnent leur place aux boutons dans un formulaire ou un tableau. */

/* Desactive par une vraie paire de couleurs, jamais par opacite : l'opacite
   delave le fond ET le texte dans la meme proportion, le rapport entre les deux
   s'effondre. Ici --doux sur --fond, 5,83:1. La bordure coloree disparait avec
   le fond, sinon le bouton garde l'air cliquable. */
.btn:disabled,
.btn[aria-disabled="true"] {
	background: var(--fond);
	/* --trait rendrait 1,25:1 ici, pour un seuil de 3 : le bouton n'aurait plus
	   de bord, donc plus de forme. */
	border-color: var(--bord-controle);
	color: var(--doux);
	cursor: not-allowed;
	box-shadow: none;
	transform: none;
}

/* Etat chargement : le texte disparait, un spinner prend sa place. */
.btn.is-loading { position: relative; color: transparent; pointer-events: none; }
.btn.is-loading::after {
	content: '';
	position: absolute;
	top: 50%; left: 50%;
	width: 16px; height: 16px;
	margin: -8px 0 0 -8px;
	border: 2px solid rgba(255, 255, 255, .4);
	border-top-color: #fff;
	border-radius: 50%;
	animation: ui-spin .7s linear infinite;
}
.btn:not(.btn--primaire):not(.btn--accent).is-loading::after { border-color: rgba(0, 0, 0, .18); border-top-color: var(--action); }

@keyframes ui-spin { to { transform: rotate(360deg); } }

/* Deux boutons cote a cote a parts egales (les deux envois du recover). */
.ui-btn-groupe { display: flex; gap: var(--espace-3); flex-wrap: wrap; }

/* La largeur d'un bouton suit son libelle, la hierarchie se lit a la couleur :
   un partage en parts egales donne le meme poids visuel a l'action principale
   et au lien de repli. La regle porte sur tous les enfants directs, car une
   action peut etre enveloppee dans un <form>. */
.ui-btn-groupe > * { flex: 0 1 auto; min-width: 0; }
/* Le <form> devenu element de partage transmet sa largeur au bouton qu'il
   porte, sinon il partage un espace que le bouton n'occupe pas. */
.ui-btn-groupe > form { display: flex; }
.ui-btn-groupe > form > .btn { width: 100%; }

/* Barre d'actions, pour un groupe de plus de deux boutons : largeur naturelle
   et repli par le flex-wrap. La regle est nommee et non appliquee a
   .ui-btn-groupe nu, dont les ecrans a deux boutons dependent. */
.ui-btn-groupe--auto > *,
.ui-btn-groupe--auto .btn { flex: 0 1 auto; }

@media (max-width: 560px) {
	.ui-btn-groupe--auto { flex-direction: column; }
	/* Une barre d'actions s'empile d'un seul bloc : la regle porte sur les
	   enfants directs quelle que soit leur variante, et sur le bouton qu'un
	   <form> envelopperait. */
	.ui-btn-groupe--auto > *,
	.ui-btn-groupe--auto > * > .btn { width: 100%; }
}

/* ================================================================ *
 * 4. FORMULAIRES
 * ================================================================ */

.ui-form { display: flex; flex-direction: column; gap: var(--espace-4); }

.ui-field { display: flex; flex-direction: column; gap: 6px; }
/* Seul champ de son bloc : label devant le champ. */
.bloc-corps > .ui-field:only-child,
.bloc-corps > .champ:only-child { display: flex; flex-direction: row; align-items: center; gap: var(--espace-3); }
.bloc-corps > .ui-field:only-child > .ui-label,
.bloc-corps > .champ:only-child > .champ-tete { margin: 0; white-space: nowrap; }
.bloc-corps > .ligne-statut:only-child { padding: 0; }

/* L'espace entre deux champs appartient aux champs, pas a leur boite, qu'un
   gabarit peut nommer autrement. En flex, gap et marge d'un item s'additionnent,
   d'ou la limitation aux freres directs. */
.ui-field + .ui-field,
.ui-field + .ui-rangee,
.ui-field + .btn,
.ui-field + .ui-form-actions,
.ui-fieldset + .ui-field,
.ui-field + .ui-fieldset { margin-top: var(--espace-4); }

/* L'aide commente le champ au-dessus d'elle : elle lui reste collee, sans quoi
   elle se lit comme l'introduction du champ d'en dessous. */
.ui-aide + .btn,
.ui-aide + .ui-rangee { margin-top: var(--espace-3); }

/* Casse normale et couleur du texte : les capitales suppriment les jambages,
   qui sont les reperes de lecture, et un libelle long devient un mur. Le poids
   suffit a distinguer un libelle de sa valeur. */
.ui-label {
	font-size: var(--t-petit);
	font-weight: var(--g-fort);
	color: var(--texte);
}
/* L'asterisque se cale en exposant, pas sur la ligne de base : c'est la forme
   que l'oeil attend d'une marque d'obligation. */
.ui-label .ui-requis {
	color: var(--erreur);
	margin-left: 2px;
	font-size: .85em;
	vertical-align: super;
	line-height: 0;
}

/* Largeur maximale par defaut : un controle dix fois plus large que sa valeur
   casse le lien entre l'etiquette et la donnee. 34rem vaut environ 545 px, la
   longueur confortable d'une ligne de saisie. Le « width: 100% » reste, il fait
   retrecir le controle sur un ecran etroit. */
.ui-champ {
	width: 100%;
	max-width: 34rem;
	/* Meme hauteur que .btn, qu'il cotoie sur une ligne de formulaire. Elle
	   absorbe aussi la bascule de taille de texte a 561 px, qui ferait sinon
	   changer le champ de gabarit. textarea.ui-champ la reprend en auto. */
	height: var(--h-ctrl);
	min-height: 0;
	padding: 0 14px;
	/* Le contour qui dit ou est le champ : --trait n'y rendrait que 1,32:1, pour
	   un seuil de 3 exige d'un element d'interface. Voir --bord-controle. */
	border: 1px solid var(--bord-controle);
	border-radius: var(--rayon-petit);
	background: var(--carte);
	color: var(--texte);
	font-family: inherit;
	/* 16 px sur mobile : en dessous, iOS zoome au focus et le formulaire devient
	   inutilisable sur telephone. */
	font-size: 16px;
	line-height: 1.3;
	transition: border-color var(--transition), box-shadow var(--transition);
}
@media (min-width: 561px) { .ui-champ { font-size: var(--t-texte); } }

/* Focus dessiné dans la boîte : un anneau extérieur mordait sur le label suivant. */
.ui-champ:focus,
.ui-champ:focus-visible {
	outline: none;
	border-color: var(--focus);
	box-shadow: inset 0 0 0 1px var(--focus);
}
.ui-champ::placeholder { color: var(--doux); }

/* Un champ desactive se lit lui aussi : vraie paire de couleurs, jamais
   opacity. Aucun etat du produit ne se signale par opacity. La paire est celle
   de .btn:disabled, --doux sur --fond a 5,83:1, et le contour garde
   --bord-controle : sans contour, ce n'est plus un champ.
   [data-verrouille] partage la meme paire : un champ readonly, comme le slug
   verrouille apres le passage en direct, ne doit pas rendre actif. */
.ui-champ:disabled,
.ui-champ[data-verrouille] {
	background: var(--fond);
	border-color: var(--bord-controle);
	color: var(--doux);
	cursor: not-allowed;
}
/* Un champ desactive garde un texte de substitution lisible. */
.ui-champ:disabled::placeholder,
.ui-champ[data-verrouille]::placeholder { color: var(--doux); }

/* La seule exception a la hauteur de controle : une zone multiligne se mesure
   en lignes. Elle reprend height:auto et retrouve un plancher. */
textarea.ui-champ {
	height: auto;
	min-height: 110px;
	padding: 12px 14px;
	resize: vertical;
}

/* Le chevron du select : SVG en data URI, trait en currentColor pour suivre
   l'accent du tenant. */
.ui-select {
	appearance: none;
	-webkit-appearance: none;
	cursor: pointer;
	padding-right: 38px;
	color: var(--texte);
	background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'/%3e%3c/svg%3e");
	background-repeat: no-repeat;
	background-position: right 13px center;
	background-size: 17px;
}

.ui-aide { font-size: var(--t-mini); color: var(--doux); }

.ui-erreur { font-size: var(--t-petit); color: var(--erreur); font-weight: var(--g-fort); }

.ui-field.is-erreur .ui-champ { border-color: var(--erreur); }
.ui-field.is-erreur .ui-champ:focus { border-color: var(--erreur); box-shadow: inset 0 0 0 1px var(--erreur); }

/* Case a cocher et radio : la zone cliquable couvre tout le libelle. */
/* Pas de min-height : sur une ligne, la cible tactile de 48px ne laissait que du vide clicable. */
.ui-case {
	display: flex;
	align-items: center;
	gap: var(--espace-3);
	cursor: pointer;
}
/* La coche est blanche par decision du produit, jamais par « accent-color » :
   celui-ci ne pilote que le fond, et le navigateur choisit seul la couleur de
   la coche d'apres la luminosite. Un tenant a l'accent clair la ferait basculer
   au noir, sous le seuil de 3 exige d'un element d'interface.
   Elle est tracee en deux bords d'un pseudo-element tourne a 45 degres : rien a
   charger, et elle suit la taille de la case. */
.ui-case input {
	appearance: none;
	-webkit-appearance: none;
	position: relative;
	width: 20px; height: 20px;
	margin: 2px 0 0;
	flex: 0 0 auto;
	/* Contour de controle : avec --trait la case decochee ne rendrait que
	   1,32:1 sur la carte, pour un seuil de 3. */
	border: 1px solid var(--bord-controle);
	background: var(--carte);
	cursor: pointer;
	transition: background-color var(--transition), border-color var(--transition);
}
.ui-case input[type="checkbox"] { border-radius: 5px; }
.ui-case input[type="radio"] { border-radius: 50%; }

.ui-case input:checked {
	background: var(--action);
	border-color: var(--action);
}
.ui-case input[type="checkbox"]:checked::after {
	content: '';
	position: absolute;
	left: 6px; top: 2px;
	width: 5px; height: 10px;
	border: solid #fff;
	border-width: 0 2px 2px 0;
	transform: rotate(45deg);
}
/* Le radio coche porte un disque plein, forme que l'oeil attend d'un choix
   unique, et qui reste lisible a 20 px la ou une coche se brouille. */
.ui-case input[type="radio"]:checked::after {
	content: '';
	position: absolute;
	inset: 5px;
	border-radius: 50%;
	background: #fff;
}
/* Meme paire de couleurs que les autres etats desactives, jamais opacity : une
   case delavee garde l'air d'un controle actif a moitie peint. */
.ui-case input:disabled {
	background: var(--fond);
	border-color: var(--bord-controle);
	cursor: not-allowed;
}
.ui-case input:disabled:checked::after { border-color: var(--doux); }
.ui-case input[type="radio"]:disabled:checked::after { background: var(--doux); }
.ui-case:has(input:disabled) { cursor: not-allowed; color: var(--doux); }

/* L'erreur d'une case s'aligne sur le texte de la case : le retrait vaut la
   case (20 px) plus le gap de la rangee (--espace-3). */
.ui-field:has(> .ui-case) > .ui-erreur { margin-left: calc(20px + var(--espace-3)); }

/* L'aide d'une case est un point d'info au survol : affichee en permanence
   elle flotte sans lien avec la case. Le texte reste lu par les lecteurs
   d'ecran via .ui-sr et aria-describedby. */
.ui-case-info {
	flex: 0 0 auto;
	width: 16px; height: 16px;
	margin-top: 2px;
	border-radius: 50%;
	background-color: var(--doux);
	-webkit-mask: var(--ic-info) center / 16px 16px no-repeat;
	mask: var(--ic-info) center / 16px 16px no-repeat;
	cursor: help;
}

/* ---- Selecteur de couleur ----
   La hauteur de 44 px aligne la pastille sur le champ voisin et sur la cible
   tactile du kit ; sans regle, le navigateur lui donne sa taille native. */
.ui-pastille {
	appearance: none;
	-webkit-appearance: none;
	inline-size: 44px;
	block-size: 44px;
	padding: 2px;
	/* Contour de controle, pas filet : avec --trait il ne rend que 1,32:1 et une
	   pastille de couleur claire se fond dans la carte. */
	border: 1px solid var(--bord-controle);
	border-radius: var(--rayon-petit);
	background: var(--carte);
	cursor: pointer;
	flex: 0 0 auto;
}
/* Le pave de couleur est un element interne du navigateur : sans ces regles il
   garde son cadre natif et ignore le rayon du controle. */
.ui-pastille::-webkit-color-swatch-wrapper { padding: 0; }
.ui-pastille::-webkit-color-swatch,
.ui-pastille::-moz-color-swatch {
	border: 0;
	border-radius: calc(var(--rayon-petit) - 3px);
}
/* Desactive par une paire de couleurs, jamais par opacite : delavee, la bordure
   tombe a 1,13:1 pour un seuil de 3, et la pastille devient un carre de couleur
   flottant. Seuls le contour et le curseur disent l'etat. */
.ui-pastille:disabled {
	border-color: var(--bord-controle);
	background: var(--fond);
	cursor: not-allowed;
}

/* Un code hexadecimal a sept caracteres : largeur propre et chasse fixe. */
.ui-champ--code { max-width: 9rem; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }

/* La taille n'est pas touchee, elle herite deja juste. */
.ui-contenu code {
	font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

/* Le libelle de la case, rendu par kit/champ.html.twig en <span> dans le label.
   Il prend la taille du corps de texte : sans regle il herite du 16 px du body
   et se rend plus gros que le .ui-label qui le surplombe. */
.ui-case > span {
	font-size: var(--t-texte);
	line-height: 1.4;
}

.ui-form-ligne { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--espace-4); }
.ui-form-ligne > .ui-field { min-width: 0; }
@media (min-width: 721px) {
	.ui-form-ligne { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Le nombre de colonnes se declare : .ui-form-ligne en calcule deux, et un
   gabarit qui lui confie trois champs voit le troisieme retomber seul, sans
   qu'aucune erreur ne le signale. La colonne unique sous 721 px reste celle du
   composant, une rangee a trois etant illisible sur telephone. */
@media (min-width: 721px) {
	.ui-form-ligne--3 { grid-template-columns: repeat(3, 1fr); }
}
/* Une rangee de champs courts suit son contenu, et le vide se regroupe a
   droite. En flex et non en colonnes egales : des « 1fr » repartiraient le vide
   entre les champs, donnant a un zoom le meme tiers de rangee qu'a une latitude.
   Sous 721 px la rangee redevient une colonne. */
.ui-form-ligne--courts { display: grid; grid-template-columns: 1fr; gap: var(--espace-4); }
@media (min-width: 721px) {
	.ui-form-ligne--courts {
		display: flex;
		flex-wrap: wrap;
		align-items: flex-start;
	}
	.ui-form-ligne--courts > .ui-field { flex: 0 0 auto; }
}

/* Une rangee ne remet pas l'espace vertical entre ses propres champs :
   « .ui-field + .ui-field » pose une marge juste dans une pile et fausse dans
   une rangee, ou elle desaligne les champs. Le gap porte deja les deux axes. */
.ui-form-ligne > .ui-field + .ui-field,
.ui-form-ligne--courts > .ui-field + .ui-field,
/* .ui-grille est dans le meme cas : elle aussi pose ses .ui-field cote a cote
   et porte son propre gap. */
.ui-grille > .ui-field + .ui-field,
.rangee-filtres > .ui-field + .ui-field,
:is(.bloc-corps, .carte, .feuille-corps) > :is(.ui-field, .ui-fieldset) + :is(.ui-field, .ui-fieldset, .ui-rangee, .btn, .ui-form-actions) { margin-top: 0; }

/* Un formulaire non plus : .ui-form est un flex, et en flex le gap s'ADDITIONNE
   a la marge de l'item, il n'y a pas de margin collapsing. Deux champs freres
   directs se separeraient sinon du double. */
.ui-form > .ui-field + .ui-field { margin-top: 0; }

/* ---- Largeur par la nature de la donnee ----
   Un controle dix fois plus large que sa valeur rompt le lien entre l'etiquette
   et la donnee. Sur le conteneur et par :has(), et non par une classe :
   Kit::champ() ecrit « class="ui-champ" » en dur, y glisser une classe
   produirait deux attributs class sur le meme controle. Ces selecteurs LISENT
   le type deja ecrit, ils ne changent aucun octet du HTML rendu.
   .ui-champ--moyen reste disponible quand le gabarit veut une largeur que le
   type ne dit pas. */
.ui-field:has(> input[type="number"]) > .ui-champ { max-width: 7rem; }
.ui-field:has(> input[inputmode="numeric"]) > .ui-champ { max-width: 9rem; }
.ui-field:has(> input[inputmode="decimal"]) > .ui-champ { max-width: 11rem; }
/* Une date et une heure rendent 135 px de texte dans un controle natif qui
   porte en plus son calendrier : 15rem laissent la place aux deux. */
.ui-field:has(> input[type="datetime-local"]) > .ui-champ { max-width: 15rem; }
.ui-field:has(> input[type="date"]) > .ui-champ { max-width: 11rem; }
.ui-field:has(> input[type="time"]) > .ui-champ { max-width: 9rem; }
/* « 06 12 34 56 78 » mesure 103 px dans la police du produit. */
.ui-field:has(> input[type="tel"]) > .ui-champ { max-width: 13rem; }

/* Un menu prend la largeur de sa plus longue option, pas celle de la page.
   « fit-content » laisse le navigateur mesurer les options lui-meme, ce
   qu'aucune valeur en dur ne sait faire : le menu suit le contenu reel du
   tenant. Le plancher de 12rem evite qu'un menu a deux options courtes
   (« Oui » / « Non ») ne devienne une cible minuscule. */
.ui-field:has(> select) > .ui-champ {
	width: fit-content;
	max-width: 100%;
}
/* Le plancher ne vaut que la ou il y a la place de le tenir : plus large que
   la colonne qui le porte, il ne protege plus la cible, il fabrique un
   debordement. Sous 721 px, « fit-content » et « max-width: 100% » suffisent. */
@media (min-width: 721px) {
	.ui-field:has(> select) > .ui-champ { min-width: 12rem; }
	/* Sauf dans une barre de filtres, ou la grille donne la mesure : les
	   colonnes de .ui-filtres sont plus etroites que le plancher, et un menu
	   qui depasse sa colonne chevauche son voisin. */
	.ui-filtres .ui-field:has(> select) > .ui-champ { min-width: 0; width: 100%; }
}

/* Un texte long s'aligne sur la colonne, il ne fabrique pas un second bord
   droit dans le meme formulaire. Dans une rangee il occupe la largeur entiere :
   c'est le seul champ dont la valeur grandit avec ce qu'on ecrit. */
.ui-form-ligne > .ui-field:has(> textarea),
.ui-form-ligne--3 > .ui-field:has(> textarea) { grid-column: 1 / -1; }

/* Variante asymetrique, pour une ligne dont le premier bloc est plus large que
   son champ texte : un depot d'image et son texte alternatif. Le repli a une
   colonne sur mobile est conserve. */
.ui-form-ligne--asymetrique { grid-template-columns: 1fr; }
@media (min-width: 721px) { .ui-form-ligne--asymetrique { grid-template-columns: 2fr 1fr; } }

/* Le bouton principal est dernier dans le DOM (ordre de tabulation naturel)
   et a droite en desktop. Sur mobile la barre s'empile, principal en haut. */
.ui-form-actions {
	display: flex;
	flex-wrap: wrap;
	gap: var(--espace-3);
	justify-content: flex-end;
	margin-top: var(--espace-4);
}
/* L'ordre d'empilement ne depend pas de l'ordre d'ecriture du gabarit : la
   regle porte sur la VARIANTE et non sur la position, un column-reverse
   descendrait le primaire ecrit en premier. */
@media (max-width: 560px) {
	.ui-form-actions { flex-direction: column; }
	.ui-form-actions .btn--primaire { order: -1; }
	.ui-form-actions .btn { width: 100%; }
}

/* Un bouton nu dans un .ui-form ne fait pas la largeur de la colonne : .ui-form
   est un flex en colonne, un <button> enfant direct s'y etirerait, et .btn
   n'a aucun plafond de largeur. Sous 560 px l'etirement est retabli. */
.ui-form > .btn { align-self: flex-start; }
@media (max-width: 560px) {
	.ui-form > .btn { align-self: stretch; width: 100%; }
}

/* Lecture seule (role viewer) : le formulaire reste lisible, les actions
   disparaissent. On n'ecrit pas "vous n'avez pas le droit", on montre. */
/* LECTURE SEULE : MEME MECANIQUE QUE LES AUTRES ETATS, SANS OPACITE.
   Mesure du 13 septembre 2026 : « opacity: .7 » donnait ici 5,93:1, donc au
/* ================================================================ *
 * 5. TABLEAUX
 * ================================================================ */

/* Toute table du produit vit dans ce conteneur : le corps de page ne defile
   JAMAIS horizontalement. */
/* Le debordement se voit, il ne se devine pas : une colonne cachee sans indice
   est une fonction perdue. L'ombre reste discrete pour ne rien masquer.
   La scrollbar systeme est retiree : superposee a l'ombre, elle donne deux
   indices de debordement au lieu d'un. Le scroll reste utilisable au doigt, au
   trackpad et a la molette, seule sa barre visuelle disparait. */
.ui-table-scroll {
	position: relative;
	overflow-x: auto;
	-webkit-overflow-scrolling: touch;
	border-radius: var(--rayon-petit);
	background-color: var(--carte);
	scrollbar-width: none;
}
.ui-table-scroll::-webkit-scrollbar { display: none; }

.ui-table-indice {
	margin: 0 0 var(--espace-2);
	font-size: var(--t-petit);
	font-weight: var(--g-fort);
	color: var(--doux);
}

/* Repere stable entre les ecrans d'une meme collecte. */
.ui-nav-collecte {
	display: flex;
	gap: var(--espace-2);
	margin: 0 0 var(--espace-5);
	padding: var(--espace-1);
	overflow-x: auto;
	-webkit-overflow-scrolling: touch;
	border: 1px solid var(--trait);
	border-radius: var(--rayon-petit);
	background: var(--carte);
}
.ui-nav-collecte a {
	flex: 0 0 auto;
	min-height: 40px;
	display: inline-flex;
	align-items: center;
	padding: 7px 12px;
	border-radius: calc(var(--rayon-petit) - 2px);
	color: var(--texte);
	font-size: var(--t-petit);
	font-weight: var(--g-fort);
	text-decoration: none;
}
.ui-nav-collecte a:hover { background: var(--fond); color: var(--action); }
/* Jamais un blanc en dur : chez un tenant a accent clair, Branding::sur()
   bascule en encre sombre et l'onglet resterait illisible. */
.ui-nav-collecte a[aria-current="page"] { background: var(--action); color: var(--sur-action); }

.ui-liste-liens { list-style: none; margin: 0; padding: 0; }
.ui-liste-liens li {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: var(--espace-3);
	padding: var(--espace-3) 0;
	border-bottom: 1px solid var(--trait);
}
.ui-liste-liens li:last-child { border-bottom: 0; }

@media (max-width: 720px) {
	.ui-nav-collecte { margin-inline: calc(-1 * var(--espace-3)); border-inline: 0; border-radius: 0; }
	.ui-nav-collecte a { min-height: var(--cible); }
	.ui-liste-liens li { align-items: flex-start; flex-direction: column; gap: var(--espace-1); }
}

.ui-table {
	width: 100%;
	border-collapse: collapse;
	font-size: var(--t-texte);
	min-width: 560px;
	background: var(--carte);
}

/* Sous 560 px le plancher de .ui-table reclame plus de place qu'il n'en existe
   et fabrique lui-meme le debordement : le remettre a zero laisse les colonnes
   se serrer dans la place reelle.
   La bascule en cartes n'est PAS le defaut : .ui-table--cartes lit data-libelle
   sur chaque <td>, et un gabarit qui ne le pose pas rendrait des valeurs
   anonymes, soit une troncature visible echangee contre un faux-sens invisible.
   Elle reste explicite, gabarit par gabarit, apres la pose des data-libelle. */
@media (max-width: 560px) {
	.ui-table { min-width: 0; }
}

/* Statut et Collectes portent un badge court : sans plafond, elles prennent a
   l'oeil autant de place que Nom. Un max-width borne les deux colonnes etroites,
   le tableau garde par ailleurs le layout auto. */
.ui-table--orgs th:nth-child(3),
.ui-table--orgs th:nth-child(4) { max-width: 90px; }

/* En layout auto, Nom herite de la largeur du mot le plus court : un badge et
   deux colonnes larges suffisent a l'ecraser sous 90 px. */
.ui-table--orgs td.ui-carte-titre { min-width: 140px; }
.ui-table caption { text-align: left; font-weight: var(--g-fort); padding: 0 0 var(--espace-2); }

.ui-table thead th {
	position: sticky;
	top: 0;
	z-index: 2;
	background: var(--action);
	color: var(--sur-action);
	font-size: var(--t-mini);
	font-weight: var(--g-fort);
	text-transform: uppercase;
	letter-spacing: .3px;
	text-align: left;
	padding: 10px 12px;
	white-space: nowrap;
}
.ui-table thead th.n { text-align: right; }
.ui-table thead th[data-sort] { cursor: pointer; user-select: none; padding: 0; }
/* Le survol eclaircit la barre, il ne la repeint pas : un aplat d'action sur
   un en-tete dit « action principale », pas « triable ». */
.ui-table thead th[data-sort]:hover { background: var(--action); filter: brightness(1.35); }
.ui-table thead th[data-dir] { background: var(--action); filter: brightness(1.2); }

/* Le bouton de tri, injecte par ui.js dans chaque en-tete triable. Il herite
   tout du <th> pour que la colonne reste identique a l'oeil ; sa raison d'etre
   est semantique, un role actionnable annonce au lecteur d'ecran la ou un
   <th tabindex="0"> reste une simple cellule. Le padding descend du <th> vers
   le bouton pour que la cible couvre la cellule, et min-height la porte a
   --cible sans allonger le <th>. */
.ui-table thead th[data-sort] > .ui-tri {
	display: flex;
	align-items: center;
	gap: 6px;
	width: 100%;
	min-height: var(--cible);
	padding: 10px 12px;
	border: 0;
	background: none;
	color: inherit;
	font: inherit;
	letter-spacing: inherit;
	text-align: inherit;
	text-transform: inherit;
	cursor: pointer;
}
.ui-table thead th[data-sort] > .ui-tri:focus-visible {
	outline: 3px solid var(--focus);
	outline-offset: -3px;
}
.ui-table thead th .ui-fleche { display: inline-block; width: 10px; font-size: 10px; opacity: .85; }
.ui-table thead th[data-dir="asc"] .ui-fleche::after { content: '\25B2'; }
.ui-table thead th[data-dir="desc"] .ui-fleche::after { content: '\25BC'; }

.ui-table tbody td { padding: 9px 12px; border-bottom: 1px solid var(--trait); color: var(--texte); }
/* Le filet de la derniere ligne doublerait le bord du cadre. */
.ui-table tbody tr:last-child td { border-bottom: 0; }
.ui-table tbody td.n { text-align: right; font-variant-numeric: tabular-nums; }
.ui-table tbody td:first-child { font-weight: var(--g-fort); }
.ui-table tbody tr:hover { background: var(--action-pale); }
.ui-table td.ui-actions { text-align: right; white-space: nowrap; }
.ui-table td.ui-actions .btn + .btn { margin-left: var(--espace-2); }

/* .ui-rangee est un flex, et son alignement par defaut annule le text-align
   de la cellule qui l'entoure : sans cette regle la colonne d'actions cesse de
   former une ligne verticale nette. */
.ui-table td.ui-actions > .ui-actions-ligne {
	justify-content: flex-end;
	flex-wrap: nowrap;
	gap: var(--espace-2);
}


/* ---- Le tableau bascule en cartes empilees sur telephone ----
   Chaque cellule devient un bloc portant son libelle de colonne, repris de
   data-libelle en ::before. Le <thead> sort de l'ecran a la maniere de .ui-sr
   et JAMAIS par display:none : il porte les scope="col" dont les lecteurs
   d'ecran se servent pour annoncer chaque cellule.
   min-width est remis a zero, sans quoi les cartes deborderaient comme le
   tableau qu'elles remplacent. */
@media (max-width: 720px) {
	.ui-table--cartes { min-width: 0; display: block; width: 100%; }
	.ui-table--cartes thead {
		position: absolute;
		width: 1px;
		height: 1px;
		margin: -1px;
		padding: 0;
		overflow: hidden;
		clip-path: inset(50%);
		white-space: nowrap;
		border: 0;
	}
	.ui-table--cartes tbody { display: block; }

	/* La carte est une grille a deux colonnes, pas une pile : empilee, elle
	   rallongerait le defilement autant que la bascule en fait gagner. Les
	   valeurs sont courtes, deux par ligne tiennent sans tronquer. Le titre et
	   les actions gardent la pleine largeur. */
	.ui-table--cartes tbody tr {
		display: grid;
		grid-template-columns: 1fr 1fr;
		/* align-items: start est indispensable : par defaut une cellule de
		   grille s'etire a la hauteur de la plus haute de sa rangee, et chaque
		   rangee couterait la hauteur de son pire element. */
		align-items: start;
		padding: var(--espace-2) 0;
		border-bottom: 2px solid var(--trait);
	}
	.ui-table--cartes tbody tr:last-child { border-bottom: 0; }

	/* La cellule devient une ligne « libelle / valeur ». Le libelle vient de
	   data-libelle et non d'un texte duplique dans le gabarit : une colonne
	   renommee ne peut pas diverger de son libelle mobile.
	   La ligne est serree : a l'interligne du tableau de bureau, une carte de
	   neuf champs depasserait 300 px et annulerait ce que la bascule fait
	   gagner. */
	.ui-table--cartes tbody td {
		display: block;
		padding: 1px var(--espace-3);
		border-bottom: 0;

		font-size: var(--t-petit);
		line-height: 1.45;
	}
	/* Le libelle et sa valeur sur la meme ligne : empile au-dessus, chaque champ
	   couterait deux lignes pour rien. Le libelle n'a pas de largeur fixe, il
	   laisse le reste a la valeur, qui passe a la ligne si elle est longue. */
	.ui-table--cartes tbody td[data-libelle]::before {
		content: attr(data-libelle);
		/* L'espace de separation est une marge, pas une espace dans le content :
		   une espace en fin de chaine est reduite a rien par le moteur de rendu. */
		margin-right: 5px;
		/* L'etiquette dit ce qu'on lit : elle ne descend jamais sous le plancher
		   de 12 px, les capitales et le gras suffisent a la distinguer. */
		font-size: var(--t-mini);
		font-weight: var(--g-fort);
		text-transform: uppercase;
		letter-spacing: .3px;
		color: var(--doux);
	}

	/* Le libelle du produit est le titre de la carte : pleine largeur, en gras,
	   sans etiquette de colonne. Dans un tableau la colonne dit ce qu'on lit,
	   dans une carte c'est la premiere ligne.
	   grid-row le remonte en tete SANS toucher a l'ordre du DOM, qui reste celui
	   des colonnes et donc celui que lit un lecteur d'ecran. */
	.ui-table--cartes tbody td.ui-carte-titre {
		grid-row: 1;
		grid-column: 1 / -1;
		padding-top: 0;
		padding-bottom: 3px;
		font-size: var(--t-texte);
		font-weight: var(--g-fort);
		line-height: 1.25;
	}
	.ui-table--cartes tbody td.ui-carte-titre::before { content: none; }

	/* En carte, c'est le libelle du produit qui porte l'identite de la ligne :
	   garder le gras sur la premiere cellule en ferait le titre. */
	.ui-table--cartes tbody td:first-child { font-weight: var(--g-normal); }

	/* La cellule d'actions reprend toute la largeur de la carte, sans libelle et
	   alignee a gauche : c'est elle que le benevole cherche. */
	.ui-table--cartes tbody td.ui-actions {
		grid-column: 1 / -1;
		display: block;
		text-align: left;
		padding-top: var(--espace-2);
	}
	.ui-table--cartes tbody td.ui-actions::before { content: none; }
	.ui-table--cartes td.ui-actions > .ui-actions-ligne { justify-content: flex-start; }

	/* Cible tactile pleine, et seulement ici : la densite de .btn--petit
	   convient a la souris et se paie au pouce, a cote d'un changement de page. */
	.ui-table--cartes td.ui-actions .btn {
		min-height: var(--cible);
		min-width: var(--cible);
	}

	/* En cartes, le cadre n'a plus rien a faire defiler : l'ombre de bord droit
	   annoncerait une colonne cachee qui n'existe plus. */
	.ui-table-scroll:has(.ui-table--cartes) { overflow-x: visible; }
	.ui-table-scroll:has(.ui-table--cartes)::after { content: none; }
}

/* Valeur longue bornee dans sa cellule : lien de comptage, URL, jeton. Sans
   elle, la colonne pousse tout le tableau en defilement horizontal. Le texte
   complet reste accessible par l'attribut title et par le bouton de copie. */
.ui-tronque {
	display: inline-block;
	max-width: 22ch;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
	vertical-align: bottom;
}

.ui-table tfoot td {
	padding: 10px 12px;
	border-top: 2px solid var(--action);
	font-weight: var(--g-fort);
	background: var(--action-pale);
}
.ui-table tfoot td.n { text-align: right; font-variant-numeric: tabular-nums; }

.ui-table--dense { font-size: var(--t-petit); }
.ui-table--dense tbody td, .ui-table--dense thead th { padding: 6px 10px; }

/* Lignes d'etat operationnel : un magasin prevu qui n'a rien compte se voit du
   premier coup d'oeil dans la liste du jour J.
   rgba(138, 58, 18) est --avertissement #8A3A12, a tenir accorde avec lui. */
.ui-table tbody tr.is-silencieux { background: rgba(138, 58, 18, .08); }
.ui-table tbody tr.is-exclu td { color: var(--doux); text-decoration: line-through; }

/* ---- Liste de diagnostics ----
   Une liste et non un tableau : un tableau porte l'instruction reparatrice dans
   la derniere colonne, celle qui sort de l'ecran la premiere sur un telephone.
   Une entree est une ligne qui se replie, non un bloc empile : en flex, la
   largeur disponible decide, et il n'y a AUCUNE colonne, donc structurellement
   aucun defilement horizontal interne.
   Le liseré de gauche porte la gravite en plus du badge ; la couleur n'est
   jamais la seule information, le badge dit le mot. */
.ui-diag {
	list-style: none;
	margin: 0;
	padding: 0;
	/* Le cadre est porte par la LISTE et non par chaque entree : des cadres
	   empiles font du bruit la ou un bloc continu se balaie. */
	border: 1px solid var(--trait);
	border-radius: var(--rayon-petit);
	overflow: hidden;
}
.ui-diag-item {
	display: flex;
	align-items: baseline;
	flex-wrap: wrap;
	gap: var(--espace-2) var(--espace-3);
	border-bottom: 1px solid var(--trait);
	border-left: 4px solid var(--doux);
	padding: var(--espace-3) var(--espace-4);
	background: var(--carte);
}
/* overflow: hidden sur la liste coupe deja les angles : rien a arrondir ici. */
.ui-diag-item:last-child { border-bottom: 0; }
.ui-diag-item.is-critical { border-left-color: var(--erreur); }
.ui-diag-item.is-warn { border-left-color: var(--avertissement); }
/* EtatController range les echecs sur TROIS niveaux (critical, warn, info) et
   le gabarit compose « ui-diag-item is-{{ niveau }} » : les trois doivent avoir
   une regle, sinon le lisere reste gris sans que rien ne le signale. */
.ui-diag-item.is-info { border-left-color: var(--info); }

/* Le nom du controle est l'identifiant PARTAGE avec l'endpoint de supervision :
   le support le cherche litteralement, on ne le traduit pas. break-all parce
   qu'il n'a pas d'espace ou casser sur un ecran etroit. */
.ui-diag-nom { font-weight: var(--g-fort); font-size: var(--t-petit); word-break: break-all; }

/* Le detail porte la seule instruction reparatrice de la page : il est au meme
   niveau typographique que le nom du controle, c'est lui qu'on vient lire. */
.ui-diag-detail { flex: 1 1 16em; color: var(--texte); }

/* ---- Bloc replie de detail technique ----
   Replie et non supprime : le detail reste la reference du support, qui a besoin
   du nom exact du controle et de sa valeur. Un <details> natif le fait sans
   JavaScript, reste ouvrable au clavier et cherchable par Ctrl+F.
   .ui-details sert aux depliants en ligne d'un formulaire ; .ui-repli est le
   meme motif porte par un CADRE de panneau, pour une section entiere. */
.ui-repli {
	background: var(--carte);
	border: 1px solid var(--trait);
	border-radius: var(--rayon-grand);
	box-shadow: var(--ombre-forte);
	padding: var(--espace-3) var(--espace-4);
}
.ui-repli > summary {
	cursor: pointer;
	font-weight: var(--g-fort);
	/* Meme jeton que .ui-panel-titre, dont ce summary joue le role. */
	font-size: var(--t-petit);
	text-transform: uppercase;
	letter-spacing: .5px;
	color: var(--action);
	/* Cible tactile minimale de 44 px : ce depliant est le seul moyen
	   d'atteindre le detail sur telephone. */
	padding: var(--espace-2) 0;
	min-height: 44px;
	display: flex;
	align-items: center;
}
.ui-repli > summary:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }
.ui-repli[open] > summary {
	border-bottom: 1px solid var(--trait);
	margin-bottom: var(--espace-4);
}

/* ================================================================ *
 * 6. CARTES, PANNEAUX, CHIFFRES
 * ================================================================ */

.ui-card {
	background: var(--carte);
	border: 1px solid var(--trait);
	border-radius: var(--rayon);
	box-shadow: var(--ombre);
	padding: var(--espace-4);
}

/* Deux cartes ne se touchent jamais : le rythme de #contenu ne couvre que ses
   enfants directs, et deux cartes imbriquees dans une section garderaient
   l'ecart nul, bordures confondues. */
.ui-card + .ui-card,
.ui-panel + .ui-panel,
.ui-card + .ui-panel,
.ui-panel + .ui-card { margin-top: var(--espace-5); }
/* Le rythme de #contenu s'arrête au <form>. */
#contenu form > :is(.bloc, .g) + :is(.bloc, .g),
#contenu form > div:not([class]) > .bloc + .bloc { margin-top: var(--v-entre); }
/* Formulaire d'édition replié : sans enfant visible, il doublait l'écart entre deux blocs. */
#contenu > form:not(:has(> :not([hidden], input[type="hidden"], script, template, .ui-sentinelle))) { display: none; }
/* La sentinelle posee par ui.js ajoute une piste, donc un ecart, a la grille. */
#contenu.page > .ui-sentinelle + .barre-actions { margin-top: calc(-1px - var(--v-entre)); }
#contenu form > .ui-sentinelle + .barre-actions { margin-top: calc(var(--v-entre) - 1px); }
/* Dans une grille le gap espace deja : cette marge y decalerait la 2e carte
   d'une rangee, jamais la 1re. */
.ui-grille > .ui-card, .ui-grille > .ui-panel,
.ui-filtres > .ui-card, .ui-filtres > .ui-panel,
.ui-pile > .ui-card, .ui-pile > .ui-panel { margin-top: 0; }

/* ---- Liste de definitions : « libelle / valeur » ----
   Sans ces regles, <dd> garde le retrait natif de 40 px que tous les
   navigateurs posent, et chaque gabarit l'annule a la main.
   La grille en auto-fill repartit les couples sur la largeur reelle de la
   carte ; le rendu reste identique quand il n'y a qu'un ou deux couples. */
.ui-defs { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: var(--espace-3) var(--espace-5); margin: 0; }
/* Il faut les DEUX quand la valeur est une URL : min-width: 0 autorise la
   maille a retrecir sous la largeur de son contenu, overflow-wrap autorise le
   contenu lui-meme, insecable faute d'espace, a se couper. */
.ui-defs > div { min-width: 0; overflow-wrap: anywhere; }
.ui-defs dt { font-size: var(--t-mini); color: var(--doux); }
/* Sans taille declaree, la valeur herite du 16 px du body et se rend plus
   grosse que le dt qui la nomme. */
.ui-defs dd { margin: 0; font-size: var(--t-texte); font-weight: var(--g-fort); }

/* Les <dd> hors .ui-defs perdent aussi leur retrait natif : le kit ne fabrique
   jamais ce decalage. */
.ui-contenu dd { margin-left: 0; }

.ui-panel {
	background: var(--carte);
	border: 1px solid var(--trait);
	border-radius: var(--rayon-grand);
	box-shadow: var(--ombre-forte);
	padding: var(--espace-4) var(--espace-5);
}
/* Le rythme de #contenu s'arrete aux enfants de <main> : sans ceci, jauge et
   texte se touchent. */
.ui-panel > * + * { margin-top: var(--espace-4); }

/* Le dernier enfant ne porte pas d'espace exterieur : c'est le conteneur qui
   donne le padding, et l'addition des deux creuse un blanc en bas du bloc. */
.ui-panel > :last-child,
.ui-card > :last-child { margin-bottom: 0; }

/* Un titre de bloc coiffe son bloc : c'est la symetrie de la regle ci-dessus.
   Sans elle, la marge haute du titre s'ajoute au padding du panneau et met plus
   d'air au-dessus du titre qu'entre lui et ce qu'il annonce. */
.ui-panel > :first-child,
.ui-card > :first-child { margin-top: 0; }

/* Dans un formulaire, le rythme appartient au gap : .ui-form est un flex, ou
   gap et marge de l'item s'ADDITIONNENT, et un depliant se detacherait du
   double de ses voisins. */
.ui-form > .ui-details { margin-top: 0; }
/* Les trois selecteurs battent « .ui-mode-admin h2 » (0,1,1) : les gabarits
   ecrivent le titre en <h2> ou en <div>, le role prime sur la balise. */
.ui-panel-titre,
.ui-mode-admin .ui-panel-titre,
.ui-mode-public .ui-panel-titre {
	/* A --t-petit il passerait sous la taille du champ qu'il chapeaute. */
	font-size: var(--t-texte);
	font-weight: var(--g-fort);
	text-transform: uppercase;
	letter-spacing: .5px;
	color: var(--action);
	border-bottom: 1px solid var(--trait);
	padding-bottom: var(--espace-2);
	margin: 0 0 var(--espace-4);
}

/* ---- Motifs communs de rangee ---- */
.ui-rangee { display: flex; align-items: center; gap: var(--espace-3); flex-wrap: wrap; }
.ui-rangee--entre { justify-content: space-between; }

@media (max-width: 560px) {
	/* Une rangee de boutons s'empile comme une barre d'actions : sans cela, un
	   bouton secondaire au libelle long y fait presque le double du primaire. */
	.ui-rangee > .btn { flex: 1 1 100%; }

	/* Un en-tete de panneau passe en colonne sur telephone : le titre reprend
	   sa ligne entiere et l'action devient une cible pleine largeur. */
	.ui-rangee--entre { flex-direction: column; align-items: stretch; }
}

.ui-details { margin-top: var(--espace-4); }
/* Sans taille declaree, le resume herite du 16 px du body. */
.ui-details > summary {
	cursor: pointer;
	padding: var(--espace-2) 0;
	font-size: var(--t-fort);
	font-weight: var(--g-fort);
	color: var(--action);
}

/* Un titre de panneau repliable est un titre de panneau, pas un resume : le
   repli est un geste, il ne change rien a la hierarchie. .ui-details-titre
   declare « inherit », donc sans ces regles le titre prend la couleur et la
   taille du <summary> qui l'enveloppe.
   Elles portent sur le TITRE et non sur le summary, pour que le rang suive la
   balise reellement ecrite : poser la taille sur le summary promerait tous les
   <h3> au rang du <h2>, y compris ceux qui ne sont que des gestes. */
.ui-details--bloc > summary { padding-top: 0; }
/* La marge est redeclaree ici, ce n'est pas une redite : « .ui-details-titre »
   (0,1,0) PERD contre « .ui-mode-admin h2 » (0,1,1). Meme piege de specificite
   que .ui-panel-titre plus haut. */
.ui-details--bloc > summary > h2.ui-details-titre {
	font-size: var(--t-titre);
	font-weight: var(--g-fort);
	color: var(--texte);
	margin: 0;
}
.ui-details--bloc > summary > h3.ui-details-titre {
	font-size: var(--t-fort);
	font-weight: var(--g-fort);
	color: var(--texte);
	margin: 0;
}

/* Le contenu deplie s'ecarte de son propre titre : .ui-details ne pose qu'une
   marge externe, rien entre son summary et ce qu'il revele. La regle porte sur
   le voisinage et non sur un type de contenu. */
.ui-details > summary + * { margin-top: var(--espace-3); }
.ui-details > summary:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }

/* Variante « section repliable » : le summary porte un vrai titre de section.
   Le <h2> y reste une balise de titre pour que la section figure dans le plan
   du document, par lequel un lecteur d'ecran navigue ; il est mis en ligne et
   depouille de ses marges pour tenir sur la ligne du summary.
   Ne jamais s'en servir pour masquer une alerte : un bloc qui porte un defaut
   de configuration doit s'ouvrir de lui-meme. */
.ui-details--bloc > summary { display: flex; align-items: baseline; gap: var(--espace-3); }
.ui-details-titre { display: inline; margin: 0; font-size: inherit; color: inherit; }

/* Le chevron est redessine en ::after parce que display:flex sur le summary
   supprime le marqueur natif sous WebKit : sans lui, la section repliee se lit
   comme un titre mort. */
.ui-details--bloc > summary::marker,
.ui-details--bloc > summary::-webkit-details-marker { content: ''; display: none; }
.ui-details--bloc > summary::after {
	content: '▾';
	margin-left: auto;
	font-size: var(--t-mini);
	transition: transform .15s ease;
}
.ui-details--bloc[open] > summary::after { transform: rotate(180deg); }
/* Zone sensible : repliee et en dernier, son cadre dit ce qu'elle porte. */
.ui-details--danger { border-color: var(--erreur); }
/* Formulaire court : une ligne de saisie n'a pas besoin de toute la largeur. */
.ui-form--etroit { max-width: 480px; }
/* Rangee serree : badges et boutons d'un meme objet, pas deux blocs voisins. */
.ui-rangee--serree { gap: var(--espace-2); }
/* Carte d'annonce : son cadre dit qu'elle porte une bonne nouvelle. */
.ui-card--succes { border-color: var(--action); }
/* Respiration d'un bloc imbrique, ou le rythme de #contenu ne descend pas. */
.ui-bloc-espace { margin-top: var(--espace-4); }
/* Un lien ou un jeton long se coupe au lieu de deborder de sa rangee. */
.ui-code--long { word-break: break-all; flex: 1 1 320px; }
@media (prefers-reduced-motion: reduce) {
	.ui-details--bloc > summary::after { transition: none; }
}

.ui-separateur { border: 0; border-top: 1px solid var(--trait); margin: var(--espace-5) 0; }

/* Un <form> qui ne doit pas casser la ligne : bouton d'action dans une
   cellule de tableau, ou a cote d'un autre bouton. */
.ui-inline { display: inline; }

/* Barre de filtres en GET. Les champs s'alignent par le BAS pour que les
   etiquettes, de hauteurs inegales, ne les decalent pas entre eux.
   Des colonnes egales, et non un flex-wrap : celui-ci coupe la ligne ou la
   place manque, sans egard pour ce qui va ensemble, et les filtres partent en
   escalier. Meme raison que l'auto-fit de .ui-grille. */
.ui-filtres {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
	gap: var(--espace-3);
	align-items: end;
	margin-bottom: var(--espace-4);
}
/* La rangee de boutons d'une barre de filtres n'est pas un filtre : elle passe
   sous eux, sinon elle occupe une colonne et decale tous les autres. */
.ui-filtres > .ui-rangee,
.ui-filtres > .ui-btn-groupe { grid-column: 1 / -1; }
.ui-rangee + .ui-filtres,
.ui-field + .ui-filtres,
.ui-filtres + .ui-table-scroll { margin-top: var(--espace-4); }

/* La barre de boutons ne touche pas le bloc qu'elle clot : un bouton colle a la
   carte qui le precede se lit comme un element DE cette carte, alors qu'il
   engage tout l'ecran. Pendant de .ui-field + .ui-rangee, pour les blocs qui ne
   sont pas des champs. */
.ui-card + .ui-rangee,
.ui-panel + .ui-rangee,
.ui-card + .ui-form-actions,
.ui-panel + .ui-form-actions,
.ui-table-scroll + .ui-rangee { margin-top: var(--espace-5); }
.ui-filtres .ui-field { margin: 0; min-width: 10rem; }

/* <fieldset> depouille : le groupe reste une vraie structure pour un
   lecteur d'ecran, sans la bordure native qui jure avec les cartes. */
.ui-fieldset { border: 0; padding: 0; margin: 0 0 var(--espace-4); }
/* Meme cran que .ui-card > legend, qui joue le meme role : sans taille
   declaree, la legende herite du 16 px du body. */
.ui-fieldset legend { font-size: var(--t-petit); font-weight: var(--g-fort); padding: 0; margin-bottom: var(--espace-2); }

.dist-erreur { max-width: 18rem; font-size: var(--t-petit); word-break: break-word; }

/* La legende d'un groupe est un titre, y compris quand le <fieldset> porte
   .ui-card au lieu de .ui-fieldset : sans regle elle rend au style natif, avec
   moins de presence que les .ui-label qu'elle surplombe. Meme traitement que
   .ui-panel-titre, dont elle joue le role. */
.ui-card > legend {
	font-size: var(--t-petit);
	font-weight: var(--g-fort);
	text-transform: uppercase;
	letter-spacing: .5px;
	color: var(--action);
	padding: 0 6px;
	margin-left: -6px;
	/* Le fond opaque interrompt la bordure sous la legende : le padding et le
	   margin-left negatif menagent la place, mais sans fond le trait traverse
	   quand meme le texte. */
	background: var(--carte);
}

/* Une carte de formulaire suit la mesure de son contenu : sans plafond, elle
   s'etend au double de ses controles et laisse sa moitie droite vide. La regle
   porte sur le conteneur, qui est la cause, et non champ par champ. */
.ui-card:has(> .ui-field),
.ui-card:has(> legend) { max-width: 46rem; }

/* Sous-cartes de la personnalisation : elles rendent les groupes visibles dans
   un formulaire unique, sans multiplier les boutons d'enregistrement. */
.orgs-brand-section {
	margin-top: var(--espace-4);
	padding: var(--espace-4);
	border: 1px solid var(--trait);
	border-radius: var(--rayon);
	background: color-mix(in srgb, var(--fond) 55%, var(--carte));
}
.orgs-brand-section > :first-child { margin-top: 0; }
.orgs-brand-section > :last-child { margin-bottom: 0; }
.orgs-brand-section--preview { margin-top: var(--espace-5); }

/* Une carte qui ne contient qu'une action n'est pas une carte : sans cela, un
   bouton isole pese a l'oeil autant qu'un panneau entier. Le chrome tombe quand
   il n'y a ni champ, ni tableau, ni legende, ni titre, ni paragraphe. */
.ui-card:not(:has(.ui-field, .ui-table, legend, h2, h3, p)) {
	background: none;
	border: 0;
	box-shadow: none;
	padding: 0;
}

/* ---- Largeur des champs courts ----
   Posee par CLASSE et non par type : un input[type=number] peut porter une
   annee comme un identifiant a douze chiffres, seul le gabarit sait ce qu'il
   demande. */
.ui-champ--moyen  { max-width: 14rem; }

/* Une zone multiligne s'etale plus qu'un champ court, sans occuper toute la
   carte : sans plafond, le formulaire a deux bords droits differents.
   L'echappatoire totale reste nommee, textarea.ui-champ--large. */
textarea.ui-champ { max-width: 48rem; }
textarea.ui-champ--large { max-width: none; }

/* Les trois champs de la carte sont cibles par leur id, declares dans
   Collectes\Formulaire.php : Kit::champ() ecrit « class="ui-champ" » en dur, y
   glisser une classe produirait deux attributs class sur le meme <input>, dont
   seul le premier compte. Une latitude, une longitude et un zoom tiennent en
   quatre caracteres. */
#champ-carte-zoom { max-width: 7rem; }
#champ-carte-lat,
#champ-carte-lng { max-width: 11rem; }

/* ---- Le bandeau de collecte de l'accueil ----
   Un seul panneau domine l'ecran d'arrivee : celui qui dit ou en est la collecte
   et quoi faire ensuite. Le liseré suffit a le distinguer. */
.ui-panel--vedette { border-left: 4px solid var(--accent); }
.ui-panel--vedette .ui-panel-titre { border-bottom: 0; margin-bottom: var(--espace-1); }
.ui-panel--vedette .ui-panel-titre a { color: inherit; text-decoration: none; }
.ui-panel--vedette .ui-panel-titre a:hover { text-decoration: underline; }

/* ---- Les etapes du parcours ----
   Une ligne par jalon, la puce dit fait ou pas. En liste ORDONNEE parce que
   l'ordre porte du sens : on ne genere pas les liens avant les magasins. */
.ui-etapes {
	list-style: none;
	margin: var(--espace-4) 0 0;
	padding: 0;
	display: grid;
	gap: var(--espace-1);
	/* Une etape est une ligne de lecture, pas une bande : etiree, elle met le
	   nom et son detail aux deux bouts d'un metre de blanc. */
	max-width: 38rem;
}
/* La mise en page de l'etape vaut qu'elle soit cliquable ou non : l'accueil la
   rend en lien, la liste des magasins en simple constat. Seuls le survol et le
   focus restent reserves a la forme cliquable. */
.ui-etape > a,
.ui-etape > .ui-etape-nom {
	display: grid;
	grid-template-columns: 12rem minmax(0, 1fr);
	align-items: baseline;
	gap: var(--espace-3);
	padding: var(--espace-2) var(--espace-3);
	border-radius: var(--rayon-petit);
	text-decoration: none;
	color: inherit;
}
/* Sans lien, le nom et le detail sont freres directs du <li> : c'est lui qui
   porte la rangee, et le nom ne reprend ni le padding ni le gap. */
.ui-etape:not(:has(> a)) {
	display: grid;
	grid-template-columns: 12rem minmax(0, 1fr);
	align-items: baseline;
	gap: var(--espace-3);
	padding: var(--espace-2) var(--espace-3);
	border-radius: var(--rayon-petit);
}
.ui-etape:not(:has(> a)) > .ui-etape-nom { display: inline-block; padding: 0; }
.ui-etape > a:hover { background: var(--action-pale); }
.ui-etape > a:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }
/* Le nom de l'etape est un LIBELLE, pas une action : il prend la couleur du
   texte, sinon il herite de celle des liens de contenu et l'ecran d'arrivee
   offre autant de fausses actions que d'etapes.
   « inline-block » et pas « inline » sur la variante sans lien : min-width n'a
   aucun effet sur un element inline, et la colonne des valeurs ne tiendrait que
   par coincidence de largeur de texte. */
.ui-etape-nom { font-size: var(--t-texte); font-weight: var(--g-fort); min-width: 0; color: var(--texte); }
.ui-etape-nom::before {
	content: "○";
	display: inline-block;
	width: 1.25em;
	color: var(--doux);
}
.ui-etape.is-faite .ui-etape-nom::before { content: "●"; color: var(--action); }
.ui-etape.is-faite .ui-etape-nom { color: var(--action); }
.ui-etape-detail { color: var(--doux); font-size: var(--t-texte); }
@media (max-width: 480px) {
	.ui-etape > a, .ui-etape:not(:has(> a)) { grid-template-columns: 1fr; gap: 2px; }
	.ui-etape-detail { padding-left: 1.25em; }
}

/* La prochaine etape, et son bouton : la seule action forte de l'ecran. */
.ui-suite {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--espace-4);
	flex-wrap: wrap;
	margin-top: var(--espace-4);
	padding-top: var(--espace-4);
	border-top: 1px solid var(--trait);
}
/* Le pied conclut la liste des etapes : jamais plus gros qu'elle. */
.ui-suite-texte { margin: 0; font-size: var(--t-texte); }

/* Une seule echelle de bouton par pied de carte : le bouton de rappel s'aligne
   sur l'action principale du panneau. .btn--petit garde sa densite ailleurs,
   dans les lignes de tableau pour lesquelles il est concu. */
.ui-panel .ui-suite ~ p > .btn--petit {
	min-height: var(--cible);
	font-size: var(--t-texte);
	padding: 10px 16px;
}

.ui-chiffre { text-align: center; }
/* La tuile est dimensionnee pour des nombres, mais Calendrier::relatif() y met
   une date complete passe 24 h : la valeur retrecit quand elle s'allonge.
   « ch » et pas « px » : c'est la LONGUEUR du texte qui pose probleme, pas sa
   taille, et l'unite suit la police. */
.ui-chiffre b {
	display: block;
	font-size: clamp(30px, 4vw, 42px);
	font-weight: var(--g-fort);
	line-height: 1;
	color: var(--action);
	font-variant-numeric: tabular-nums;
}

.ui-chiffre span { display: block; font-size: var(--t-petit); font-weight: var(--g-fort); color: var(--doux); margin-top: 5px; }

/* La vedette se distingue par la couleur, pas par l'echelle : a taille double,
   des statistiques de meme rang se lisent comme un titre suivi de notes. La
   mise en avant passe par le degrade pose juste en dessous. */
.ui-chiffre--vedette b { font-size: var(--t-display-3); font-weight: var(--g-fort); }
/* Le chiffre le plus important de l'ecran garde une couleur de repli, jamais
   « transparent » : si le degrade ne peint pas (impression, forced-colors,
   moteur qui annonce background-clip sans l'appliquer au texte), le total
   devient invisible. C'est -webkit-text-fill-color, qui n'existe que la ou le
   degrade fonctionne, qui efface le repli. */
@supports (background-clip: text) or (-webkit-background-clip: text) {
	.ui-chiffre--vedette b {
		background: var(--degrade);
		-webkit-background-clip: text;
		background-clip: text;
		color: var(--action);
		-webkit-text-fill-color: transparent;
	}
}

/* auto-fit et jamais auto-fill : auto-fill cree des colonnes meme quand il n'y
   a rien a y mettre, et un champ orphelin se retrouve suivi de cellules vides.
   auto-fit laisse les colonnes remplies s'etaler sur la largeur disponible. */
.ui-grille { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: var(--espace-6); }
/* Tuiles de chiffres courts : en auto-fit comme la grille mere, jamais en
   auto-fill qui laisse des colonnes vides. */
.ui-grille--compacte { grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); }

/* ================================================================ *
 * 7. ALERTES, FLASH, TOAST
 * ================================================================ */

.ui-alerte {
	display: flex;
	/* Centre et non flex-start : le bouton fermer, a la cible tactile pleine,
	   est plus haut que le texte d'une alerte a une ligne et etire le bloc. */
	align-items: center;
	gap: var(--espace-4);
	/* Ces bandeaux portent les mises en garde de l'ecran, souvent sur plusieurs
	   lignes : serres contre leur bord, ils se lisent comme une note. */
	padding: 14px 18px;
	border: 1px solid var(--trait);
	border-left: 4px solid var(--doux);
	border-radius: var(--rayon-petit);
	font-size: var(--t-petit);
	line-height: 1.45;
	font-weight: var(--g-normal);
	background: var(--carte);
}
.ui-alerte + .ui-alerte { margin-top: var(--espace-2); }
/* Le texte occupe le reste de la ligne, sans quoi une alerte courte garde son
   bouton de fermeture colle au texte au lieu de le repousser au bord. */
.ui-alerte-texte { flex: 1 1 auto; min-width: 0; }

/* Le fond de l'info est bleute comme sa bordure : un fond d'une autre famille
   sous un liseré bleu se lit comme deux messages differents. */
.ui-alerte--info { background: var(--carte); border-color: var(--trait); border-left-color: var(--doux); color: var(--texte); }
.ui-alerte--succes { background: var(--action-pale); border-color: var(--action-clair); border-left-color: var(--action); color: var(--action); }
/* rgba(138, 58, 18) est --avertissement #8A3A12 : fond et bordure sont a tenir
   accordes avec lui, sans quoi le texte et son fond divergent. */
.ui-alerte--avertissement { background: rgba(138, 58, 18, .08); border-color: rgba(138, 58, 18, .22); border-left-color: var(--avertissement); color: var(--avertissement); }
/* color-mix fait suivre le fond a --erreur, donc au tenant. Repli sans
   color-mix : le fond de carte, neutre et jamais faux. */
.ui-alerte--erreur { background: var(--carte); border-color: var(--erreur); border-left-color: var(--erreur); color: var(--erreur); }
@supports (background: color-mix(in srgb, red 8%, white)) {
	.ui-alerte--erreur {
		background: color-mix(in srgb, var(--erreur) 8%, var(--carte));
		border-color: color-mix(in srgb, var(--erreur) 22%, var(--carte));
	}
}

/* La croix reste petite a l'oeil, sa zone cliquable fait la cible tactile
   complete : ratee au pouce, elle donne l'impression que la page ne repond
   pas. */
.ui-alerte-fermer {
	flex: 0 0 auto;
	width: var(--cible); height: var(--cible);
	margin: -8px -8px -8px 0;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	border: none;
	border-radius: 50%;
	background: transparent;
	color: inherit;
	font-size: var(--t-texte);
	font-weight: var(--g-fort);
	cursor: pointer;
	opacity: .7;
}
.ui-alerte-fermer:hover { opacity: 1; }

/* Ligne de statut : un etat normal, qui n'appelle aucune action. Ni fond, ni
   bordure, ni graisse, c'est ce qui la distingue d'une alerte. */
.ui-statut {
	display: flex;
	align-items: baseline;
	gap: var(--espace-2);
	margin: 0 0 var(--espace-3);
	font-size: var(--t-petit);
	line-height: 1.45;
	color: var(--doux);
}
.ui-statut::before {
	content: '';
	width: 6px; height: 6px;
	border-radius: 50%;
	background: currentColor;
	flex: 0 0 auto;
	transform: translateY(-1px);
}
.ui-statut--bien { color: var(--action); }
.ui-statut--attention { color: var(--avertissement); }

.ui-flash { margin: 0 0 var(--espace-5); }

/* Toast : fixe en haut, centre. */
.ui-toast-zone {
	position: fixed;
	top: 20px; left: 50%;
	transform: translateX(-50%);
	z-index: 9700;
	width: calc(100% - 32px);
	max-width: 520px;
	display: flex;
	flex-direction: column;
	gap: var(--espace-2);
	pointer-events: none;
}
.ui-toast {
	pointer-events: auto;
	display: flex;
	align-items: center;
	gap: var(--espace-4);
	/* Un message qui surgit doit respirer : il est lu en une seconde, de biais,
	   pendant qu'on fait autre chose. */
	padding: 16px 22px;
	line-height: 1.45;
	border-radius: var(--rayon-petit);
	font-size: var(--t-texte);
	font-weight: var(--g-fort);
	/* Jamais un blanc en dur : le fond est --action, surchargeable par tenant, et
	   Branding::sur() calcule le texte qui va dessus. */
	color: var(--sur-action);
	background: var(--action);
	box-shadow: 0 4px 16px rgba(0, 40, 35, .3);
	cursor: pointer;
	animation: ui-toast-in .3s ease;
}
.ui-toast--erreur { background: var(--erreur); }
.ui-toast--info { background: var(--info); }
.ui-toast--avertissement { background: var(--avertissement); }
.ui-toast.is-sortant { animation: ui-toast-out .3s ease forwards; }

@keyframes ui-toast-in { from { opacity: 0; transform: translateY(-16px); } to { opacity: 1; transform: none; } }
@keyframes ui-toast-out { to { opacity: 0; transform: translateY(-16px); } }

/* ================================================================ *
 * 8. BADGES
 * ================================================================ */

.ui-badge {
	display: inline-flex;
	/* Une pilule garde la largeur de son mot, y compris dans une rangee passee
	   en colonne sur telephone, ou stretch l'etirerait. */
	flex: 0 0 auto;
	align-self: flex-start;
	align-items: center;
	gap: 6px;
	padding: 3px 10px;
	border-radius: var(--rayon-pilule);
	font-size: var(--t-mini);
	font-weight: var(--g-fort);
	text-transform: uppercase;
	letter-spacing: .3px;
	white-space: nowrap;
	background: var(--carte);
	/* Avec --trait la pilule ne rendrait que 1,32:1 : le badge perdrait son
	   contour et ne se lirait plus comme une pastille d'etat. */
	border: 1px solid var(--bord-controle);
	color: var(--doux);
}
.ui-badge::before {
	content: '';
	width: 7px; height: 7px;
	border-radius: 50%;
	background: currentColor;
	flex: 0 0 auto;
}
.ui-badge.is-neutre { color: var(--doux); }
.ui-badge.is-info { color: var(--info); border-color: rgba(29, 78, 216, .3); }
.ui-badge.is-succes { color: var(--action); border-color: var(--action-clair); background: var(--action-pale); }
.ui-badge.is-erreur { color: var(--erreur); border-color: var(--erreur); }
@supports (border-color: color-mix(in srgb, red 30%, white)) {
	.ui-badge.is-erreur { border-color: color-mix(in srgb, var(--erreur) 30%, var(--carte)); }
}
/* rgba(138, 58, 18) suit --avertissement, comme .ui-etat.is-attente plus bas. */
.ui-badge.is-avertissement { color: var(--avertissement); border-color: rgba(138, 58, 18, .3); }
/* Le seul endroit ou la famille rose ecrit, et avec son encre : 9,78:1 au lieu
   de 2,34:1. Le rose reste en bord et en fond, ou il ne porte aucun mot. */
.ui-badge.is-live { color: var(--accent-encre); border-color: var(--accent); background: var(--accent-voile); }
@supports (border-color: color-mix(in srgb, red 35%, white)) {
	.ui-badge.is-live { border-color: color-mix(in srgb, var(--accent) 55%, var(--carte)); }
}
.ui-badge.is-live::before { animation: ui-pulse 1.4s ease-in-out infinite; }

@keyframes ui-pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: .35; transform: scale(.75); } }

/* ================================================================ *
 * 9. PAGINATION
 * ================================================================ */

.ui-pagination { display: flex; flex-wrap: wrap; gap: var(--espace-2); align-items: center; margin: var(--espace-5) 0 0; }
.ui-pagination a,
.ui-pagination span {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-width: var(--cible);
	min-height: var(--cible);
	padding: 0 12px;
	border: 1px solid var(--trait);
	border-radius: var(--rayon-petit);
	background: var(--carte);
	color: var(--texte);
	font-size: var(--t-texte);
	font-weight: var(--g-fort);
	text-decoration: none;
}
.ui-pagination a:hover { border-color: var(--focus); color: var(--action); }
.ui-pagination [aria-current="page"] { background: var(--action); border-color: var(--action); color: var(--sur-action); }
.ui-pagination .ui-pagination-ellipse { border-color: transparent; background: transparent; color: var(--doux); min-width: 0; padding: 0 4px; }
.ui-pagination .ui-pagination-inactif { color: var(--doux); }

/* ================================================================ *
 * 11. SEGMENTE, JAUGE
 * ================================================================ */

.ui-seg {
	display: inline-flex;
	background: var(--action-pale);
	border: 1px solid var(--trait);
	border-radius: var(--rayon-pilule);
	padding: 4px;
	gap: 2px;
	flex-wrap: wrap;
}
.ui-seg button {
	border: none;
	background: transparent;
	cursor: pointer;
	font-family: inherit;
	font-size: var(--t-petit);
	font-weight: var(--g-fort);
	/* --texte et non --doux : sur le fond --action-pale, --doux mesure 4,49:1 pour
	   un seuil de 4,5, et l'ecart s'aggrave des qu'un tenant eclaircit le
	   fond. */
	color: var(--texte);
	padding: 8px 16px;
	min-height: 36px;
	border-radius: var(--rayon-pilule);
	transition: color var(--transition), background var(--transition);
}
.ui-seg button:hover { color: var(--action); }
/* Le segment actif pose son libelle sur un fond PLEIN, jamais sur un degrade :
   dans la moitie claire d'un degrade le libelle tombe sous le seuil, et aucun
   outil ne le voit, backgroundColor valant transparent sur un fond degrade.
   Les trois selecteurs sont necessaires : les gabarits marquent l'etat actif
   par aria-pressed ou aria-current, .is-active n'est la que si un script pose
   la classe. */
.ui-seg button.is-active,
.ui-seg button[aria-pressed="true"],
/* L'ombre teintee suit l'accent du tenant, comme celle du bouton primaire. */
.ui-seg [aria-current="page"] { background: var(--action); color: var(--sur-action); box-shadow: 0 4px 14px rgba(15, 34, 41, .20); }
@supports (box-shadow: 0 4px 14px color-mix(in srgb, red 28%, transparent)) {
	.ui-seg [aria-current="page"] { box-shadow: 0 4px 14px color-mix(in srgb, var(--action) 28%, transparent); }
}
/* Le lien inactif d'un filtre par statut, equivalent en <a> de .ui-seg button
   ci-dessus : sur le fond --action-pale, --doux mesure 4,49:1 pour un seuil de
   4,5. Portee limitee a .ui-seg, ailleurs --discret est pose sur --carte. */
.ui-seg .btn--discret:not([aria-current]) { color: var(--texte); }

@media (max-width: 560px) {
	.ui-seg { display: flex; width: 100%; }
	.ui-seg button { flex: 1 1 auto; }
}

/* La jauge est un indicateur, pas une bande de fond : sans plafond, une
   collecte a 0 % etire un rail vide sur toute la largeur du panneau. Le
   plafond reprend la mesure de lecture de .ui-champ. */
.ui-jauge { height: 12px; max-width: 34rem; border-radius: var(--rayon-pilule); background: var(--action-pale); overflow: hidden; }
.ui-jauge > span {
	display: block;
	height: 100%;
	width: var(--valeur, 0%);
	border-radius: var(--rayon-pilule);
	background: var(--degrade);
	transition: width .8s ease;
}
.ui-jauge--mini { height: 7px; }
.ui-jauge-libelle { display: flex; justify-content: space-between; font-size: var(--t-mini); font-weight: var(--g-fort); margin-top: 6px; }

/* ================================================================ *
 * 12. ETATS : CHARGEMENT, VIDE, ERREUR
 * ================================================================ */

.ui-chargement {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: var(--espace-4);
	min-height: 50vh;
	color: var(--doux);
	font-weight: var(--g-fort);
}
/* La piste du spinner se voit, sinon seule la tete tourne dans le vide :
   --action-pale sur --fond donne 1,03:1. Elle prend le contour des controles,
   lisible sur les deux fonds. */
.ui-chargement::before {
	content: '';
	width: 40px; height: 40px;
	border: 3px solid var(--bord-controle);
	border-top-color: var(--accent);
	border-radius: 50%;
	animation: ui-spin .8s linear infinite;
}

/* Le bloc est borne et centre, comme la carte d'identification : un message
   etire sur toute la largeur se lit comme une bande vide. */
.ui-vide, .ui-erreur-page {
	text-align: center;
	padding: var(--espace-6) var(--espace-4);
	/* Avec --trait le cadre en tirets ne rendrait que 1,25:1 : rien ne
	   distinguerait l'etat vide d'une page ratee. */
	border: 1px dashed var(--bord-controle);
	border-radius: var(--rayon);
	background: var(--carte);
	max-width: 34rem;
	margin-inline: auto;
}
.ui-vide-icone { font-size: 34px; line-height: 1; display: block; margin-bottom: var(--espace-3); }
/* Le titre d'une page d'erreur a la presence d'un titre de page : a --t-fort il
   ne depasserait le corps que d'un pixel.
   A FAIRE hors CSS, dans kit/erreur-page.html.twig : rendre un h1 et non un h2
   quand le bloc occupe seul la page, un ecran sans h1 n'ayant pas de titre pour
   un lecteur d'ecran. */
.ui-vide h2, .ui-erreur-page h2 { font-size: var(--t-titre); margin: 0 0 var(--espace-3); color: var(--texte); }
.ui-vide p, .ui-erreur-page p { color: var(--doux); margin: 0 0 var(--espace-4); }
.ui-erreur-page { border-color: var(--erreur); }
.ui-erreur-page h2 { color: var(--erreur); }

/* ================================================================ *
 * 13. GABARIT ADMIN
 * ================================================================ */
.ui-mode-admin { min-height: 100vh; }

/* ---- La barre haute ----
   Noms de classes de la maquette, non renommes : .barre, .barre-marque,
   .barre-rech, .org-bouton, .pastille. */

.barre {
	position: sticky;
	top: 0;
	z-index: 60;
	display: flex;
	align-items: center;
	gap: var(--espace-4);
	height: var(--h-barre);
	padding: 0 var(--espace-4);
	background: var(--carte);
	border-bottom: 1px solid var(--trait);
}
.barre-marque {
	display: flex;
	align-items: center;
	gap: var(--espace-2);
	flex: none;
	text-decoration: none;
	color: inherit;
	min-width: 0;
}
.barre-marque svg { display: block; }
/* Le logo depose par un tenant est un <img>, jamais inline : il n'a traverse
   le nettoyeur qu'une fois, au depot. */
.barre-marque img { height: 30px; width: auto; }
/* Le logo porte ses propres couleurs : pas de « color », qui l'aplatirait en
   une teinte. Largeur libre, c'est un logotype large et non un carre. */
.barre-logo-defaut { height: 30px; width: auto; flex: 0 0 auto; }
/* Le nom du produit ou du tenant. Il tronque : un nom d'organisation a rallonge
   ne pousse pas la recherche hors de la barre. */
.barre-nom {
	font-family: var(--f-titre);
	font-weight: var(--g-fort);
	font-size: 16px;
	color: var(--action);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

/* .org-bouton, .barre-rech et .pastille : une seule definition, section 6
   de la maquette (« L'OSSATURE DE L'ECRAN »). */

/* La deconnexion reste un POST a jeton : un GET permettrait de deconnecter
   quelqu'un par une simple image dans un courriel. Elle vit en pied de menu. */
@media (max-width: 720px) {
	.barre { gap: var(--espace-2); padding: 0 var(--espace-3); }
	.org-bouton { display: none; }
}
@media (max-width: 480px) {
	.barre-nom { display: none; }
}

/* ---- LE CADRE ET LE MENU LATERAL, 228 px ------------------------------ */

.cadre {
	display: grid;
	grid-template-columns: var(--l-menu) minmax(0, 1fr);
	min-height: calc(100vh - var(--h-barre));
}
/* Menu vide (aucune organisation choisie) : masque en CSS, le <nav> est fige par chassis.golden. */
.cadre:not(:has(.menu-a)) { grid-template-columns: minmax(0, 1fr); }
.cadre:not(:has(.menu-a)) > .menu { display: none; }
.cadre:not(:has(.menu-a)) > .ui-contenu { max-width: 800px; margin-inline: auto; }
.menu {
	background: var(--action-fonce);
	padding: var(--espace-4) var(--espace-3);
	display: grid;
	gap: var(--espace-5);
	align-content: start;
}
@media (min-width: 1081px) {
	.menu {
		position: sticky;
		top: var(--h-barre);
		align-self: start;
		height: calc(100vh - var(--h-barre));
		overflow-y: auto;
	}
}

/* Le balisage des groupes est celui de Kit::nav(), fige par kit-ui.golden qui
   compare octet a octet : .ui-nav-groupe et .ui-nav-groupe-titre ne peuvent pas
   etre renommes, les deux ecritures partagent donc la meme regle. */
.menu-grp,
.menu .ui-nav-groupe { display: grid; gap: 2px; }
.menu-grp .label,
.menu .ui-nav-groupe-titre {
	font: var(--g-fort) var(--t-mini)/var(--lh-mini) var(--f-titre);
	letter-spacing: var(--ls-capitale);
	text-transform: uppercase;
	color: #fff;
	opacity: .62;
	padding: 0 var(--espace-3) var(--espace-1);
	margin: 0;
}
.menu-a,
.menu .ui-nav-groupe a {
	display: flex;
	align-items: center;
	gap: var(--espace-3);
	min-height: 44px;
	padding: var(--espace-3);
	border-radius: var(--r-s);
	color: var(--sur-fonce);
	text-decoration: none;
	cursor: pointer;
	font-family: var(--f-texte);
	font-size: var(--t-texte);
	font-weight: var(--g-normal);
	border: 0;
	background: transparent;
	text-align: left;
	width: 100%;
	/* Un libelle long ne casse pas la colonne, il tronque. */
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}
.menu-a:hover,
.menu .ui-nav-groupe a:hover { background: rgba(255, 255, 255, .09); color: #fff; }
/* Entree active : aplat plein de l'accent, encre claire dessus. */
.menu-a.on,
.menu .ui-nav-groupe a.is-actif {
	background: var(--accent);
	color: var(--sur-action, #fff);
	font-weight: var(--g-fort);
	box-shadow: none;
}
/* Les icones du menu sont un peu plus fortes que le texte qu'elles accompagnent.
   Le trait et le remplissage viennent de .ui-icone. */
.menu-a .ui-icone,
.menu .ui-nav-groupe a .ui-icone {
	width: var(--ic-fort);
	height: var(--ic-fort);
	opacity: .92;
}
.menu-a.on .ui-icone,
.menu .ui-nav-groupe a.is-actif .ui-icone { opacity: 1; }

/* Sous 1081 px le menu devient une rangee horizontale defilante, collee
   sous la barre. Le masque a droite dit qu'il reste des entrees. */
@media (max-width: 1080px) {
	/* grid-template-rows: auto 1fr, sinon le min-height du cadre etire la rangee
	   du menu sur les ecrans courts. */
	.cadre { grid-template-columns: 1fr; grid-template-rows: auto 1fr; }
	.menu {
		display: flex;
		overflow-x: auto;
		gap: var(--espace-2);
		padding: var(--espace-3);
		align-content: initial;
		align-items: center;
		scrollbar-width: none;
		-webkit-overflow-scrolling: touch;
		position: sticky;
		top: var(--h-barre);
		z-index: 50;
	}
	/* Le degrade ne parait que si .menu deborde reellement, classe posee par
	   indiquerMenuDefilable dans ui.js. */
	.menu.menu--defilable {
		-webkit-mask-image: linear-gradient(to right, #000 calc(100% - 32px), transparent);
		mask-image: linear-gradient(to right, #000 calc(100% - 32px), transparent);
	}
	.menu::-webkit-scrollbar { display: none; }
	.menu-grp,
	.menu .ui-nav-groupe { display: flex; gap: var(--espace-2); flex: none; }
	.menu-grp .label,
	.menu .ui-nav-groupe-titre { display: none; }
	.menu-grp + .menu-grp,
	.menu .ui-nav-groupe + .ui-nav-groupe {
		border-left: 1px solid rgba(255, 255, 255, .18);
		padding-left: var(--espace-3);
		margin-left: var(--espace-2);
	}
	.menu-a,
	.menu .ui-nav-groupe a { white-space: nowrap; flex: none; width: auto; }
}

/* ---- LA COLONNE DE CONTENU ------------------------------------------- */

/* Padding uniforme, comme la maquette. */
/* padding-top superieur aux autres cotes : le line-height du h1 mange une
   partie du padding en haut, la respiration reelle sous la barre etait
   plus courte que les 24px du token. */
.ui-contenu { padding: calc(var(--espace-5) + 12px) var(--espace-5) var(--espace-5); max-width: var(--largeur-contenu); width: 100%; }

/* Le supplement de 12px compense le line-height du h1 quand il ouvre la
   page. Un bouton .retour au-dessus mange deja cette respiration : le
   supplement s'ajoutait par-dessus et eloignait le bouton de la barre. */
.ui-contenu:has(> .page > .retour) { padding-top: var(--espace-5); }

/* Pose par ui.js : la barre reste dans le flux normal (non collee) tant que
   sa place naturelle n'est pas atteinte, pour ne jamais recouvrir la fin
   d'un formulaire plus haut que le viewport des l'arrivee sur l'ecran. */
.barre-actions.ui-pas-encore-au-fond { position: static; }

/* ---- La barre minimale de la page de refus ----
   .ui-barre ne sert que cette page, dont le balisage est fige octet a octet par
   tests/gardes-test.php : il est rendu deux fois, par
   templates/socle/erreur-admin.html.twig et par src/Frontiere/auth.php, et les
   deux sorties sont comparees. La renommer demanderait de toucher les deux.
   Elle porte la marque et un lien d'accueil, et rien d'autre : un menu complet
   sur un refus dirait ce qu'on vient de cacher. */
.ui-barre {
	display: flex;
	align-items: center;
	gap: var(--espace-4);
	height: var(--h-barre);
	padding: 0 var(--espace-4);
	background: var(--carte);
	border-bottom: 1px solid var(--trait);
}
.ui-barre-logo { display: flex; align-items: center; gap: var(--espace-2); min-width: 0; text-decoration: none; color: inherit; }
.ui-barre-logo-defaut { height: 30px; width: auto; flex: 0 0 auto; }
.ui-barre-marque {
	font-family: var(--f-titre);
	font-weight: var(--g-fort);
	font-size: 16px;
	color: var(--action);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

/* ---- Le rythme vertical, pose une fois pour toutes les pages ----
   « > * + * » et non une marge sur chaque composant : la marge appartient a la
   RELATION entre deux blocs, pas au bloc. Un composant qui porte sa marge basse
   en laisse une en trop quand il finit la page et se bat avec celle de son
   voisin ; le selecteur de proximite ne pose d'espace qu'ENTRE deux freres.
   Il vise <main id="contenu">, present dans les deux chassis. La page de
   comptage n'a pas ce conteneur et n'est donc pas touchee. */
#contenu > * + * { margin-top: var(--espace-5); }

/* Deux exceptions, justifiees par le SENS de la relation : une alerte parle du
   bloc qui la suit, un titre annonce ce qui vient dessous.
   Posees au niveau du composant, sans « #contenu > », pour valoir aussi dans une
   carte ou un panneau, comme .ui-field + .ui-field. */
.ui-alerte + *,
.ui-flash + * { margin-top: var(--espace-4); }
h2 + *, h3 + *, h4 + * { margin-top: var(--espace-3); }

/* Les memes, repetees sous « #contenu > », par specificite : « #contenu > * + * »
   porte un id (1,0,1) et « h2 + * » n'a que deux elements (0,0,2), donc les
   regles ci-dessus ne gagnent jamais en enfant direct. */
#contenu > .ui-alerte + *,
#contenu > .ui-flash + * { margin-top: var(--espace-4); }
#contenu > h2 + *, #contenu > h3 + *, #contenu > h4 + * { margin-top: var(--espace-3); }

/* Les titres ne cumulent pas leur marge propre avec celle du rythme. */
#contenu > h1 { margin-top: 0; }
/* Le chapo commente le titre de page, il ne s'en detache pas. */
#contenu > h1 + p { margin-top: var(--espace-3); }
#contenu > h2, #contenu > h3 { margin-bottom: 0; }

/* Le premier et le dernier enfant ne portent jamais d'espace exterieur : c'est
   le conteneur qui donne le padding de page, et les deux s'additionneraient. */
#contenu > :first-child { margin-top: 0; }
#contenu > :last-child { margin-bottom: 0; }

.ui-fil { font-size: var(--t-petit); color: var(--doux); margin: 0 0 var(--espace-4); }
/* Le fil reste discret : il situe, il n'appelle pas au clic. La specificite doit
   depasser celle de « .ui-contenu a », d'ou le selecteur complet. */
.ui-contenu .ui-fil a { color: var(--doux); }
.ui-contenu .ui-fil a:hover { color: var(--action); }
.ui-fil span[aria-current] { color: var(--texte); font-weight: var(--g-fort); }
.ui-fil-sep { margin: 0 6px; }

.ui-pied {
	/* Hors de la portee de « #contenu > * + * » : sa marge est a lui. */
	margin-top: var(--espace-6);
	padding: var(--espace-4);
	font-size: var(--t-mini);
	color: var(--doux);
	text-align: center;
}
.ui-pied a { color: var(--doux); }

/* ================================================================ *
 * 14. GABARIT PUBLIC (live, bilan)
 * ================================================================ */

.ui-mode-public .ui-contenu { margin: 0 auto; padding: 22px 18px 60px; }

.ui-entete-public {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 18px;
	flex-wrap: wrap;
	background: var(--carte);
	border: 1px solid var(--trait);
	border-radius: var(--rayon-grand);
	box-shadow: var(--ombre-forte);
	padding: 16px 20px;
	margin-bottom: var(--espace-5);
}
.ui-entete-gauche { display: flex; align-items: center; gap: 16px; min-width: 0; }
.ui-entete-logo { height: 52px; width: auto; display: block; flex: 0 0 auto; }

/* Meme logo bichrome que dans la barre haute : aucune couleur a poser. La
   largeur reste auto pour ne pas ecraser le logotype. */
.ui-entete-logo-defaut { width: auto; }
.ui-entete-titres { min-width: 0; }
.ui-entete-titres h1 { font-size: clamp(24px, 4.4vw, 38px); line-height: 1.05; margin: 0; }
.ui-entete-titres .ui-sous-titre { font-size: var(--t-texte); color: var(--doux); font-weight: var(--g-fort); margin-top: 3px; }
.ui-partenaires { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.ui-partenaires img { height: 38px; width: auto; }

@media (max-width: 560px) {
	.ui-entete-public { padding: 14px 16px; }
	.ui-entete-logo { height: 42px; }
	.ui-partenaires img { height: 30px; }
}

/* ================================================================ *
 * 15. UTILITAIRES MINIMAUX
 * ================================================================ */

.ui-pile { display: flex; flex-direction: column; gap: var(--espace-4); }

/* Messages d'une ligne d'import : sans ceci, puces natives et retrait de 40 px
   dans une cellule de tableau. */
.ui-messages { list-style: none; margin: 0; padding: 0; }
.ui-messages > li + li { margin-top: var(--espace-1); }
.ui-entre { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--espace-3); }

/* La taille est declaree avec la couleur : sans elle, un texte secondaire
   herite du 16 px du body et se rend plus gros que le corps qu'il commente. */
.ui-doux { color: var(--doux); font-size: var(--t-texte); }
/* Dans un tableau dense, c'est le tableau qui donne la mesure : a specificite
   egale .ui-doux gagne, etant ecrit plus loin, et un commentaire secondaire se
   rendrait plus gros que la donnee qu'il commente. */
.ui-table--dense .ui-doux { font-size: var(--t-petit); }

/* ================================================================ *
 * 16. MOUVEMENT REDUIT
 * ================================================================ */

/* Un utilisateur qui demande moins de mouvement n'en recoit aucun. Le spinner
   s'immobilise, c'est pourquoi .ui-chargement porte toujours un libelle. */
@media (prefers-reduced-motion: reduce) {
	*, *::before, *::after {
		animation-duration: .001ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: .001ms !important;
		scroll-behavior: auto !important;
	}
	.btn:hover { transform: none !important; }
}

/* ================================================================ *
 * 17. IMPRESSION
 * ================================================================ */

/* Base de l'export PDF du bilan : on imprime le contenu, pas l'interface. */
@media print {
	:root { --fond: #fff; --carte: #fff; --texte: #000; --doux: #444; --trait: #999; }
	body { background: #fff; color: #000; }
	.barre, .menu, .ui-barre, .ui-toast-zone,
	.ui-evitement, .ui-form-actions, .ui-pagination, .btn { display: none !important; }
	.cadre { display: block !important; }
	.ui-contenu { max-width: none; padding: 0; }
	.ui-card, .ui-panel { box-shadow: none; border-color: #999; break-inside: avoid; page-break-inside: avoid; }
	.ui-table { min-width: 0; font-size: 11px; }
	.ui-table-scroll { overflow: visible; }
	.ui-table thead { display: table-header-group; }
	.ui-table thead th { position: static; background: #eee; color: #000; }
	.ui-chiffre--vedette b { color: #000 !important; background: none !important; -webkit-text-fill-color: initial; }
	/* L'URL des liens n'est utile qu'en pied de page : ailleurs elle hache le
	   texte du bilan. */
	.ui-pied a::after { content: " (" attr(href) ")"; font-size: 10px; }
}

/* ====================================================================== *
 * .ui-auth : pages d'identification (connexion, mot de passe oublie,
 * reinitialisation, activation d'invitation).
 *
 * Elles n'ont ni barre, ni navigation, ni organisation courante : on n'est pas
 * encore authentifie. D'ou leur mise en page propre, une carte centree, plutot
 * que le gabarit d'administration.
 * ====================================================================== */

.ui-auth {
	/* min-height plutot que height : sur un petit ecran clavier ouvert, la carte
	   doit pouvoir defiler au lieu d'etre coupee. */
	min-height: 100vh;
	display: flex;
	flex-direction: column;
	justify-content: center;
	align-items: center;
	/* Gap a zero et espacement pose entre freres : un gap ici s'ajouterait au
	   padding haut de la carte, que l'oeil lit comme un seul blanc, et
	   decentrerait le titre. */
	gap: 0;
	padding: 24px 16px;
	padding-top: max(24px, env(safe-area-inset-top));
	padding-bottom: max(24px, env(safe-area-inset-bottom));
	background: var(--fond);
}

/* Le titre et le formulaire partagent la meme largeur, sinon le titre flotte
   au-dessus d'une carte plus etroite. */
.ui-auth > * {
	width: 100%;
	max-width: 400px;
}

.ui-auth > * + * { margin-top: var(--espace-4); }
.ui-auth > form { margin-top: var(--espace-2); }

/* Specificite (1,0,2) : gagne sur « #contenu > h1 { margin-top: 0 } » (1,0,1),
   qui neutralise la marge des titres pour ne pas cumuler avec le rythme des
   pages normales. Ici pas de rythme au-dessus, la marge doit rester. */
#contenu.ui-auth > h1 {
	margin: var(--espace-6) 0 0;
	font-size: var(--t-page);
	font-weight: var(--g-fort);
	color: var(--action);
	text-align: center;
}

/* La carte : c'est elle qui porte le padding de la page. */
.ui-auth > form {
	background: var(--carte);
	border: 1px solid var(--trait);
	border-radius: var(--rayon-grand, 20px);
	box-shadow: var(--ombre);
	padding: 24px 22px;
	display: flex;
	flex-direction: column;
	gap: 14px;
}

/* Le bouton occupe toute la largeur de la carte : c'est l'action unique de la
   page, elle n'a pas a etre cherchee. Ecart nettement marque, pas un supplement
   discret : le gap de 14px du formulaire suffit entre deux champs, l'action qui
   suit doit se lire comme un bloc different, pas comme un champ de plus. */
.ui-auth > form .btn {
	width: 100%;
	margin-top: var(--espace-5);
}

/* Confirmation collee a son mot de passe : elles forment une seule question
   en deux champs, le gap uniforme du formulaire les lisait comme deux
   questions separees. */
.ui-auth > form .ui-field:has(input[name="password2"]) {
	margin-top: calc(-1 * var(--espace-4) + var(--espace-2));
}

/* Liens secondaires (mot de passe oublie, retour). Le gabarit les place DANS le
   formulaire, sous le bouton (p.liens) : cibler .ui-auth > p seul ne les
   atteindrait jamais. */
.ui-auth .liens,
.ui-auth > nav,
.ui-auth > p {
	margin: 0;
	text-align: center;
	font-size: var(--t-texte);
}

/* Trait de separation entre l'action principale et les recours.
   Les deux selecteurs sont necessaires : les ecrans a formulaire portent le
   recours DANS le <form>, ceux qui n'en ont pas en enfant direct de .ui-auth. */
.ui-auth > form .liens,
.ui-auth > .liens {
	margin-top: 2px;
	padding-top: 14px;
	border-top: 1px solid var(--trait);
}

/* Plusieurs liens : ils s'alignent au lieu de se coller. */
.ui-auth .liens a + a { margin-left: 14px; }

.ui-auth a {
	color: var(--action);
	text-decoration: none;
	border-bottom: 1px solid transparent;
	transition: border-color var(--transition, .15s ease);
}

.ui-auth a:hover,
.ui-auth a:focus-visible {
	border-bottom-color: currentColor;
}

/* Le selecteur porte sur .ui-flash et non sur .ui-alerte : colleta_ui_flash()
   enveloppe TOUJOURS ses alertes dans un <div class="ui-flash">, donc l'enfant
   direct de .ui-auth est l'enveloppe et jamais l'alerte. */
.ui-auth > .ui-flash {
	margin: 0;
}

@media (max-width: 480px) {
	.ui-auth > form { padding: 20px 16px; }
	.ui-auth > h1 { font-size: var(--t-titre); }
}

/* Marque, au-dessus du titre de la page d'identification. Declaration unique :
   deux blocs de meme selecteur se contrediraient en silence. */
.ui-auth-marque {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--espace-3);
	/* Le bloc de marque respire sous le titre. */
	margin: 0 0 var(--espace-3);
	font-size: var(--t-petit);
	font-weight: var(--g-fort);
	letter-spacing: .14em;
	text-transform: uppercase;
	/* Le teal, comme le reste de la page : --accent n'y rend que 2,34:1.
	   A rouvrir quand le logo definitif tranchera la couleur. */
	color: var(--action);
	text-align: center;
}

/* Le logo est un LOGOTYPE large qui porte deja le mot : hauteur posee, largeur
   libre. Le SVG injecte en ligne herite de la couleur du <p> par currentColor,
   donc il suit la surcharge du tenant sans regle propre. */
.ui-auth-logo {
	width: auto;
	height: 40px;
	max-width: 100%;
	display: block;
}

/* ====================================================================== *
 * Champ image : apercu, depot, suppression.
 * ====================================================================== */

.ui-upload-corps {
	display: flex;
	gap: 14px;
	align-items: flex-start;
}

/* Apercu. Le damier rend visible la transparence d'un PNG et montre qu'un logo
   blanc sur fond blanc sera invisible. */
.ui-upload-apercu {
	position: relative;
	flex: 0 0 104px;
	width: 104px;
	height: 104px;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 8px;
	border: 1px solid var(--trait);
	border-radius: var(--rayon-petit, 10px);
	background-color: var(--carte);
	background-image:
		linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%),
		linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%);
	background-size: 14px 14px;
	background-position: 0 0, 7px 7px;
}

.ui-upload-apercu img {
	max-width: 100%;
	max-height: 100%;
	object-fit: contain;
}

.ui-upload-vide {
	font-size: var(--t-mini);
	color: var(--doux);
	text-align: center;
}

/* La croix est posee SUR l'apercu : on supprime la ou on voit ce qu'on
   supprime. Elle n'existe que s'il y a une image. */
.ui-upload-croix {
	position: absolute;
	top: -8px;
	right: -8px;
	width: 26px;
	height: 26px;
	display: none;
	align-items: center;
	justify-content: center;
	padding: 0;
	border: 1px solid var(--trait);
	border-radius: 50%;
	background: var(--carte);
	color: var(--doux);
	font-size: var(--t-fort);
	line-height: 1;
	cursor: pointer;
	box-shadow: var(--ombre);
	transition: background var(--transition, .15s ease), color var(--transition, .15s ease);
}

.ui-upload.has-image .ui-upload-croix { display: flex; }

.ui-upload-croix:hover,
.ui-upload-croix:focus-visible {
	background: var(--erreur, #C0264B);
	border-color: transparent;
	color: #fff;
}

.ui-upload-actions {
	flex: 1;
	min-width: 0;
	display: flex;
	flex-direction: column;
	gap: 8px;
}

/* La zone de depot est un <label> : cliquer ouvre le selecteur de fichier sans
   JavaScript. Le glisser-deposer n'est qu'un supplement. */
.ui-upload-zone {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 2px;
	padding: 18px 12px;
	border: 2px dashed var(--trait);
	border-radius: var(--rayon-petit, 10px);
	background: var(--fond);
	text-align: center;
	cursor: pointer;
	transition: border-color var(--transition, .15s ease), background var(--transition, .15s ease);
}

.ui-upload-zone:hover,
.ui-upload-zone.is-survol {
	border-color: var(--accent);
	background: var(--action-pale);
}

.ui-upload-zone strong { font-size: var(--t-texte); }
.ui-upload-zone span   { font-size: var(--t-petit); color: var(--doux); }
.ui-upload-zone small  { font-size: var(--t-mini); color: var(--doux); }

/* Une image existe deja : la grande zone se compacte en un bouton
   « Remplacer ». */
.ui-upload.has-image .ui-upload-zone {
	flex-direction: row;
	gap: 8px;
	padding: 10px 14px;
	border-style: solid;
	border-width: 1px;
	background: var(--carte);
}

.ui-upload.has-image .ui-upload-zone span,
.ui-upload.has-image .ui-upload-zone small { display: none; }

/* L'input reste focusable au clavier bien que masque : display:none le
   retirerait de la navigation au clavier. */
.ui-upload-input {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip: rect(0 0 0 0);
	white-space: nowrap;
	border: 0;
}

.ui-upload-input:focus-visible + .ui-upload-zone {
	outline: 3px solid var(--focus, var(--action));
	outline-offset: 2px;
}

/* Adresse web : cas minoritaire, donc replie. */
.ui-upload-externe > summary {
	font-size: var(--t-mini);
	color: var(--doux);
	cursor: pointer;
	padding: 2px 0;
	list-style: none;
}

.ui-upload-externe > summary::-webkit-details-marker { display: none; }

.ui-upload-externe > summary::before {
	content: "+ ";
	font-weight: var(--g-fort);
}

.ui-upload-externe[open] > summary::before { content: "− "; }

.ui-upload-externe > summary:hover { color: var(--action); }
.ui-upload-externe > .ui-input { margin-top: 6px; }

@media (max-width: 560px) {
	.ui-upload-corps { flex-direction: column; }
	.ui-upload-apercu { align-self: flex-start; }
}

/* ================================================================ *
 * 18. LISTE A ZONES ET PASTILLES D'ETAT
 *
 * Deux composants qui remplacent le tableau la ou le tableau est le mauvais
 * objet. Le budget de densite est une exigence du produit : une ligne vise 56 a
 * 64 px sur bureau et 90 a 110 px sur telephone. Tout ce qui suit sert ce
 * budget, y compris les valeurs en dur (interligne serre, pastilles compactes).
 * ================================================================ */

.ui-liste {
	list-style: none;
	margin: 0;
	padding: 0;
	border: 1px solid var(--trait);
	border-radius: var(--rayon-petit);
	background: var(--carte);
	/* overflow: hidden borne les coins arrondis sur la premiere et la derniere
	   ligne sans leur poser un rayon individuel. */
	overflow: hidden;
}

/* Le filet separe, il n'entoure pas : une bordure par ligne doublerait le trait
   entre deux voisines. */
.ui-liste-ligne + .ui-liste-ligne { border-top: 1px solid var(--trait); }
.ui-liste-ligne:hover { background: var(--action-pale); }

/* La grille explicite a trois zones, coeur du composant.
   « minmax(0, 1fr) » et JAMAIS « 1fr » sur l'identite : la taille minimale
   automatique d'une maille est la largeur de son CONTENU, donc un nom long ou
   une URL sans espace pousserait la grille au-dela de son cadre au lieu de se
   tronquer. Les deux autres zones sont en « auto ». */
.ui-liste-corps {
	display: grid;
	grid-template-columns: minmax(0, 1fr) auto auto;
	align-items: center;
	gap: var(--espace-3);
	/* Padding resserre, et non l'--espace-4 du reste du produit : c'est lui qui
	   tient le budget de 56 a 64 px avec une identite sur deux lignes. */
	padding: 10px var(--espace-3);
	min-height: 56px;
}

/* min-width: 0 sur chaque enfant, meme raison : sans lui, text-overflow ne peut
   jamais s'appliquer, la maille ne descendant pas sous la largeur de son
   texte. */
.ui-liste-identite,
.ui-liste-meta,
.ui-liste-action { min-width: 0; }

.ui-liste-identite { display: flex; flex-direction: column; gap: 1px; }

.ui-liste-titre {
	font-size: var(--t-texte);
	font-weight: var(--g-fort);
	line-height: 1.3;
	/* Le titre se tronque plutot que de casser la ligne : deux lignes de titre
	   feraient sauter le budget de densite. */
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.ui-liste-titre a { text-decoration: none; }
.ui-liste-titre a:hover { text-decoration: underline; }

.ui-liste-sous-titre {
	font-size: var(--t-petit);
	color: var(--doux);
	line-height: 1.3;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.ui-liste-meta {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--espace-2);
	justify-content: flex-end;
}

/* Les valeurs courtes de la zone meta ne sont pas des badges : elles n'en
   portent donc pas le cadre. */
.ui-liste-valeur { font-size: var(--t-petit); color: var(--doux); white-space: nowrap; }
.ui-liste-valeur b { color: var(--texte); font-variant-numeric: tabular-nums; }

.ui-liste-action { display: flex; align-items: center; gap: var(--espace-2); }

/* Ligne entiere cliquable : seul un titre en .ui-liste-etire s'etire (::after
   sur toute la ligne). Une ligne a boutons n'est pas etiree, jamais deux
   destinations sur la meme zone. */
.ui-liste-ligne { position: relative; }
.ui-liste-titre a, .ui-liste-action .btn { position: relative; z-index: 1; }
.ui-liste-titre a.ui-liste-etire { position: static; }
.ui-liste-etire::after { content: ''; position: absolute; inset: 0; }
.ui-liste-ligne:has(.ui-liste-etire) { cursor: pointer; }
.ui-liste-ligne:has(.ui-liste-etire):hover .ui-liste-etire { text-decoration: underline; }
/* Anneau en inset : l'overflow:hidden de .ui-liste rognerait un outline. */
.ui-liste-etire:focus-visible { outline: none; }
.ui-liste-ligne:has(.ui-liste-etire:focus-visible) { box-shadow: inset 0 0 0 3px var(--focus); }
.ui-liste-action > .ui-icone { color: var(--doux); }

/* Le role et le chevron forment un bloc groupe : la grille 1fr/auto/auto
   laisse le nom absorber le reste et garde le couple colle a droite. */
.ui-liste--choix .ui-liste-meta { justify-content: flex-end; }
.ui-liste--choix .ui-liste-corps { gap: var(--espace-4); }
@media (max-width: 720px) {
	.ui-liste--choix .ui-liste-corps { row-gap: 2px; }
	.ui-liste--choix .ui-liste-meta { grid-column: 1; grid-row: 2; justify-content: flex-start; }
	.ui-liste--choix .ui-liste-action { grid-column: 2; grid-row: 1 / span 2; }
}

/* ---- Le depliant par ligne ----
   Il porte ce qui serait hors ecran : lien long, telephone, email. Un <details>
   FERME garde son contenu dans le DOM, donc document.getElementById() le trouve
   et les boutons de copie de ui.js fonctionnent sans qu'il soit ouvert. */
.ui-liste-depliant { border-top: 1px dashed var(--trait); }

.ui-liste-depliant > summary {
	/* Cible tactile pleine : c'est le seul moyen d'atteindre les donnees
	   reléguées, la rater au pouce est un cul-de-sac. */
	min-height: var(--cible);
	display: flex;
	align-items: center;
	padding: 0 var(--espace-3);
	font-size: var(--t-mini);
	font-weight: var(--g-fort);
	color: var(--doux);
	cursor: pointer;
	list-style: none;
	text-transform: uppercase;
	letter-spacing: .3px;
}
.ui-liste-depliant > summary::-webkit-details-marker { display: none; }
.ui-liste-depliant > summary::before { content: "+ "; margin-right: 4px; font-size: var(--t-petit); }
.ui-liste-depliant[open] > summary::before { content: "− "; }
.ui-liste-depliant > summary:hover { color: var(--action); }

.ui-liste-depliant-corps {
	padding: var(--espace-2) var(--espace-3) var(--espace-3);
	font-size: var(--t-petit);
	display: grid;
	gap: var(--espace-2);
}
/* overflow-wrap: anywhere, et pas seulement min-width: 0 : un lien long est
   une chaine SANS espace, donc insecable par defaut. Il faut les deux, et
   c'est le second qui manque toujours. Meme correction que .ui-defs. */
.ui-liste-depliant-corps > div { min-width: 0; overflow-wrap: anywhere; }
.ui-liste-depliant-corps dt {
	font-size: var(--t-mini);
	color: var(--doux);
	text-transform: uppercase;
	letter-spacing: .3px;
	font-weight: var(--g-fort);
}

/* ---- TELEPHONE ------------------------------------------------------
 *
 * La ligne passe en pile : identite en haut, meta dessous, action en
 * pleine largeur. Le budget vise 90 a 110 px, contre 227 px mesures sur
 * les cartes de /admin/collectes/ a 390 px. */
@media (max-width: 720px) {
	.ui-liste-corps {
		grid-template-columns: minmax(0, 1fr);
		align-items: stretch;
		gap: var(--espace-2);
		padding: var(--espace-3);
	}
	/* La meta revient a gauche : alignee a droite sous un titre aligne a
	   gauche, elle formait une diagonale que l'oeil doit rattraper. */
	.ui-liste-meta { justify-content: flex-start; }
	/* L'action prend la pleine largeur et la cible tactile pleine : c'est
	   elle qu'on vient chercher, et .btn--petit descend a 36 px, sous le
	   plancher de 44. */
	.ui-liste-action > .btn { flex: 1 1 auto; min-height: var(--cible); }
}

/* ---- PASTILLES D'ETAT -----------------------------------------------
 * .ui-etat et non .ui-pastille : ce nom designe deja le selecteur de
 * couleur du branding. Plus compact qu'un .ui-badge : un badge dit le
 * statut d'un objet, une pastille dit un fait parmi plusieurs. */
.ui-etats { display: inline-flex; flex-wrap: wrap; gap: 4px; align-items: center; min-width: 0; }

.ui-etat {
	display: inline-flex;
	align-items: center;
	gap: 4px;
	padding: 2px 8px;
	border-radius: var(--rayon-pilule);
	border: 1px solid var(--bord-controle);
	background: var(--carte);
	color: var(--doux);
	font-size: var(--t-mini);
	font-weight: var(--g-fort);
	line-height: 1.5;
	/* nowrap EST STRUCTUREL : « téléphone non mobile » casse en deux lignes
	   au milieu d'une grappe, et toute la ligne de liste gonfle avec lui. */
	white-space: nowrap;
}
.ui-etat::before {
	content: '';
	width: 6px; height: 6px;
	border-radius: 50%;
	background: currentColor;
	flex: 0 0 auto;
}
.ui-etat.is-fait { color: var(--action); border-color: var(--action-clair); background: var(--action-pale); }
.ui-etat.is-attente { color: var(--avertissement); border-color: rgba(138, 58, 18, .3); }
.ui-etat.is-probleme { color: var(--erreur); border-color: var(--erreur); }
@supports (border-color: color-mix(in srgb, red 30%, white)) {
	.ui-etat.is-probleme { border-color: color-mix(in srgb, var(--erreur) 30%, var(--carte)); }
}
.ui-etat.is-neutre { color: var(--doux); }

/* A la souris, le resume du depliant n'a pas besoin de la cible tactile de
 * 44 px : c'est une cible precise. Elle est remise sous 720 px, ou le
 * depliant est le seul acces aux donnees reléguées. */
@media (min-width: 721px) {
	.ui-liste-depliant > summary { min-height: 0; padding-top: 4px; padding-bottom: 4px; }
}

/* Sur telephone, les valeurs courtes passent en ligne plutot que de
 * s'empiler chacune sur la sienne. Le separateur est DECORATIF, donc pose
 * en CSS (::before) et non dans le gabarit, sinon un lecteur d'ecran le lit
 * comme un mot de plus. */
@media (max-width: 720px) {
	.ui-liste-meta { gap: var(--espace-2); row-gap: 2px; }
	.ui-liste-valeur + .ui-liste-valeur::before {
		content: "·";
		margin-right: var(--espace-2);
		color: var(--trait);
	}
}

/* Sur telephone, le bouton « Ouvrir » ne prend plus toute la largeur : le
 * titre de la carte est deja un lien vers la meme destination, un bouton
 * pleine largeur le double pour rien. La regle « flex: 1 1 auto » d'au-dessus
 * ne s'applique donc pas ici, ou la ligne est elle-meme cliquable. */
/* Sur telephone, l'action reste sur la ligne du titre plutot que sa propre
 * bande : les cibles tactiles de 44 px ne se rognent pas, il faut donc
 * economiser une bande plutot que comprimer une cible. */
@media (max-width: 720px) {
	.ui-liste-corps {
		grid-template-columns: minmax(0, 1fr) auto;
		align-items: center;
	}
	/* La meta passe SOUS le couple titre/action, sur les deux colonnes. */
	.ui-liste-meta { grid-column: 1 / -1; justify-content: flex-start; }
	/* Largeur naturelle : un bouton etale sur toute la largeur doublait le
	   titre, qui est deja un lien vers la meme destination. */
	.ui-liste-action > .btn { flex: 0 0 auto; }
	/* Le resume du depliant garde sa cible de 44 px, sans le padding
	   vertical qui s'y ajoutait. */
	.ui-liste-depliant > summary { padding-top: 0; padding-bottom: 0; }
}

/* ================================================================ *
 * 20. ICONES D'INTERFACE
 * SVG inline uniquement, jamais une police ni un CDN (poids sur la 4G d'un
 * magasin). Dessin dans img/icone/icones.svg, servi via kit_icone().
 * ================================================================ */

/* LA TAILLE EST EN em ET NON EN px : l'icone suit alors la taille du texte
   qu'elle accompagne, donc --t-petit dans un bouton petit et --t-texte
   ailleurs, sans une regle par contexte. flex-shrink: 0 l'empeche d'etre
   ecrasee par un libelle long, ce qui la deformerait.
   vertical-align: -.125em rattrape l'alignement optique : une boite de
   texte n'est pas centree sur sa ligne de base, et une icone posee dessus
   sans correction flotte visiblement trop haut. */
/* LE TRAIT ET LE REMPLISSAGE VIVENT ICI, ET NULLE PART AILLEURS. Portes par
   l'enveloppe du sprite, ils resolvaient « currentColor » contre l'encre du
   SPRITE, qui vit hors de l'hote : la loupe de la barre sortait en cercle
   noir plein. .ui-icone est l'element qui porte le <use>, donc le seul
   porteur qui suive la couleur de son contexte. */
.ui-icone {
	width: 1.2em;
	height: 1.2em;
	flex: 0 0 auto;
	vertical-align: -.125em;
	fill: none;
	stroke: currentColor;
	/* Le trait ne s'epaissit pas quand l'icone grandit : sans cette ligne,
	   une icone de 34 px porterait un trait de 2,5 px et jurerait a cote
	   d'une icone de 16 px. */
	vector-effect: non-scaling-stroke;
}

/* Dans un bouton, une pastille ou un badge, l'icone precede son libelle et
   l'espacement est porte par le gap du conteneur, deja pose plus haut. */
.btn > .ui-icone { width: 1.1em; height: 1.1em; }

/* LE MENU NE PASSE PLUS PAR UN MASQUE (22 septembre 2026). Le chassis porte
   desormais le balisage de la maquette et pose ses icones en <use> vers le
   sprite, comme partout ailleurs. Les dix data-URI du menu et de la barre
   sont partis avec les regles qui les lisaient. */

/* ---- LES ALERTES ---------------------------------------------------
 * kit/alerte.html.twig est fige octet a octet, le masque prend donc la
 * couleur du texte plutot qu'une classe. Icone decorative : le role ARIA
 * (alert/status) porte deja l'urgence pour les lecteurs d'ecran. */
.ui-alerte::before {
	content: '';
	flex: 0 0 auto;
	width: 17px;
	height: 17px;
	/* Plus de margin-top manuel : .ui-alerte centre desormais ses enfants
	   (align-items: center), le flex fait le calage lui-meme. */
	background-color: currentColor;
	-webkit-mask-position: center;
	mask-position: center;
	-webkit-mask-repeat: no-repeat;
	mask-repeat: no-repeat;
	-webkit-mask-size: 17px 17px;
	mask-size: 17px 17px;
	-webkit-mask-image: var(--ic-info);
	mask-image: var(--ic-info);
}
.ui-alerte--succes::before { -webkit-mask-image: var(--ic-succes); mask-image: var(--ic-succes); }
.ui-alerte--erreur::before { -webkit-mask-image: var(--ic-erreur); mask-image: var(--ic-erreur); }
.ui-alerte--avertissement::before { -webkit-mask-image: var(--ic-avertissement); mask-image: var(--ic-avertissement); }

/* ---- LES PASTILLES D'ETAT ------------------------------------------
 * Un glyphe plutot qu'un simple disque colore, pour porter le sens par la
 * forme (un daltonien ne distingue pas la couleur seule). Ces regles
 * redefinissent .ui-etat::before pose plus haut, d'ou le width/height repose. */
.ui-etat.is-fait::before,
.ui-etat.is-attente::before,
.ui-etat.is-probleme::before {
	width: 13px;
	height: 13px;
	border-radius: 0;
	background-color: currentColor;
	-webkit-mask-position: center;
	mask-position: center;
	-webkit-mask-repeat: no-repeat;
	mask-repeat: no-repeat;
	-webkit-mask-size: 13px 13px;
	mask-size: 13px 13px;
}
.ui-etat.is-fait::before { -webkit-mask-image: var(--ic-succes); mask-image: var(--ic-succes); }
.ui-etat.is-attente::before { -webkit-mask-image: var(--ic-attente); mask-image: var(--ic-attente); }
.ui-etat.is-probleme::before { -webkit-mask-image: var(--ic-avertissement); mask-image: var(--ic-avertissement); }

/* La fleche des liens externes (nav.html.twig, fige) devient un dessin :
   le caractere « ↗ » rend differemment selon la police installee. Le
   « (nouvel onglet) » en .ui-sr reste, il porte le sens pour un lecteur
   d'ecran. */
.menu .ui-nav-groupe a > span[aria-hidden="true"] {
	display: inline-block;
	width: 13px;
	height: 13px;
	/* La fleche d'origine est mangee par une couleur transparente et une
	   taille de police nulle : « display:none » sur le <span> retirerait
	   aussi le masque, qui est porte par ce meme element. */
	font-size: 0;
	color: transparent;
	vertical-align: -.05em;
	background-color: currentColor;
	-webkit-mask-position: center;
	mask-position: center;
	-webkit-mask-repeat: no-repeat;
	mask-repeat: no-repeat;
	-webkit-mask-size: 13px 13px;
	mask-size: 13px 13px;
	-webkit-mask-image: var(--ic-externe);
	mask-image: var(--ic-externe);
	opacity: .7;
}

/* ---- LES BOUTONS D'ACTION ------------------------------------------
 * En masque, pas en kit_icone() : Kit::bouton() detruit le marquage de
 * surete du libelle, et le gabarit est fige octet a octet. */
.ui-btn-groupe > .btn::before,
.ui-rangee > .btn::before {
	content: '';
	display: inline-block;
	width: 16px;
	height: 16px;
	flex: 0 0 auto;
	background-color: currentColor;
	-webkit-mask-position: center;
	mask-position: center;
	-webkit-mask-repeat: no-repeat;
	mask-repeat: no-repeat;
	-webkit-mask-size: 16px 16px;
	mask-size: 16px 16px;
	/* Aucun dessin par defaut : un bouton dont le href n'est pas prevu
	   ci-dessous ne doit pas montrer une boite vide. « content: none » est
	   repose sur ce cas juste en dessous. */
	-webkit-mask-image: none;
	mask-image: none;
}
/* LA REGLE DE REPLI, ET ELLE EST INDISPENSABLE. Une boite masquee SANS
   image de masque se peint en APLAT : c'est exactement ce qui a fait les
   carres gris du selecteur de collecte. Tout bouton non prevu perd donc son
   pseudo-element entierement. */
.ui-btn-groupe > .btn:not([href*="/import"]):not([href*="/edit"]):not([href*="/produit"]):not([href*="/famille"]):not([href*="/dupliquer"]):not([href*="/export"]):not([href*="/geocode"]):not([href*="/liens"])::before,
.ui-rangee > .btn:not([href*="/import"]):not([href*="/edit"]):not([href*="/produit"]):not([href*="/famille"]):not([href*="/dupliquer"]):not([href*="/export"]):not([href*="/geocode"]):not([href*="/liens"])::before {
	content: none;
}
.ui-btn-groupe > .btn[href*="/import"]::before,
.ui-rangee > .btn[href*="/import"]::before {
	-webkit-mask-image: var(--ic-importer); mask-image: var(--ic-importer);
}
.ui-btn-groupe > .btn[href*="/export"]::before,
.ui-rangee > .btn[href*="/export"]::before {
	-webkit-mask-image: var(--ic-exporter); mask-image: var(--ic-exporter);
}
.ui-btn-groupe > .btn[href*="/produit"]::before,
.ui-btn-groupe > .btn[href*="/edit"]::before,
.ui-rangee > .btn[href*="/produit"]::before,
.ui-rangee > .btn[href*="/edit"]::before {
	-webkit-mask-image: var(--ic-ajouter); mask-image: var(--ic-ajouter);
}
.ui-btn-groupe > .btn[href*="/famille"]::before,
.ui-rangee > .btn[href*="/famille"]::before {
	-webkit-mask-image: var(--ic-catalogue); mask-image: var(--ic-catalogue);
}
.ui-btn-groupe > .btn[href*="/dupliquer"]::before,
.ui-rangee > .btn[href*="/dupliquer"]::before {
	-webkit-mask-image: var(--ic-copier); mask-image: var(--ic-copier);
}
.ui-btn-groupe > .btn[href*="/geocode"]::before,
.ui-rangee > .btn[href*="/geocode"]::before {
	-webkit-mask-image: var(--ic-direct); mask-image: var(--ic-direct);
}
.ui-btn-groupe > .btn[href*="/liens"]::before,
.ui-rangee > .btn[href*="/liens"]::before {
	-webkit-mask-image: var(--ic-generer); mask-image: var(--ic-generer);
}


/* 2. « EXPORTER LA LISTE » AVAIT PERDU SON ICONE. Son href n'est pas un
 *    chemin « /export » mais un PARAMETRE DE REQUETE, « ?export=liste »
 *    (MagasinsController : url($cid, $filtres, ['export' => 'liste'])). Il
 *    echappait donc a tous les selecteurs positifs tout en tombant dans la
 *    liste « :not() » qui retire la boite : resultat, ni dessin ni repli.
 *    Le selecteur ci-dessous attrape les deux formes, le segment et le
 *    parametre. */
.ui-btn-groupe > .btn[href*="export="]::before,
.ui-rangee > .btn[href*="export="]::before {
	content: '';
	-webkit-mask-image: var(--ic-exporter);
	mask-image: var(--ic-exporter);
}

/* 2 bis. « EXPORTER LA LISTE », LA SPECIFICITE MANQUANTE.
 *
 * La correction precedente posait bien le masque, mais le « content »
 * restait a « none » : mesure du 14 septembre 2026, content: none et
 * mask: ok sur le meme bouton, donc AUCUN dessin a l'ecran.
 *
 * LA CAUSE EST LA SPECIFICITE, PAS L'ORDRE. La regle de repli empile HUIT
 * « :not() », et chacune compte comme une classe : elle pese (0,9,1) la ou
 * « .ui-btn-groupe > .btn[href*="export="]::before » ne pese que (0,3,1).
 * Etre ecrite plus loin ne rattrape pas seize points d'ecart.
 *
 * On repose donc le « content » avec la MEME arme : le selecteur ci-dessous
 * reprend la chaine de :not() et y ajoute l'attribut, ce qui le met
 * strictement au-dessus. Pas de !important : il masquerait le probleme au
 * lieu de le resoudre, et le prochain qui lirait la feuille ne saurait pas
 * pourquoi. */
.ui-btn-groupe > .btn[href*="export="]:not([href*="/import"]):not([href*="/edit"]):not([href*="/produit"]):not([href*="/famille"]):not([href*="/dupliquer"]):not([href*="/export"]):not([href*="/geocode"]):not([href*="/liens"])::before,
.ui-rangee > .btn[href*="export="]:not([href*="/import"]):not([href*="/edit"]):not([href*="/produit"]):not([href*="/famille"]):not([href*="/dupliquer"]):not([href*="/export"]):not([href*="/geocode"]):not([href*="/liens"])::before {
	content: '';
	-webkit-mask-image: var(--ic-exporter);
	mask-image: var(--ic-exporter);
}

/* ================================================================ *
 * 21. LES TROIS BRIQUES DE LA REFONTE : ONGLETS, INTERRUPTEUR, FEUILLE
 *
 * Trois composants dont RIEN n'existait avant le 22 septembre 2026, et
 * dont dependent le bandeau de collecte, le statut a trois positions et
 * toutes les confirmations d'action.
 *
 * Ils sont ECRITS UNE FOIS, ICI, avant que cinq ecrans ne les reclament
 * chacun a sa facon : c'est exactement ainsi qu'etaient nees les huit
 * hauteurs de champ que la refonte vient de supprimer.
 * ================================================================ */

/* ---- LES ONGLETS ---------------------------------------------------
 *
 * L'ACTIF DOIT ETRE FRANCHEMENT VISIBLE. Le defaut releve a la relecture
 * de la maquette : une nuance de gris entre l'actif et le reste laisse
 * l'organisateur chercher sur quel onglet il se trouve. La barre fait
 * donc 3 px, pas 1, et la graisse change AUSSI : deux signaux, parce
 * qu'une barre seule disparait pour qui distingue mal les couleurs. */
.ui-onglets {
	display: flex;
	gap: var(--espace-1);
	/* Ombre interne et non bordure : overflow rognait le trait de l'actif posé dessus. */
	box-shadow: inset 0 -2px 0 var(--trait);
	/* Sur un ecran etroit, les onglets defilent au doigt plutot que de
	   passer a la ligne : une barre d'onglets sur deux lignes ne se lit
	   plus comme une barre. */
	overflow-x: auto;
	overflow-y: hidden;
	scrollbar-width: none;
	margin-bottom: var(--v-dedans);
}
.ui-onglets::-webkit-scrollbar { display: none; }
/* Le degrade ne parait que si la barre deborde reellement, classe posee
   par indiquerMenuDefilable dans ui.js, comme pour .menu. */
.ui-onglets.menu--defilable {
	-webkit-mask-image: linear-gradient(to right, #000 calc(100% - 32px), transparent);
	mask-image: linear-gradient(to right, #000 calc(100% - 32px), transparent);
}

.ui-ong {
	display: inline-flex;
	align-items: center;
	gap: var(--espace-2);
	/* La meme hauteur que tout controle, et pour la meme raison. */
	height: var(--h-ctrl);
	min-height: 0;
	padding: 0 var(--espace-4);
	/* Toujours présente (transparente si inactif) et de l'épaisseur du rail. */
	border-bottom: 2px solid transparent;
	color: var(--doux);
	font-size: var(--t-texte);
	font-weight: var(--g-normal);
	line-height: 1;
	text-decoration: none;
	white-space: nowrap;
	transition: color var(--transition), border-color var(--transition);
}
.ui-ong:hover { color: var(--action); }
/* L'icone suit le palier de texte de l'onglet, --t-texte appelle --ic-t-texte. */
.ui-ong > .ui-icone { width: var(--ic-t-texte); height: var(--ic-t-texte); }

/* DEUX SIGNAUX POUR L'ACTIF, jamais un seul : la couleur et la barre pour
   l'oeil, la graisse pour qui ne distingue pas les deux teals. */
.ui-ong.is-actif {
	border-bottom-color: var(--accent);
	color: var(--action);
	font-weight: var(--g-fort);
}

/* ---- L'INTERRUPTEUR ------------------------------------------------
 *
 * LA PISTE EST DERIVEE DU MARQUEUR, --marq * 2 + --ep-marq * 2, elle ne
 * se pose jamais a la main : c'est la deuxieme regle du systeme UI. Une
 * piste ecrite « 52px » se serait desolidarisee du marqueur au premier
 * changement de celui-ci, ce qui est exactement l'histoire des huit
 * hauteurs de champ.
 *
 * LE REPLI EST LE POINT CRITIQUE. Ce controle porte le statut d'une
 * collecte, c'est-a-dire la reponse a « est-ce que 92 magasins peuvent
 * compter ». Il est bati sur des radios REELS : sans la feuille, il reste
 * trois boutons radio etiquetes, degrade mais utilisable et surtout
 * HONNETE. Aucun :has() n'est employe, le selecteur de frere suffit. */
.ui-inter { display: inline-flex; flex-direction: column; gap: var(--espace-2); }

.ui-inter-piste {
	position: relative;
	display: inline-flex;
	align-items: center;
	height: var(--piste-h);
	padding: var(--ep-marq);
	border-radius: var(--rayon-pilule);
	background-color: var(--bord-controle);
	transition: background-color var(--transition);
}
.ui-inter--deux .ui-inter-piste  { width: var(--piste-l); }

/* TROIS POSITIONS DEMANDENT UN PAS PLUS LARGE QUE LA BILLE, sinon les
   reperes sont recouverts et l'oeil ne voit que deux crans : mesure au
   navigateur le 22 septembre 2026, 74 px pour trois positions, on ne
   distinguait rien. Le pas vaut donc la bille PLUS un cran d'ecart, ce qui
   laisse toujours voir ou l'on peut aller. Derive, pas pose a la main. */
.ui-inter--trois { --pas: calc(var(--marq) + var(--ep-marq)); }
.ui-inter--trois .ui-inter-piste { width: calc(var(--pas) * 3 + var(--ep-marq) * 2); }
/* La bille se centre dans son pas, au lieu de se coller a gauche. */
.ui-inter--trois .ui-inter-bille { margin-left: calc((var(--pas) - var(--marq)) / 2); }
/* TROIS REPERES SOUS LA BILLE : ils disent qu'il y a trois crans meme quand
   la bille en occupe un. Sans eux, un interrupteur a trois positions se lit
   comme un interrupteur a deux. */
.ui-inter--trois .ui-inter-piste {
	background-image:
		radial-gradient(circle at calc(var(--pas) * .5 + var(--ep-marq)) 50%, rgba(255, 255, 255, .55) 3px, transparent 3.5px),
		radial-gradient(circle at calc(var(--pas) * 1.5 + var(--ep-marq)) 50%, rgba(255, 255, 255, .55) 3px, transparent 3.5px),
		radial-gradient(circle at calc(var(--pas) * 2.5 + var(--ep-marq)) 50%, rgba(255, 255, 255, .55) 3px, transparent 3.5px);
}

/* LES RADIOS SONT MASQUES, JAMAIS SUPPRIMES. « display: none » les
   retirerait de l'ordre de tabulation et du groupe annonce par le lecteur
   d'ecran : le controle deviendrait inaccessible au clavier. Ils gardent
   donc leur taille et leur place, sous la bille. */
.ui-inter-choix {
	position: absolute;
	width: var(--marq);
	height: var(--marq);
	margin: 0;
	opacity: 0;
	cursor: pointer;
	z-index: 2;
}
.ui-inter-choix:nth-of-type(1) { left: var(--ep-marq); }
.ui-inter-choix:nth-of-type(2) { left: calc(var(--ep-marq) + var(--pas)); }
.ui-inter-choix:nth-of-type(3) { left: calc(var(--ep-marq) + var(--pas) * 2); }
/* La zone cliquable couvre son pas entier : viser une cible de 22 px au
   doigt dans une piste de 82 est un faux clic garanti. */
.ui-inter-choix { width: var(--pas); }

/* LES ETIQUETTES NE SONT PAS « display: none » NON PLUS, et pour une
   raison differente : elles portent le nom accessible de chaque position.
   Masquees visuellement, lues par le lecteur d'ecran. */
.ui-inter-etiq {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
}

.ui-inter-bille {
	position: absolute;
	left: var(--ep-marq);
	width: var(--marq);
	height: var(--marq);
	border-radius: 50%;
	background: var(--carte);
	box-shadow: 0 1px 3px rgba(15, 34, 41, .3);
	transition: transform var(--transition);
	pointer-events: none;
}

/* LA COURSE VAUT EXACTEMENT UN MARQUEUR. C'est ce qui fait que la bille
   s'arrete au bord de la piste sans calcul separe. */
/* LA COURSE VAUT UN PAS, et le pas vaut le marqueur sur deux positions.
   C'est ce qui fait que la bille s'arrete au bord de la piste sans calcul
   separe, quel que soit le nombre de crans. */
.ui-inter--deux { --pas: var(--marq); }
.ui-inter-choix:nth-of-type(2):checked ~ .ui-inter-bille { transform: translateX(var(--pas)); }
.ui-inter-choix:nth-of-type(3):checked ~ .ui-inter-bille { transform: translateX(calc(var(--pas) * 2)); }

/* LA PISTE DIT L'ETAT, PAS SEULEMENT LA POSITION DE LA BILLE. Sur trois
   crans, la position du milieu est une attente et la derniere un etat
   actif : deux teals de force differente les distinguent sans mot.
   :has() EST LE SEUL MOYEN ICI, parce que la piste est le PARENT des
   radios et qu'aucun selecteur de frere ne remonte. C'est aussi le seul
   endroit du composant qui en depend, et la degradation est benigne : un
   navigateur sans :has() rend une piste grise dont la BILLE bouge quand
   meme, plus la phrase de la position en cours sous le controle. */
/* « background-color » ET PAS LE RACCOURCI « background » : le raccourci
   remet background-image a « none », ce qui effaçait les trois reperes de
   la piste a trois crans. Mesure au navigateur : backgroundImage rendait
   « none » alors que la regle etait bien ecrite, quinze lignes plus haut. */
.ui-inter--deux  .ui-inter-piste:has(.ui-inter-choix:nth-of-type(2):checked) { background-color: var(--action); }
.ui-inter--trois .ui-inter-piste:has(.ui-inter-choix:nth-of-type(2):checked) { background-color: var(--focus); }
.ui-inter--trois .ui-inter-piste:has(.ui-inter-choix:nth-of-type(3):checked) { background-color: var(--action); }

/* L'ANNEAU DE FOCUS SE POSE SUR LA PISTE, pas sur le radio invisible :
   un anneau sur un element transparent ne se voit nulle part. */
.ui-inter-choix:focus-visible ~ .ui-inter-bille {
	outline: 2px solid var(--focus);
	outline-offset: 3px;
}

/* LA LEGENDE DES TROIS CRANS. Trois mots alignes sur la piste, dont un
   seul est en encre pleine : l'oeil trouve la position courante sans avoir
   a interpreter la place d'une bille. */
.ui-inter-legende {
	display: flex;
	gap: var(--espace-3);
	margin: 0;
	padding: 0;
	list-style: none;
	color: var(--doux);
	font-size: var(--t-mini);
	line-height: var(--lh-mini);
}
.ui-inter-legende > .is-actif { color: var(--action); font-weight: var(--g-fort); }

/* LA PHRASE SOUS LE CONTROLE EST LE VRAI REPLI. Le dessin peut ne pas
   rendre ; le texte, lui, dit toujours la position en cours. */
.ui-inter-aide {
	margin: 0;
	color: var(--doux);
	font-size: var(--t-mini);
	line-height: var(--lh-mini);
}

/* ---- LA FEUILLE MODALE ---------------------------------------------
 *
 * <dialog> NATIF : le piege de focus, Echap, l'inertie du fond et la
 * couche superieure sont donnes par le navigateur. Ce qui suit n'est que
 * l'habillage, et le ::backdrop est le voile. */
.ui-feuille {
	width: min(440px, calc(100vw - var(--espace-6)));
	max-height: calc(100vh - var(--espace-6));
	padding: 0;
	border: none;
	border-radius: var(--rayon);
	background: var(--carte);
	color: var(--texte);
	box-shadow: var(--ombre-forte);
}
.ui-feuille--large { width: min(720px, calc(100vw - var(--espace-6))); }

/* Le voile est teinte de l'encre teal, pas d'un noir neutre : un gris
   pur sur une page teintee se lit comme un calque mal regle. */
.ui-feuille::backdrop { background: rgba(8, 50, 60, .42); }

.ui-feuille-tete {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--espace-4);
	margin: 0;
	padding: var(--espace-3) var(--espace-4);
	border-bottom: 1px solid var(--trait);
	background: var(--action-pale);
}

/* LA CROIX EST UN CONTROLE, donc une cible tactile pleine : une croix de
   la taille de son glyphe se rate au doigt. */
.ui-feuille-fermer {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: var(--h-ctrl-s);
	height: var(--h-ctrl-s);
	flex: 0 0 auto;
	border: none;
	border-radius: var(--rayon-petit);
	background: none;
	color: var(--doux);
	font-size: var(--t-titre);
	line-height: 1;
	cursor: pointer;
	transition: background var(--transition), color var(--transition);
}
.ui-feuille-fermer:hover { background: var(--action-clair); color: var(--action); }

/* LE CORPS DEFILE, PAS LA FEUILLE ENTIERE : la tete et le pied restent
   visibles, donc le bouton de confirmation aussi. Un long formulaire dont
   le bouton part sous la ligne de flottaison se lit comme une impasse. */
.ui-feuille-corps {
	padding: var(--espace-4);
	overflow-y: auto;
}
.ui-feuille-pied {
	display: flex;
	justify-content: flex-end;
	gap: var(--espace-2);
	padding: var(--espace-3) var(--espace-4);
	border-top: 1px solid var(--trait);
	background: var(--action-pale);
}

/* SUR TELEPHONE, ELLE MONTE DU BAS ET PREND LA LARGEUR. C'est le geste
   attendu sur un ecran tenu a une main : le pouce atteint le bas, pas le
   centre. Le rayon ne reste qu'en haut, ou la feuille se detache. */
@media (max-width: 560px) {
	.ui-feuille,
	.ui-feuille--large {
		width: 100vw;
		max-width: 100vw;
		max-height: 88vh;
		margin: auto auto 0;
		border-radius: var(--rayon) var(--rayon) 0 0;
	}
}

/* ---- LE BANDEAU DE CONTEXTE D'UNE COLLECTE -------------------------
 *
 * Il repond en une ligne aux trois questions de l'arrivee sur un ecran de
 * collecte : laquelle, dans quel etat, et comment en sortir.
 *
 * LA SORTIE EST A DROITE, ET C'EST TOUT SON PROPOS. « En direct » ouvre une
 * page PUBLIQUE dans un autre onglet du navigateur. Tant qu'il etait parmi
 * les onglets, il promettait de changer la page sous la barre, ce qu'il n'a
 * jamais fait. Le sortir demande de lui donner une place : celle-ci. */
.ui-bandeau-collecte {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--espace-4);
	flex-wrap: wrap;
	margin-bottom: var(--v-dedans);
}
/* Le titre et le mot d'etat sur la MEME ligne de base : l'etat qualifie le
   titre, il ne le suit pas comme une legende. */
.ui-bandeau-titres {
	display: flex;
	align-items: center;
	gap: var(--espace-3);
	flex-wrap: wrap;
	min-width: 0;
}
/* Le h1 perd sa marge basse ici : c'est le bandeau qui porte l'ecart, et
   deux sources d'espacement en produisent toujours un de travers. */
.ui-bandeau-titres > h1 { margin: 0; min-width: 0; }
/* SUR TELEPHONE, LA SORTIE NE PREND PAS LA LARGEUR. Mesure a 390 px : le
   bouton passait seul sur sa ligne et y prenait l'allure de l'action
   principale de l'ecran, alors que c'est un lien vers une autre page. */
@media (max-width: 560px) {
	.ui-bandeau-collecte > .btn { align-self: flex-start; }
}

/* ---- LA LIGNE DE STATUT DU COMPTAGE ---------------------------------
 *
 * HORS DE TOUT BLOC, ET SEULE SUR SA LIGNE. Elle repond a la seule
 * question qui vaille un jour de collecte : est-ce que les magasins
 * peuvent compter en ce moment. La ranger dans un bloc de reglages la
 * mettrait au meme rang qu'un fuseau horaire.
 *
 * L'ECART AU-DESSUS EST CELUI D'UN CHANGEMENT DE SUJET : ce qui precede
 * parle de la collecte, ceci parle du terrain. */
.ui-ligne-statut {
	margin: var(--v-entre) 0;
}
.ui-ligne-statut-controle {
	display: flex;
	align-items: flex-start;
	gap: var(--espace-4);
	flex-wrap: wrap;
}
/* Le bouton s'aligne sur la PISTE, pas sur le bloc entier : l'interrupteur
   porte sa phrase de repli en dessous, et se centrer dessus ferait flotter
   le bouton au milieu de deux lignes de texte. */
.ui-ligne-statut-controle > .btn {
	margin-top: calc((var(--piste-h) - var(--h-ctrl-s)) / 2);
}


/* ================================================================ *
 * LE SYSTEME DE LA MAQUETTE, PORTE TEL QUEL
 * ================================================================ *
 *
 * Source : 06-admin/COLLETA-MAQUETTE-ADMIN.html, sections 3 a 13 et
 * responsive, portees le 23 septembre 2026. La maquette est la reference,
 * et ces regles sont les siennes, AU CARACTERE PRES : les retoucher ici
 * ferait diverger l'ecran du document qui l'a valide.
 *
 * POURQUOI ELLES ARRIVENT EN FIN DE FEUILLE. Les gabarits de templates/
 * portent deja les classes de la maquette (.carte, .btn, .tbl, .champ...),
 * et AUCUNE n'avait de regle : 165 classes orphelines, mesurees le
 * 23 septembre. Les ecrans rendaient donc du texte nu sous un chassis
 * correct. Posees ici, ces regles gagnent sur les .ui-* historiques la ou
 * les deux se rencontrent, ce qui est exactement l'arbitrage voulu.
 *
 * LES SECTIONS 1 ET 2 (roles de texte, icones) NE SONT PAS REPRISES :
 * ui.css les porte deja plus haut, aux memes valeurs. Les reprendre
 * dupliquerait la typographie sans rien changer a l'ecran.
 *
 * LA SECTION 14 NON PLUS : c'est la barre de navigation de la maquette,
 * un outil de travail qui n'appartient pas au produit.
 *
 * LA PORTEE EST L'ADMIN ET LE PUBLIC, JAMAIS LE COMPTAGE. La page de
 * terrain ne charge pas cette feuille, mais la garde vaut pour le jour ou
 * quelqu'un l'y ajouterait : .colleta-plus ne doit jamais etre repeint par
 * un .btn d'admin.
 */

/* ================== 3. LE RYTHME ET LES CONTENANTS ================== */

.g{display:grid; gap:var(--e3)}
.g--serre{gap:var(--e2)}
.g--aere{gap:var(--e4)}
.g2{grid-template-columns:repeat(2,minmax(0,1fr))}
.g3{grid-template-columns:repeat(3,minmax(0,1fr))}
.g4{grid-template-columns:repeat(4,minmax(0,1fr))}
/* Une rangee s'aligne en haut, elle ne repartit pas sa hauteur :
   c'est ce qui etirait un champ a 52px. */
.g{align-items:start}
.f{display:flex; flex-wrap:wrap; gap:var(--e2); align-items:center}
.f-fin{margin-left:auto}
.f--fin{justify-content:flex-end}

/* La page : le rythme par defaut est --v-entre. Tout enfant direct est
   un sujet a part entiere. Il n'y a que deux exceptions, nommees. */
.page{padding:var(--e5); display:grid; gap:var(--v-entre); align-content:start; min-width:0}

/* EXCEPTION 1, la coiffe. Le retour, le fil et le titre sont UNE SEULE
   chose : d'ou l'on vient et ou l'on est. Elle s'arrete aux onglets.
   La barre d'onglets ne coiffe pas ce qui suit, elle l'annonce : changer
   d'onglet change tout le contenu, c'est bien --v-entre qui les separe. */
.page > .retour + .contexte,
.page > .retour + .page-tete,

/* EXCEPTION 2, la barre de filtres et son compteur appartiennent a la
   liste qu'ils filtrent. */
.page > .rangee-filtres + .tbl,
.page > .rangee-filtres + .bloc,
.page > .rangee-filtres + .famille,
.page > .filtres + .tbl,
.page > .rangee-filtres + .filtres,
.page > .rangee-filtres + .tri,
.page > .filtres + .tri,
.page > .tri + *{margin-top:calc(var(--v-dedans) - var(--v-entre))}

/* La zone de danger prend l'air : on ne supprime pas par inadvertance. */
.page > .danger{margin-top:var(--v-colle)}

/* ---- LE CONTENANT, une seule definition ----
   Tout ce qui pose du papier passe par la. Aucune autre regle du
   fichier ne redit fond, trait ou rayon. */
.carte,.bloc,.tbl,.famille,.feuille,.chiffre,.danger,.barre-actions,
.menu-perso{
  background:var(--k-fond); border:var(--k-trait); border-radius:var(--k-rayon);
}
.bloc,.tbl,.famille,.feuille,.menu-perso{overflow:hidden}

.carte{padding:var(--k-retrait); display:grid; gap:var(--v-dedans); align-content:start}
.carte-titre{margin:0}
.carte-trait{border:0; border-top:var(--k-trait); margin:0}

/* Les trois etages d'un contenant a rangees : tete, corps, pied.
   Une bande est une RANGEE : ses occupants tiennent sur une ligne. */
.bloc-tete,.famille-tete,.feuille-tete,.tbl-tete{
  padding:var(--k-retrait-serre); background:var(--c-surface-2);
  border-bottom:var(--k-trait);
}
.bloc-tete{display:flex; align-items:center; justify-content:space-between;
  gap:var(--e3); flex-wrap:wrap}
/* Une rangee : le « h2 + * » des empilements decalerait ce qui suit le titre. */
.bloc-tete > h2 + *,.famille-tete > h3 + *{margin-top:0}
/* Ces contenants espacent par leur gap : une marge d'enfant (navigateur, « h2 + * ») s'y ajouterait. */
:is(.bloc-corps,.carte,.danger,.feuille-corps) > *{margin-block:0}
/* Marge negative : le bouton ne grossit pas la tete, toutes gardent la meme hauteur. */
.bloc-tete > .btn,.bloc-tete > form > .btn,.bloc-tete .ic-btn{
  height:var(--h-ctrl-s); margin-block:calc((var(--t-mini) * var(--lh-mini) - var(--h-ctrl-s)) / 2)}
.bloc-tete > form{display:flex}
.tbl-pied,.feuille-pied{
  padding:var(--k-retrait-serre); background:var(--c-surface-2);
  border-top:var(--k-trait);
}
.bloc-corps,.feuille-corps{padding:var(--k-retrait); display:grid; gap:var(--v-dedans)}

/* La rangee : meme rembourrage, meme trait, meme regle du dernier. */
.tbl-l,.ligne,.feuille-l{padding:var(--k-retrait-serre); border-bottom:var(--k-trait)}
.produit{padding:var(--e2) var(--e4); border-bottom:var(--k-trait)}
/* :last-of-type et non :last-child pour .produit : .tbl-pied suit toujours
   le dernier .produit dans .famille, qui n'est donc jamais le DERNIER enfant. */
.tbl-l:last-of-type,.ligne:last-child,.feuille-l:last-child,
.produit:last-of-type{border-bottom:0}

/* ---- LA LARGEUR DE LECTURE ----
   Ce qui se REMPLIT est borne, ce qui se BALAYE prend la page.
   La borne se pose sur le CORPS, jamais sur le champ. */
.bloc-corps:has(.saisie),.bloc-corps:has(.ui-champ),
.carte:has(.saisie),.carte:has(.ui-champ),
.feuille-corps:has(.saisie),.feuille-corps:has(.ui-champ){max-width:var(--l-formulaire)}
/* Des filtres ne font pas d'une liste un formulaire : elle se balaye, pleine largeur. */
.bloc-corps:has(> .rangee-filtres){max-width:none}
.carte--texte{max-width:var(--l-lecture)}
.tbl,.famille,.page > .g4,.page > .g3,.page > .g2{max-width:none}

/* Les contenants qui changent de ton : la meme boite, une autre couleur. */
.danger{background:var(--c-erreur-voile); border-color:#C88AB9;
  padding:var(--k-retrait); display:grid; gap:var(--v-dedans)}
/* Meme carte coloree que .collecte-compte span (liste des collectes) : un
   chiffre se lit comme une donnee posee, pas comme une carte neutre. */
.chiffre{padding:var(--k-retrait); display:grid; gap:var(--v-colle); align-content:start;
  background:var(--c-action-voile); border-color:transparent}
.g > .chiffre{align-self:stretch}

/* LES INTITULES EN PETITES CAPITALES, et c'est une regle de la maquette que
   l'application n'avait PAS portee : « .k » n'existait nulle part dans cette
   feuille, si bien que « Magasins prets » s'affichait en minuscules a cote
   d'un « MAGASINS PRETS » sur la maquette. Compare au navigateur, les deux
   ecrans cote a cote, le 23 septembre 2026.

   LE MEME TRAITEMENT QUE .bloc-tete h2 ET .fait .k : ces trois-la jouent le
   meme role, nommer une valeur qu'on lit juste en dessous, et la maquette
   les regroupe dans une seule regle. Le mot est petit, capitalise et doux :
   c'est la valeur qui porte, pas son etiquette. */
.chiffre .k,.fait .k,.ratt-c .k,.tbl-tete>*,.etiquette,.jour-groupe{
  font-size:var(--t-mini); font-weight:var(--g-fort);
  text-transform:uppercase; letter-spacing:var(--ls-capitale,.09em);
  color:var(--c-doux); line-height:var(--lh-mini,1.35);
}
/* La VALEUR se lit, la clef se survole : elle reprend donc le corps de texte
   et la couleur d'encre, sans quoi les deux se confondraient. */
.fait .v,.ratt-c .v{font-size:var(--t-texte); color:var(--c-encre)}
/* Le compteur d'une pastille est un chiffre, pas une phrase. */
.fpil .n{font-size:var(--t-mini); line-height:1}
/* Le nom d'une ligne de tableau porte, c'est par lui qu'on la reconnait. */
.cel-nom{font-weight:var(--g-fort); color:var(--c-encre)}
/* Le contexte accompagne le titre, il ne rivalise pas avec lui. */
.contexte{font-size:var(--t-petit); color:var(--c-doux)}
.contexte h1{font-size:var(--t-page)}
/* Un badge est une etiquette : meme cran, meme interlettrage. */
.badge{font-size:var(--t-mini); letter-spacing:var(--ls-capitale,.09em); line-height:1}
.chiffre--vedette{background:var(--c-action-fonce); border-color:var(--c-action-fonce)}
.famille.eteinte .famille-tete{background:var(--c-surface)}

/* ---- L'OMBRE ----
   Ce qui FLOTTE en porte une, ce qui est POSE n'en porte pas. */
.feuille,.menu-perso{box-shadow:var(--ombre-f)}
.barre-actions{box-shadow:var(--ombre)}

/* ================== 4. LES CONTROLES DE SAISIE ================== */

.champ{display:grid; gap:var(--e2); align-content:start}
.champ-tete{display:flex; align-items:baseline; justify-content:space-between; gap:var(--e2)}
.champ .requis{color:var(--c-accent-encre); margin-left:var(--e1)}

/* La hauteur est POSEE, pas deduite. Le padding vertical ne participe
   plus au calcul : il centre le texte dans une boite deja dimensionnee. */
.saisie{
  height:var(--h-ctrl); min-height:0; width:100%;
  border:var(--ep-bord) solid var(--c-bord); border-radius:var(--r-s);
  padding:0 var(--e3); background:var(--c-papier);
  font:var(--t-texte)/var(--lh-texte) var(--f-texte); color:var(--c-encre);
  appearance:none; -webkit-appearance:none;
  transition:border-color .15s ease, background-color .15s ease;
}
.saisie::placeholder{color:#7E8F96}
.saisie:hover:not(:disabled){border-color:var(--c-action-vif)}
/* Focus dessiné dans la boîte : un anneau extérieur mordait sur le label suivant. */
.saisie:focus-visible{outline:none; border-color:var(--focus); box-shadow:inset 0 0 0 1px var(--focus)}
.saisie:disabled{background:var(--c-surface-2); color:#5A6A70; border-color:var(--c-trait); cursor:not-allowed}

/* Le widget natif de date faisait 2px de plus que sa line-height :
   sans height fixe il depassait a 50px. */
.saisie[type=date],.saisie[type=time]{min-width:0}
.saisie::-webkit-date-and-time-value{text-align:left; margin:0}
.saisie::-webkit-datetime-edit{padding:0}

/* Le mono garde sa police, pas sa taille : un code et un nom poses
   cote a cote doivent avoir la meme assise. Cause du 46 et du 47. */
.saisie--mono{font-family:var(--f-code); font-size:var(--t-texte); letter-spacing:-.01em}

/* EXCEPTION, le textarea : il sert a coller plusieurs lignes.
   Hauteur comptee en lignes, 3 x 21px + padding + bordure = 90. */
textarea.saisie{
  height:auto; min-height:calc(3 * 21px + 24px + 3px);
  padding:var(--e3); line-height:var(--lh-texte); resize:vertical;
}

/* Le select : meme boite, plus la place du chevron. Le chevron suit
   le palier d'icone du texte qu'il accompagne, 18px, pas 17. */
select.saisie{
  padding-right:42px; cursor:pointer;
  background-image:var(--u-chevron); background-repeat:no-repeat;
  background-position:right var(--e3) center; background-size:18px;
}

/* Le champ suffixe : l'unite HERITE de la hauteur, elle ne la recalcule pas. */
.saisie-grp{display:flex; align-items:stretch; min-width:0; height:var(--h-ctrl)}
.saisie-grp .saisie{border-radius:var(--r-s) 0 0 var(--r-s); position:relative; z-index:1}
.saisie-grp .unite{
  display:grid; place-items:center; padding:0 var(--e4); flex:none;
  border:var(--ep-bord) solid var(--c-bord); border-left:0;
  border-radius:0 var(--r-s) var(--r-s) 0;
  background:var(--c-surface-2); color:var(--c-doux);
  font:var(--g-fort) var(--t-petit)/var(--lh-nu) var(--f-titre); white-space:nowrap;
}

/* .aide ne porte QU'UN etat de validation, jamais une explication. */
.aide{color:var(--c-doux); display:flex; gap:var(--e2); align-items:flex-start}
.aide .ic{margin-top:calc((var(--t-mini) * var(--lh-mini) - var(--ic-mini)) / 2); flex:none}

.champ.ok .saisie{border-color:var(--c-succes-encre)}
.champ.ok .aide{color:var(--c-succes-encre); font-weight:var(--g-fort)}
/* L'erreur epaissit la bordure SANS bouger la boite : la hauteur est
   fixe, l'element ne saute plus d'un pixel en devenant invalide. */
.champ.err .saisie{border-color:var(--c-erreur); border-width:2px; background:#FFFAFB}
.champ.err .aide{color:var(--c-erreur-encre); font-weight:var(--g-fort)}
.champ.att .saisie{border-color:var(--c-anis-encre)}
.champ.att .aide{color:var(--c-anis-encre); font-weight:var(--g-fort)}

/* ---- LA RECHERCHE, un seul composant ----
   .barre-rech etait un doublon de .rech dont toutes les valeurs
   etaient reecrites a la main. */
.rech,.barre-rech{
  display:flex; align-items:center; gap:var(--e3); min-width:0;
  height:var(--h-ctrl); min-height:0; padding:0 var(--e4);
  border:var(--ep-bord) solid var(--c-bord); border-radius:var(--r-pilule);
  background:var(--c-papier); transition:border-color .15s ease;
}
/* .barre-rech, variante discrete : un aplat doux sans contour au repos, qui
   s'affirme au survol puis au focus. Elle pose dans la barre sans la remplir. */
.barre-rech{
  grid-column:2; width:100%; height:var(--h-ctrl-barre); gap:var(--e2); padding:0 var(--e3);
  border-color:transparent; background:var(--c-surface-2); color:var(--c-doux); cursor:text;
  transition:border-color var(--transition), background-color var(--transition);
}
.barre-rech:hover:not(:focus-within){background:color-mix(in srgb, var(--c-surface-2), var(--c-action) 7%)}
.barre-rech .ui-icone{width:var(--ic-petit); height:var(--ic-petit)}
/* Sur telephone le champ est etroit : le texte indicatif finit sur des
   points de suspension, pas coupe au milieu d'un mot. */
.barre .barre-rech input{font-size:var(--t-petit); text-overflow:ellipsis}
/* Pas sur téléphone : sous 16 px, iPhone zoome au focus. */
@media (min-width:721px){ .barre .barre-rech input{font-size:var(--t-mini)} }
.barre-rech input::placeholder{color:var(--c-doux); opacity:1}
.rech:focus-within,.barre-rech:focus-within{border-color:var(--focus); box-shadow:inset 0 0 0 1px var(--focus)}
.barre-rech:focus-within{background:var(--c-papier); color:var(--c-action)}
.rech input,.barre-rech input{
  border:0; outline:0; flex:1 1 auto; min-width:0; width:100%;
  height:100%; min-height:0; padding:0;
  font:var(--t-texte)/var(--lh-texte) var(--f-texte);
  background:transparent; color:var(--c-encre);
}

/* ---- EXCEPTION, le select de densite ----
   Le seul controle a 36px : pose seul sous un tableau, hors de toute
   rangee d'outils, rien ne s'aligne dessus. */
.par-page{display:flex; align-items:center; gap:var(--e2); flex-wrap:wrap; color:var(--c-doux)}
.par-page select{
  height:var(--h-ctrl-s); min-height:0; padding:0 34px 0 var(--e3); cursor:pointer;
  border:var(--ep-bord) solid var(--c-bord); border-radius:var(--r-s);
  background:var(--c-papier); color:var(--c-action);
  font:var(--g-fort) var(--t-petit)/var(--lh-nu) var(--f-titre);
  appearance:none; -webkit-appearance:none;
  background-image:var(--u-chevron); background-repeat:no-repeat;
  background-position:right var(--e2) center; background-size:var(--ic-petit);
}
.par-page select:focus-visible{outline:none; border-color:var(--focus); box-shadow:inset 0 0 0 1px var(--focus)}

/* ---- LE MARQUEUR ----
   UN SEUL marqueur pour tout booleen, 22px. .tbl-case ne peut plus
   diverger de .case input. */
.case input[type=checkbox],
.case input[type=radio],
.tbl-case{
  appearance:none; -webkit-appearance:none; margin:0; flex:none; cursor:pointer;
  width:var(--marq); height:var(--marq);
  border:var(--ep-bord) solid var(--c-bord); border-radius:var(--r-marq);
  background:var(--c-papier); display:grid; place-items:center;
  transition:background-color .15s ease, border-color .15s ease;
}
.case input[type=radio]{border-radius:50%}
.case input[type=checkbox]:checked,
.case input[type=radio]:checked,
.tbl-case:checked{background:var(--c-action); border-color:var(--c-action)}
.case input[type=checkbox]:checked::after,
.tbl-case:checked::after{
  content:""; width:11px; height:6px; border:2.5px solid #fff; border-top:0; border-right:0;
  transform:rotate(-45deg) translate(1px,-1px);
}
.case input[type=radio]:checked::after{
  content:""; width:9px; height:9px; border:0; border-radius:50%; background:#fff; transform:none;
}
.case input:focus-visible,.tbl-case:focus-visible{box-shadow:var(--focus-anneau); outline:none}

/* La cible tactile : une case de 48px est cliquable en entier, la case
   de tableau est seule, on lui donne sa cible sans la grossir. */
.tbl-case{position:relative}
.tbl-case::before{
  content:""; position:absolute; top:50%; left:50%; transform:translate(-50%,-50%);
  width:44px; height:44px;
}

/* ---- LE BLOC DE CASE, deux formes NOMMEES ----
   .case 48px une ligne, .case--decrite 70px un titre et sa ligne.
   Le choix est dans la classe, pas dans le texte qu'on ecrit dedans. */
.case{
  display:flex; align-items:center; gap:var(--e3); cursor:pointer;
  height:var(--h-ctrl); min-height:0; padding:0 var(--e3);
  border:var(--ep-bord) solid var(--c-trait); border-radius:var(--r-s);
  background:var(--c-papier);
  transition:border-color .15s ease, background-color .15s ease;
}
.case:hover{border-color:var(--c-action-vif); background:var(--c-action-voile)}
.case:has(input:checked){border-color:var(--c-action); background:var(--c-action-voile)}
.case .txt{
  display:block; flex:1 1 auto; min-width:0;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.case .txt b{font-family:var(--f-titre); font-weight:var(--g-fort);
  font-size:var(--t-texte); line-height:var(--lh-texte)}

/* EXCEPTION, la variante decrite : deux lignes, hauteur fixe aussi. */
.case--decrite{height:70px; align-items:flex-start; padding:var(--e3)}
.case--decrite input{margin-top:2px}
.case--decrite .txt{white-space:normal}
.case--decrite .txt b{
  display:block; margin-bottom:2px;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.case--decrite .txt b ~ *,
.case--decrite .txt br + *{display:block; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}

/* ---- L'INTERRUPTEUR A TROIS POSITIONS ----
   L'interrupteur voisin avec un cran de plus. Ce qui rend trois crans
   lisibles n'est pas la taille, c'est le jeu entre eux : le pas vaut la
   bille plus --e1, donc les reperes ne sont jamais recouverts.
   Bille 24, pas 28, piste 3 pas + 2 x --e1 = 90 sur 32. */
.bascule3{
  --m:24px; --pas:28px;
  display:inline-flex; align-items:center; height:var(--h-ctrl); flex:none;
  position:relative; cursor:pointer;
}
.bascule3 .piste{
  width:calc(var(--pas) * 3 + var(--e1) * 2);   /* 90 */
  height:calc(var(--m) + var(--e1) * 2);        /* 32 */
  border-radius:var(--r-pilule);
  flex:none; padding:var(--e1); transition:background-color .2s ease;
  /* Trois reperes dans la piste : sans eux, trois crans se lisent
     comme un interrupteur a deux positions un peu large. */
  background-color:var(--c-piste);
  background-image:
    radial-gradient(circle at calc(var(--pas) * .5 + var(--e1)) 50%, rgba(255,255,255,.55) 3px, transparent 3.5px),
    radial-gradient(circle at calc(var(--pas) * 1.5 + var(--e1)) 50%, rgba(255,255,255,.55) 3px, transparent 3.5px),
    radial-gradient(circle at calc(var(--pas) * 2.5 + var(--e1)) 50%, rgba(255,255,255,.55) 3px, transparent 3.5px);
}
.bascule3 .bille{
  width:var(--m); height:var(--m); border-radius:50%; background:#fff; display:block;
  margin-left:calc((var(--pas) - var(--m)) / 2);
  box-shadow:0 1px 3px rgba(15,34,41,.3); transition:transform .2s ease;
}
/* Les trois zones cliquables se superposent a la piste, un tiers chacune. */
.bascule3 label{
  position:absolute; top:0; bottom:0; width:calc(100% / 3); cursor:pointer;
}
.bascule3 label:nth-of-type(1){left:0}
.bascule3 label:nth-of-type(2){left:calc(100% / 3)}
.bascule3 label:nth-of-type(3){left:calc(100% / 3 * 2)}
.bascule3 input{position:absolute; opacity:0; width:0; height:0}
/* La position choisie deplace la bille et colore la piste.
   Fermee reste grise, c'est l'etat eteint, comme un interrupteur au repos. */
.bascule3:has(label:nth-of-type(2) input:checked) .bille{transform:translateX(var(--pas))}
.bascule3:has(label:nth-of-type(3) input:checked) .bille{transform:translateX(calc(var(--pas) * 2))}
.bascule3:has(label:nth-of-type(2) input:checked) .piste{background-color:var(--c-action-vif)}
.bascule3:has(label:nth-of-type(3) input:checked) .piste{background-color:var(--c-action)}
.bascule3:has(input:focus-visible) .piste{box-shadow:var(--focus-anneau)}

/* ---- L'INTERRUPTEUR ----
   Sa bille EST le marqueur, sa zone cliquable EST la hauteur de controle. */
.bascule{display:flex; align-items:center; gap:var(--e3); cursor:pointer; height:var(--h-ctrl); min-height:0}
.bascule input{position:absolute; opacity:0; width:0; height:0}
.bascule .piste{
  width:calc(var(--marq) * 2 + var(--e1) * 2);  /* 52 */
  height:calc(var(--marq) + var(--e1) * 2);     /* 30 */
  border-radius:var(--r-pilule); background:var(--c-piste);
  flex:none; padding:var(--e1); transition:background-color .2s ease;
}
.bascule .bille{
  width:var(--marq); height:var(--marq); border-radius:50%; background:#fff; display:block;
  box-shadow:0 1px 3px rgba(15,34,41,.3); transition:transform .2s ease;
}
.bascule input:checked + .piste{background:var(--c-action)}
.bascule input:checked + .piste .bille{transform:translateX(var(--marq))}
.bascule input:focus-visible + .piste{box-shadow:var(--focus-anneau)}

/* ---- LE SEGMENT, aligne sur la hauteur de controle ---- */
.seg{display:inline-flex; height:var(--h-ctrl); border:var(--ep-bord) solid var(--c-bord);
  border-radius:var(--r-pilule); overflow:hidden}
.seg button{
  height:100%; min-height:0; padding:0 var(--e4); border:0; background:var(--c-papier); cursor:pointer;
  font:var(--g-fort) var(--t-petit)/var(--lh-nu) var(--f-titre); color:var(--c-action); white-space:nowrap;
  border-right:var(--ep-bord) solid var(--c-bord);
}
.seg button:last-child{border-right:0}
.seg button.on{background:var(--c-action); color:#fff}
.seg--plein{display:flex; width:100%}
.seg--plein button{flex:1 1 0}

/* ---- LA RANGEE D'OUTILS ----
   La rangee impose sa hauteur a ses occupants : c'est ce qui rend
   l'alignement automatique, sans qu'un ecran ait a y penser. */
/* ALIGNES PAR LE HAUT, PAS CENTRES, 23 septembre 2026. « align-items:center »
   centrait chaque champ sur sa propre hauteur : un champ portant une phrase
   d'aide sous lui est plus haut, donc son libelle ET sa saisie remontaient.
   Mesure au navigateur sur l'ecran Journal : « Jusqu'au » sortait 15 px
   au-dessus des quatre autres, sur une rangee censee etre droite.
   « flex-start » aligne les LIBELLES, qui sont ce que l'oeil suit. */
.rangee-filtres{display:flex; align-items:center; gap:var(--e3); flex-wrap:wrap; row-gap:var(--e2)}
/* « center » EST LA VALEUR DE LA MAQUETTE, mesuree le 23 septembre 2026.
   Le « flex-start » pose plus tot corrigeait un vrai defaut de l'ecran
   Journal, ou le champ « Jusqu'au », seul a porter un texte d'aide, sortait
   15 px plus haut que ses voisins. Mais il le corrigeait en changeant la
   rangee ENTIERE, donc tous les ecrans, pour un cas qui n'en concerne
   qu'un. La rangee reprend la valeur de la maquette et le cas du texte
   d'aide se traite la ou il est, juste dessous. */
.rangee-filtres > .champ:has(.aide){align-self:flex-start}
/* ... ET ON COMPENSE LA HAUTEUR DU TEXTE D'AIDE. « flex-start » seul ne
   suffisait pas : il calait le champ par le haut, si bien que sa SAISIE
   remontait de la hauteur de l'aide au lieu de s'aligner sur les autres.
   Ce qui doit etre droit, c'est la rangee des saisies, pas le bord des
   conteneurs. On rend donc au champ la place que son aide prend dessous. */
/* 24 px : la difference de hauteur MESUREE entre un champ qui porte une
   aide (98 px) et un champ nu (74 px), sur l'ecran Journal. La formule en
   jetons donnait 20 px et laissait 2 px de decalage, parce qu'elle
   oubliait la gouttiere de la grille du champ en plus de celle du texte. */
.rangee-filtres > .champ:has(.aide){margin-top:24px}
/* Les controles qui n'ont pas de libelle au-dessus (recherche, bouton) se
   recalent sur la hauteur de saisie des autres, sinon ils flotteraient en
   haut de la rangee. */
.rangee-filtres > .rech,
.rangee-filtres > .btn,
.rangee-filtres > .filtres{align-self:center}
.rangee-filtres > .ui-field ~ .f{align-self:flex-end}
.rangee-filtres > .rech,
.rangee-filtres > .saisie,
.rangee-filtres > .champ > .saisie{height:var(--h-ctrl)}
.filtres{display:flex; gap:var(--e2); flex-wrap:wrap}
.tri{display:flex; align-items:center; gap:var(--e3); flex-wrap:wrap; color:var(--c-doux)}

/* ================== 5. LES ACTIONS ==================
   Le bouton rejoint la hauteur de controle : sans cela une rangee
   d'outils garde 3px d'ecart entre son bouton et son champ. */

.btn{
  display:inline-flex; align-items:center; justify-content:center; gap:var(--e2);
  height:var(--h-ctrl); min-height:0; padding:0 var(--e5); border-radius:var(--r-pilule);
  font:var(--g-fort) var(--t-texte)/var(--lh-nu) var(--f-titre);
  border:var(--ep-bord) solid var(--c-bord); cursor:pointer; text-decoration:none;
  white-space:nowrap; background:var(--c-papier); color:var(--c-action);
  transition:transform .12s ease;
}
.btn:active{transform:translateY(1px)}
.btn--primaire{background:var(--c-action); color:#fff; border-color:var(--c-action)}
.btn--primaire:hover{background:var(--c-action-fonce)}
.btn--accent{background:var(--c-accent); color:var(--c-accent-contraste); border-color:var(--c-accent)}
.btn--accent:hover{background:var(--c-accent-vif)}
.btn--discret{background:transparent; border-color:transparent; color:var(--c-action)}
.btn--discret:hover{background:var(--c-action-voile)}
.btn--danger{background:var(--c-papier); color:var(--c-erreur-encre); border-color:#C88AB9}
.btn--danger:hover{background:var(--c-erreur-voile)}
.btn[disabled]{background:var(--c-surface-2); color:#5A6A70; border-color:var(--c-trait);
  cursor:not-allowed; opacity:1}
/* Deux densites, et deux seulement. Le petit garde la hauteur de
   controle et change de rembourrage ; le mini est la densite reduite. */
.btn--petit{height:var(--h-ctrl); padding:0 var(--e4); font-size:var(--t-petit)}
.btn--mini{height:var(--h-ctrl-s); padding:0 var(--e3); font-size:var(--t-mini)}
.btn.charge{position:relative; color:transparent!important; pointer-events:none}
.btn.charge .ic{visibility:hidden}
.btn.charge::after{
  content:""; position:absolute; width:17px; height:17px; border-radius:50%;
  border:2.5px solid rgba(255,255,255,.35); border-top-color:#fff;
  animation:tourne .7s linear infinite;
}
.btn:not(.btn--primaire):not(.btn--accent).charge::after{
  border-color:rgba(14,84,100,.25); border-top-color:var(--c-action);
}
@keyframes tourne{to{transform:rotate(360deg)}}

/* ---- LE BOUTON A ICONE, une seule taille, au-dessus du seuil tactile ---- */
.ic-btn,.cel-menu,.produit-ouvre{
  width:var(--h-ctrl-s); height:var(--h-ctrl-s); border-radius:var(--r-s); border:0; padding:0;
  background:transparent; cursor:pointer; display:grid; place-items:center;
  color:var(--c-doux);
}
.ic-btn:hover,.produit-ouvre:hover{background:var(--c-action-voile); color:var(--c-action)}
.ic-btn--danger:hover{background:var(--c-erreur-voile); color:var(--c-erreur-encre)}
/* EXCEPTION nommee : les trois points du menu de ligne sont un dessin,
   pas un mot. Leur ecart separe les points, il n'espace pas du texte. */
.cel-menu{justify-self:end; font:var(--g-fort) var(--t-fort)/var(--lh-nu) var(--f-titre); letter-spacing:.12em}
.cel-menu:hover{background:var(--c-surface-2); color:var(--c-action)}
.cel-actions{justify-self:end; display:flex; align-items:center; gap:2px}
/* Un bouton a icone pose DANS une ligne de texte garde sa cible de 36px
   mais ne pousse pas la ligne : il deborde optiquement, il ne grossit pas. */
.cel-nom .ic-btn{margin:calc((var(--t-texte) * var(--lh-texte) - var(--h-ctrl-s)) / 2) 0}

.lien-mini{
  border:0; background:transparent; cursor:pointer; padding:0;
  font:var(--g-fort) var(--t-petit)/var(--lh-nu) var(--f-titre);
  color:var(--c-action); text-decoration:underline;
}
.lien-mini:hover{color:var(--c-accent-encre)}
.tbl .lien-mini,.produit-nom{text-decoration:none}
.tbl .lien-mini:hover,.tbl .lien-mini:focus-visible,a.produit-nom:hover,a.produit-nom:focus-visible{text-decoration:underline}

.retour{
  display:inline-flex; align-items:center; gap:var(--e2); height:var(--h-ctrl-s);
  font:var(--g-fort) var(--t-petit)/var(--lh-nu) var(--f-titre); color:var(--c-action);
  text-decoration:none; border:0; background:transparent; cursor:pointer; padding:0;
  width:fit-content;
}
.retour:hover{color:var(--c-accent-encre)}

/* ---- LA PASTILLE, la pilule et l'onglet : hauteur de controle ---- */
.statut,.org-bouton,.fpil,.pastille-filtre{
  display:inline-flex; align-items:center; gap:var(--e2); white-space:nowrap;
  height:var(--h-ctrl); min-height:0; padding:0 var(--e4); border-radius:var(--r-pilule);
  border:var(--ep-bord) solid var(--c-bord); background:var(--c-papier); cursor:pointer;
  font:var(--g-fort) var(--t-petit)/var(--lh-nu) var(--f-titre); color:var(--c-action);
}
.statut{height:auto; min-height:32px; padding:var(--e1) var(--e3); background:transparent;
  border-color:transparent; color:var(--c-encre); font-weight:var(--g-fort)}
.statut:hover{background:var(--c-action-voile); border-color:transparent}
.statut .quoi{color:var(--c-doux); font-weight:var(--g-normal)}
.statut .pt{width:9px; height:9px; border-radius:50%; flex:none; background:currentColor}
/* Teal quand ca compte, rose quand ca attend, gris quand c'est ferme. */
.statut--ouverte{color:var(--c-action)}
.statut--ouvre{color:var(--c-accent-encre)}
.statut--fermee{color:var(--c-doux)}
.statut--ouverte .quoi,.statut--ouvre .quoi,.statut--fermee .quoi{color:var(--c-doux)}

/* Le selecteur d'organisation parle la langue du champ de recherche : meme
   hauteur, meme aplat doux, meme survol. */
.org-bouton{height:var(--h-ctrl-barre); min-width:0; padding:0 var(--e3); border-color:transparent;
  background:var(--c-surface-2); color:var(--c-encre);
  transition:border-color var(--transition), background-color var(--transition)}
button.org-bouton:hover{border-color:var(--c-trait); background:var(--c-papier)}
/* Le focus se dessine dans la boite, comme celui du champ voisin. */
.org-bouton:focus-visible{outline:none; border-color:var(--focus); box-shadow:inset 0 0 0 1px var(--focus)}
/* Sans liste d'organisations, c'est un simple nom : rien ne doit promettre un clic. */
span.org-bouton{cursor:default}
.org-bouton .ui-icone{width:var(--ic-petit); height:var(--ic-petit); color:var(--c-doux)}
.nom-org{overflow:hidden; text-overflow:ellipsis; max-width:18ch}

.fpil:hover{border-color:var(--c-action-vif)}
.fpil.on{background:var(--c-action); border-color:var(--c-action); color:#fff}
.fpil .n{
  font-family:var(--f-code); background:var(--c-surface-2);
  color:var(--c-doux); padding:2px var(--e2); border-radius:var(--r-pilule);
}
.fpil.on .n{background:rgba(255,255,255,.2); color:#fff}

.pastille-filtre{border-color:var(--c-action); background:var(--c-action-voile); cursor:default}
.pastille-filtre .x{
  border:0; background:transparent; cursor:pointer; color:inherit;
  line-height:1; padding:0 2px; display:grid; place-items:center;
}

.pastille{
  grid-column:3; justify-self:end; position:relative;
  width:var(--h-ctrl-barre); height:var(--h-ctrl-barre); flex:none; border-radius:50%; cursor:pointer; padding:0;
  display:grid; place-items:center; border:0;
  background:var(--c-action); color:var(--sur-action); text-decoration:none;
  font:var(--g-fort) var(--t-mini)/var(--lh-nu) var(--f-titre);
  transition:background-color var(--transition);
}
/* La cible tactile reste a 48 px autour d'un disque de 32 px. */
.pastille::after{content:""; position:absolute; inset:calc((var(--h-ctrl-barre) - var(--h-ctrl)) / 2); border-radius:50%}
.pastille:hover{background:var(--c-action-fonce)}
/* Anneau interieur vif puis lisere clair : lisible sur le disque sombre sans deborder. */
.pastille:focus-visible{outline:none; box-shadow:inset 0 0 0 2px var(--focus), inset 0 0 0 4px var(--c-papier)}

/* ================== 6. L'OSSATURE DE L'ECRAN ================== */

/* Trois colonnes, les deux cotes egaux : la recherche se centre dans la
   fenetre et non dans la place que lui laissent le logo et la pastille. Un
   cote plus large que sa part la decentre plutot que de la chevaucher. */
.barre{
  position:sticky; top:0; z-index:60;
  display:grid; grid-template-columns:minmax(max-content,1fr) minmax(0,480px) minmax(max-content,1fr);
  align-items:center; gap:var(--e4);
  height:var(--h-barre); padding:0 var(--e4);
  background:var(--c-papier); border-bottom:var(--k-trait);
}
.barre-gauche{display:flex; align-items:center; gap:var(--e3); min-width:0}
.barre-marque{display:flex; align-items:center; gap:var(--e2); flex:none; text-decoration:none}
.barre-marque svg{display:block}

.cadre{display:grid; grid-template-columns:var(--l-menu) minmax(0,1fr); min-height:calc(100vh - var(--h-barre))}
.menu{background:var(--c-action-fonce); padding:var(--e4) var(--e3);
  display:grid; gap:var(--e5); align-content:start}
@media (min-width:1081px){
  .menu{position:sticky; top:var(--h-barre); align-self:start;
    height:calc(100vh - var(--h-barre)); overflow-y:auto}
}
.menu-grp{display:grid; gap:2px}
.menu-grp .label{color:#fff; opacity:.62; padding:0 var(--e3) var(--e1)}
.menu-a{
  display:flex; align-items:center; gap:var(--e3); min-height:44px; padding:var(--e3);
  border-radius:var(--r-s); color:var(--c-sur-fonce); text-decoration:none; cursor:pointer;
  font-family:var(--f-texte); border:0; background:transparent;
  /* 13 px, valeur mesuree sur la maquette : l'application rendait 15 px.
     Un menu lateral se parcourt du regard, il ne se lit pas.
     LA GRAISSE RESTE NORMALE, et c'est le point corrige le 23 septembre
     2026 : posee a 600 ici, elle mettait TOUS les items en gras, si bien
     que l'item actif n'avait plus que sa pastille rose pour se distinguer.
     La maquette ne met en gras que « .menu-a.on ». */
  font-size:var(--t-petit); font-weight:var(--g-normal);
  text-align:left; width:100%;
}
.menu-a:hover{background:rgba(255,255,255,.09); color:#fff}
.menu-a.on{background:var(--c-accent); color:var(--c-accent-contraste); font-weight:var(--g-fort)}
.menu-a .ic{opacity:.92}
.menu-a.on .ic{opacity:1}

.page-tete{display:flex; justify-content:space-between; gap:var(--e4); flex-wrap:wrap; align-items:flex-start}
.page-tete .fil{color:var(--c-doux); margin-bottom:var(--e1)}
.contexte{display:flex; align-items:baseline; flex-wrap:wrap; gap:var(--e2) var(--e3); color:var(--c-doux)}
.contexte h1{margin-right:var(--e2)}
.contexte{width:100%}
.sortie-tete{margin-left:auto; display:flex; align-items:center; gap:var(--e2); flex:none}

/* ---- LA BARRE D'ACTIONS ----
   Elle colle en bas : on n'a jamais a remonter pour enregistrer. */
.barre-actions{
  position:sticky; bottom:var(--e3); z-index:5;
  display:flex; align-items:center; justify-content:space-between;
  gap:var(--e3); flex-wrap:wrap; padding:var(--k-retrait-serre);
}
/* Sans etat-sauve a gauche, les boutons restent a droite comme sur les fiches
   (au-dessus de 720px : en colonne, la barre les etire deja sur toute la largeur). */
@media (min-width:721px){ .barre-actions > .f:only-child{margin-left:auto} }
.etat-sauve{color:var(--c-doux)}
.etat-sauve.modifie{color:var(--c-anis-encre); font-weight:var(--g-fort)}
.etat-sauve.sauve{color:var(--c-action); font-weight:var(--g-fort)}

/* ================== 7. LES TABLEAUX ET LES LISTES ================== */

.tbl-tete,.tbl-l{
  display:grid;
  grid-template-columns:minmax(0,1.15fr) minmax(0,1.15fr) minmax(0,1.3fr) 40px;
  gap:var(--e3) var(--e4); align-items:start;
}
.tbl.sans-asso .tbl-tete,.tbl.sans-asso .tbl-l{grid-template-columns:minmax(0,1.2fr) minmax(0,1.3fr) 40px}
.tbl.sans-asso .col-asso{display:none}
@media (min-width:721px){ .tbl--format .tbl-tete,.tbl--format .tbl-l{grid-template-columns:minmax(0,1fr) minmax(0,1.5fr)} }
/* Une liste de colonnes CSV est du texte courant : en flex elle ne revenait pas a la ligne. */
.tbl--format .cel-nom{display:block}
.col-chef{min-width:0}
.tbl--repertoire .tbl-tete,.tbl--repertoire .tbl-l,
.tbl--repertoire.sans-asso .tbl-tete,.tbl--repertoire.sans-asso .tbl-l{
  grid-template-columns:minmax(0,1.35fr) minmax(0,1.2fr) max-content max-content;
}
/* Magasins : un magasin, puis ses rattachements. Chef et association sont par collecte,
   ils vivent donc dans le rattachement, pas dans une colonne qui ignore la collecte. */
#tbl-magasins .tbl-tete,#tbl-magasins .tbl-l{
  grid-template-columns:minmax(0,1fr) minmax(0,1.9fr) max-content;
}
#tbl-magasins .tbl-l > .cel{align-self:start}
/* Un rattachement sur une ligne : la collecte, son association, son chef.
   Les trois colonnes sont alignees d'un rattachement a l'autre. */
#tbl-magasins .rattach{
  grid-template-columns:minmax(0,1fr) minmax(0,1fr) minmax(0,1fr);
  gap:var(--e3); align-items:baseline;
}
#tbl-magasins .rattach + .rattach{margin-top:var(--e2); padding-top:var(--e2)}
#tbl-magasins .rattach-legs{display:contents}
#tbl-magasins .rattach-leg{display:block}
#tbl-magasins .rattach-leg .k{display:none}
#tbl-magasins .tbl-tete .sous{
  display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1fr) minmax(0,1fr);
  gap:var(--e3);
}
@media (max-width:760px){
  #tbl-magasins .rattach{grid-template-columns:minmax(0,1fr)}
  #tbl-magasins .rattach-leg .k{display:block}
  #tbl-magasins .tbl-tete .sous{display:none}
}
.tbl--repertoire .tbl-l > .cel{align-self:center}
.tbl--coche .tbl-tete,.tbl--coche .tbl-l,
.tbl--coche.sans-asso .tbl-tete,.tbl--coche.sans-asso .tbl-l{
  grid-template-columns:26px minmax(0,1.35fr) minmax(0,1.2fr) minmax(0,.9fr) 40px;
}
.tbl--repertoire .cel-lieu,.tbl--repertoire .cel-etat{overflow-wrap:break-word}
.tbl-tete{padding-block:var(--e2)}
.tbl-l > .cel-menu,.tbl-l > .cel-actions{align-self:center}
.tbl-tete .ouvre{
  display:inline-flex; align-items:center; gap:var(--e2); border:0; background:transparent;
  cursor:pointer; padding:0; font:inherit; color:inherit; letter-spacing:inherit;
}
.tbl-tete .ouvre:hover{color:var(--c-action)}

.cel{display:grid; gap:var(--e1); min-width:0}
.cel-nom{display:flex; align-items:center; gap:var(--e2)}
.cel-lieu,.cel-etat{color:var(--c-doux)}
.cel-ref{color:var(--c-doux)}
.cel-etat.att{color:var(--c-anis-encre); font-weight:var(--g-fort)}
.cel-etat.ko{color:var(--c-erreur-encre); font-weight:var(--g-fort)}
/* Confirme se dit en teal, comme l'etat Ouverte d'une collecte : meme sens, meme couleur.
   Le vert menthe n'appartient pas a l'identite. */
.cel-etat.ok{color:var(--c-action); font-weight:var(--g-fort)}
.cel-vide{color:var(--c-doux)}
/* Rattacher : le leg manquant du triplet est un bouton, pas un mot mort.
   Meme hauteur que les autres controles compacts du tableau. */
.cel-rattacher{
  justify-self:start; display:inline-flex; align-items:center; gap:var(--e2);
  height:var(--h-ctrl-s); padding-inline:var(--e3);
  border:var(--ep-bord) dashed var(--c-trait); border-radius:var(--r-s);
  background:transparent; cursor:pointer; color:var(--c-action);
  font:var(--g-fort) var(--t-petit)/var(--lh-nu) var(--f-titre);
}
.cel-rattacher:hover{border-style:solid; border-color:var(--c-action); background:var(--c-surface-2)}
.cel .lien-mini{justify-self:start; text-align:left}
.cel-manque{color:var(--c-anis-encre); font-weight:var(--g-fort); font-size:var(--t-petit)}
.cel-collecte{font-family:var(--f-titre); color:var(--c-encre)}
/* Un rattachement : la collecte nomme la ligne, l'association qu'elle porte est dessous.
   Empiler deux collectes puis une seule association ne disait pas laquelle allait avec laquelle. */
.rattach{display:grid; gap:var(--e1)}
/* La bande de collecte groupe les rattachements d'une fiche. Sur une fiche on ne
   repete jamais l'entite regardee : la ligne porte les deux autres legs du triplet. */
.bande-collecte{
  display:flex; align-items:center; gap:var(--e3);
  background:var(--c-surface-2); padding:var(--k-retrait-serre);
  border-top:var(--ep-bord) solid var(--c-trait);
}
.bande-collecte b{font-family:var(--f-titre); font-size:var(--t-fort)}
.bande-collecte .statut{margin-left:auto}
.ratt-l{display:grid; gap:var(--e3) var(--e4); padding:var(--k-retrait);
  grid-template-columns:repeat(auto-fit,minmax(0,1fr)) ; align-items:center}
.ratt-l + .ratt-l{border-top:var(--ep-bord) solid var(--c-trait)}
.ratt-c{display:grid; gap:var(--e1); min-width:0; align-self:start}
.ratt-c .k{font-size:var(--t-mini); color:var(--c-doux); text-transform:uppercase; letter-spacing:.04em}
.ratt-c .v{font-size:var(--t-texte)}
.ratt-l .lien-mini{justify-self:end}
@media (max-width:760px){
  .ratt-l{grid-template-columns:minmax(0,1fr)}
  .ratt-l .lien-mini{justify-self:start}
}
.rattach + .rattach{margin-top:var(--v-dedans); padding-top:var(--v-dedans); border-top:var(--ep-bord) solid var(--c-trait)}
.rattach-legs{display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:var(--e2) var(--e3); margin-top:var(--e1)}
.rattach-leg{display:grid; gap:2px; min-width:0}
.rattach-leg .k{font-size:var(--t-mini); color:var(--c-doux); text-transform:uppercase; letter-spacing:.04em}
@media (max-width:760px){.rattach-legs{grid-template-columns:minmax(0,1fr)}}

.tbl-pied{display:flex; align-items:center; justify-content:space-between; gap:var(--e3);
  flex-wrap:wrap; color:var(--c-doux)}
.pages{display:flex; align-items:center; gap:var(--e1)}
.pages button{
  min-width:var(--h-ctrl-s); height:var(--h-ctrl-s); border-radius:var(--r-s); border:0; cursor:pointer;
  background:transparent; color:var(--c-action);
  font:var(--g-fort) var(--t-petit)/var(--lh-nu) var(--f-titre);
}
.pages button:hover{background:var(--c-action-voile)}
.pages button.on{background:var(--c-action); color:#fff}

/* ---- LES CARTES DE LISTE ---- */
.collecte{display:grid; gap:var(--v-dedans); position:relative;
  transition:border-color .15s ease, box-shadow .15s ease}
.collecte-nom-lien{text-decoration:none}
/* Lien etire : le ::after du titre couvre la carte, une seule destination.
   Les liens internes (A faire, Direct) passent au-dessus vers la leur. */
.collecte-nom-lien::after{content:""; position:absolute; inset:0; border-radius:var(--k-rayon)}
.collecte .afaire-l,.collecte .btn{position:relative; z-index:1}
.collecte:hover{border-color:var(--c-action-vif); box-shadow:var(--ombre)}
/* Soulignement seulement si c'est bien la carte qui agit, pas un lien interne. */
.collecte:hover:not(:has(.afaire-l:hover,.btn:hover)) .collecte-nom-lien{text-decoration:underline}
.collecte-nom-lien:focus-visible{outline:none}
.collecte:has(.collecte-nom-lien:focus-visible){box-shadow:var(--focus-anneau)}

.collecte-jour{color:var(--c-doux)}

.collecte-compte{display:flex; gap:var(--e3); flex-wrap:wrap}
.collecte-compte span{
  display:flex; flex-direction:column-reverse; gap:2px;
  padding:var(--e3) var(--e4); border-radius:var(--r-s);
  background:var(--c-action-voile); min-width:88px;
  font:var(--t-mini)/var(--lh-petit) var(--f-texte); color:var(--c-doux);
}
.collecte-compte b{font:var(--g-fort) var(--t-page)/var(--lh-nu) var(--f-texte); font-variant-numeric:tabular-nums; color:var(--c-action-fonce)}

.afaire{display:grid; gap:var(--e1)}
.afaire-l{
  display:grid; grid-template-columns:minmax(0,1fr) 22px; gap:var(--e3); align-items:center;
  min-height:44px; padding:var(--e2) var(--e3); border-radius:var(--r-s);
  border:0; background:transparent; cursor:pointer; text-align:left; width:100%;
  font:var(--t-petit)/var(--lh-petit) var(--f-texte); color:var(--c-encre);
}
.afaire-l:hover{background:var(--c-accent-voile)}
.afaire-l .ic{justify-self:center}
.afaire-l b{font-weight:var(--g-fort)}

.ligne{display:grid; grid-template-columns:minmax(0,1.3fr) minmax(0,1fr) minmax(0,1fr) auto;
  gap:var(--e3); align-items:center}
/* Colonne « auto » : une ligne a une seule icone decalait ses colonnes par rapport aux autres. */
.ligne > .cel-actions{min-width:calc(2 * var(--h-ctrl-s) + 2px)}
.ligne .doux{color:var(--c-doux)}

.faits{display:grid; grid-template-columns:repeat(auto-fit,minmax(170px,1fr)); gap:var(--v-dedans)}
.fait{display:grid; gap:var(--e1); min-width:0; align-content:start}
.fait .v code.lien-court{display:block; white-space:nowrap; overflow:hidden; text-overflow:ellipsis}
/* Une adresse ou une URL n'a pas d'espace ou couper : sans break-word
   elle depasse sa colonne. Mesure : 190px dans 176px, sur tablette. */
.fait .v{overflow-wrap:anywhere}
.pied-fiche{display:grid; gap:var(--e2); padding-top:var(--k-retrait); border-top:var(--k-trait)}

/* ================== 8. BADGES, JAUGES, CHIFFRES, ALERTES ================== */

.badge{
  display:inline-flex; align-items:center; gap:var(--e2); white-space:nowrap;
  font-family:var(--f-titre); font-weight:var(--g-fort);
  padding:var(--e2) var(--e3); border-radius:var(--r-pilule); border:1px solid transparent;
}
.badge .pt{width:7px; height:7px; border-radius:50%; background:currentColor; flex:none}
/* Les etats se disent en teal et en rose, jamais en vert : hors identite. */
.b-ok{background:var(--c-action-voile); color:var(--c-action); border-color:var(--c-action)}
.b-att{background:var(--c-accent-voile); color:var(--c-accent-encre); border-color:var(--c-accent-vif)}
.b-ko{background:var(--c-erreur-voile); color:var(--c-erreur-encre); border-color:#C88AB9}
.b-neu{background:var(--c-action-voile); border-color:#A8D4DA}
.b-off{background:var(--c-surface-2); border-color:var(--c-trait)}

.jauge{display:grid; gap:var(--e2)}
.jauge-piste{height:13px; border-radius:var(--r-pilule); background:var(--c-voile); overflow:hidden}
.jauge-part{height:100%; border-radius:var(--r-pilule);
  background:linear-gradient(90deg,var(--c-action-vif),var(--c-accent-vif))}
.jauge-l{display:flex; justify-content:space-between; gap:var(--e3); color:var(--c-doux)}
.jauge-l b{color:var(--c-encre); font-family:var(--f-texte);
  font-size:var(--t-texte); font-weight:var(--g-normal); font-variant-numeric:tabular-nums}

.chiffre .v{font:var(--g-fort) clamp(26px,4.4vw,38px)/1.1 var(--f-texte);
  font-variant-numeric:tabular-nums; letter-spacing:var(--ls-normal); color:var(--c-action-fonce)}
.chiffre .s{color:var(--c-doux)}
.chiffre--vedette .k{color:#A8D4DA}
.chiffre--vedette .v{color:#fff; font-size:clamp(32px,6vw,52px)}
.chiffre--vedette .s{color:var(--c-sur-fonce)}

.alerte{display:grid; grid-template-columns:26px minmax(0,1fr); gap:var(--e3);
  padding:var(--k-retrait); border-radius:var(--k-rayon); border:1px solid; align-items:start}
.alerte h4{font-size:var(--t-texte); line-height:var(--lh-texte);
  text-transform:none; letter-spacing:var(--ls-normal); color:inherit; margin-bottom:var(--e1)}
.alerte .f{margin-top:var(--e3)}
.a-info{background:var(--c-action-voile); border-color:#A8D4DA; color:#0A404D}
.a-ok{background:var(--c-succes-voile); border-color:var(--c-succes); color:var(--c-succes-encre)}
.a-att{background:var(--c-alerte-voile); border-color:#FF9DAF; color:var(--c-alerte-encre)}
.a-err{background:var(--c-erreur-voile); border-color:#C88AB9; color:var(--c-erreur-encre)}

.danger .f{gap:var(--e3)}

.vide{text-align:center; padding:var(--e6) var(--e4); border:2px dashed var(--c-bord);
  border-radius:var(--k-rayon); background:var(--c-surface-2)}
.vide h4{text-transform:none; letter-spacing:var(--ls-normal);
  color:var(--c-encre); margin:var(--e3) 0 var(--e2)}
.vide .f{justify-content:center; margin-top:var(--k-retrait)}

.mot{display:grid; gap:var(--e3); justify-items:center; text-align:center;
  padding:var(--e6) var(--e4); color:var(--c-doux); font-size:var(--t-petit)}
.mot b{color:var(--c-encre)}

/* ================== 9. LE STATUT ==================
   Pas de bandeau, pas de cadre : une ligne, comme un reglage.
   Elle se pose en haut du Suivi parce qu'on y revient tous les jours,
   la ou les vrais reglages se touchent une fois. */

.ligne-statut{
  display:flex; align-items:center; gap:var(--e4); flex-wrap:wrap;
  padding:var(--e3) 0;
}
.ligne-statut:not(:last-child){border-bottom:var(--k-trait)}
.ligne-statut .quoi{display:flex; align-items:baseline; gap:var(--e3); flex-wrap:wrap; min-width:0}
.ligne-statut b{font-family:var(--f-titre); font-weight:var(--g-fort)}
/* L'explication de la position choisie, une seule a la fois, collee au libelle. */
.ligne-statut .dit-statut{display:inline}
.ligne-statut .cran-statut{margin-left:calc(var(--e2) - var(--e4)); font-family:var(--f-titre);
  font-weight:var(--g-fort); white-space:nowrap}
.ligne-statut .bascule3{margin-left:0}

/* ================== 10. LES REGLAGES ================== */

.reglage{display:flex; align-items:center; justify-content:flex-start; gap:var(--e4);
  flex-wrap:wrap; padding:var(--e3) 0; border-bottom:var(--k-trait)}
label.reglage{cursor:pointer}
.reglage:last-child,.reglage:has(+ noscript){border-bottom:0}
.reglage > .saisie{width:auto; max-width:100%}
.reglage-ouvre{
  display:flex; align-items:center; gap:var(--e3); width:100%; height:var(--h-ctrl);
  padding:0; border:0; border-bottom:var(--k-trait);
  background:transparent; cursor:pointer; text-align:left; color:var(--c-encre);
}
.reglage-ouvre:last-child{border-bottom:0}
.reglage-ouvre:hover{color:var(--c-action)}
.reglage-ouvre .n{margin-left:auto; font-weight:var(--g-normal);
  font-family:var(--f-texte); color:var(--c-doux)}

.rangee-date{display:flex; align-items:flex-end; gap:var(--e3); flex-wrap:wrap}
.rangee-date + .rangee-date{margin-top:var(--e4)}
.rangee-date .champ{flex:1 1 180px; min-width:0}

.depot{border:2px dashed var(--c-bord); border-radius:var(--k-rayon); padding:var(--e5) var(--e4);
  text-align:center; background:var(--c-surface-2); cursor:pointer; display:grid;
  gap:var(--e2); justify-items:center; transition:border-color .15s ease, background-color .15s ease}
.depot:hover{border-color:var(--c-accent); background:var(--c-accent-voile)}
.depot.rempli{border-style:solid; border-color:var(--c-succes-encre); background:var(--c-succes-voile)}
.depot b{font-family:var(--f-titre); font-weight:var(--g-fort)}
.depot span{color:var(--c-doux)}
.depot input{position:absolute; opacity:0; width:0; height:0}

/* ================== 11. FAMILLES, PRODUITS, LOGOS ================== */

.famille-tete{display:flex; align-items:center; gap:var(--e3); flex-wrap:wrap}
.famille-tete .n{color:var(--c-doux); text-transform:none; letter-spacing:var(--ls-normal)}
.famille.eteinte .produit-nom{color:var(--c-doux)}

.produit{display:grid; grid-template-columns:20px var(--h-ctrl-s) 36px minmax(0,1fr) auto auto;
  gap:var(--e3); align-items:center}
.produit-poignee{color:var(--c-doux); cursor:grab; font-size:var(--t-texte);
  line-height:var(--lh-nu); user-select:none}
.produit-image{width:36px; height:36px; border-radius:var(--r-s); background:var(--c-voile);
  display:grid; place-items:center; color:var(--c-action); flex:none}
.produit-image.sans{background:var(--c-surface-2); border:1px dashed var(--c-bord); color:var(--c-bord)}
.produit-tailles{color:var(--c-doux); white-space:nowrap}

/* Fausse ligne de produit : meme rythme et memes colonnes que la liste,
   mais le pictogramme et le libelle indiquent clairement l'ajout. */
.produit-ajout{
  background:var(--c-papier); color:var(--c-action);
  text-decoration:none; border-bottom:0;
  transition:background-color .15s ease, color .15s ease;
}
.produit-ajout:hover{background:var(--c-surface-2); color:var(--c-action-fonce)}
.produit-ajout:focus-visible{position:relative; z-index:1; outline:none; box-shadow:var(--focus-anneau)}
.produit-ajout-icone{
  width:36px; height:36px; border-radius:var(--r-s);
  display:grid; place-items:center; flex:none;
  background:var(--c-papier); border:1px dashed var(--c-action); color:currentColor;
}
.produit-ajout-icone .ic{width:var(--ic-petit); height:var(--ic-petit)}
.produit-ajout-texte{
  justify-self:start; font-family:var(--f-titre); font-size:var(--t-petit);
  font-weight:var(--g-fort); text-decoration:underline;
  text-decoration-color:color-mix(in srgb, currentColor 45%, transparent);
  text-underline-offset:3px;
}

.compte{width:26px; height:26px; flex:none; border-radius:50%; cursor:pointer; padding:0;
  border:2px solid var(--c-bord); background:var(--c-papier); display:grid; place-items:center}
.compte::after{content:""; width:12px; height:12px; border-radius:50%; background:transparent}
.compte[aria-pressed="true"]{border-color:var(--c-action)}
.compte[aria-pressed="true"]::after{background:var(--c-action)}
.compte:hover{border-color:var(--c-action-vif)}

.tailles{grid-column:1/-1; display:grid; gap:var(--e1); margin:var(--e2) 0 var(--e3) 76px}
.taille{display:grid; grid-template-columns:minmax(0,1fr) 26px 90px; gap:var(--e3);
  align-items:center; padding:var(--e2) var(--e3); border-radius:var(--r-s);
  background:var(--c-surface-2)}
.taille .poids{color:var(--c-doux); text-align:right}

.legende{display:flex; gap:var(--e4); flex-wrap:wrap; color:var(--c-doux)}
.legende span{display:inline-flex; align-items:center; gap:var(--e2); white-space:nowrap}

.pastilles-rang{display:grid; grid-template-columns:repeat(auto-fill,minmax(132px,1fr)); gap:var(--e2)}
.pastille-c{display:grid; gap:var(--e1); padding:var(--e3); cursor:pointer; position:relative;
  background:var(--c-papier); border:var(--ep-bord) solid var(--c-trait); border-radius:var(--r-s);
  transition:border-color .15s ease}
.pastille-c:hover{border-color:var(--c-action-vif)}
.pastille-c .pc{height:34px; border-radius:var(--r-marq); border:1px solid rgba(0,0,0,.12)}
.pastille-c .pc-n{font-family:var(--f-titre); font-weight:var(--g-fort); font-size:var(--t-petit)}
.pastille-c .pc-v{color:var(--c-doux)}

.logos{display:grid; grid-template-columns:repeat(auto-fill,minmax(160px,1fr)); gap:var(--e3)}
.logo-c{display:grid; gap:var(--e2); justify-items:center; padding:var(--k-retrait) var(--e3);
  border:var(--k-trait); border-radius:var(--k-rayon); background:var(--c-papier); text-align:center}
.logo-c .vig{width:100%; height:64px; border-radius:var(--r-s); background:var(--c-surface-2);
  display:grid; grid-template:100% / 100%; place-items:center; color:var(--c-bord); overflow:hidden}
.logo-c .vig img{display:block; width:100%; height:100%; min-height:0; object-fit:contain; padding:var(--e2)}
.logo-c b{font-family:var(--f-titre); font-weight:var(--g-fort);
  font-size:var(--t-petit); line-height:var(--lh-petit); color:var(--c-encre)}
.logo-c .url{overflow-wrap:break-word}

.lien-cadre{display:flex; align-items:center; gap:var(--e3); flex-wrap:wrap;
  padding:var(--k-retrait-serre); border-radius:var(--r-s);
  background:var(--c-surface-2); border:var(--k-trait)}
.lien-cadre code{color:var(--c-action); word-break:break-all; flex:1 1 220px;
  font-size:var(--t-petit); line-height:var(--lh-petit)}

.mention{display:inline-flex; align-items:center; gap:var(--e2);
  font-family:var(--f-titre); font-weight:var(--g-fort); color:var(--c-doux);
  padding:var(--e2) var(--e3); border-radius:var(--r-pilule); background:var(--c-surface-2);
  border:var(--k-trait)}
.mention b{color:var(--c-action-fonce)}

/* ================== 12. FEUILLES ET MENUS ================== */

.voile{position:fixed; inset:0; z-index:80; background:rgba(8,50,60,.42);
  display:grid; place-items:start center; padding:80px var(--e4) var(--e4); overflow:auto}
.voile[hidden]{display:none}
.feuille{width:min(440px,100%)}
.feuille--large{width:min(720px,100%)}
.feuille-tete{display:flex; align-items:center; justify-content:space-between; gap:var(--e3)}
.feuille-pied{display:flex; align-items:center; justify-content:flex-end; gap:var(--e2)}
.feuille-l{display:flex; align-items:center; gap:var(--e3); width:100%; min-height:56px;
  border-left:0; border-right:0; border-top:0;
  background:transparent; cursor:pointer; text-align:left;
  font-family:var(--f-texte); color:var(--c-encre)}
.feuille-l:hover{background:var(--c-action-voile)}
.feuille-l.on{font-weight:var(--g-fort)}
.feuille-l .coche{width:18px; flex:none; color:var(--c-action); display:grid; place-items:center}
.feuille-l .doux{color:var(--c-doux)}

.menu-perso{position:fixed; top:64px; right:var(--e4); z-index:70; width:min(280px,calc(100vw - 32px))}
.menu-perso[hidden]{display:none}
.menu-perso .tete{padding:var(--k-retrait-serre); border-bottom:var(--k-trait)}
.menu-perso .tete b{display:block; font-family:var(--f-titre); font-weight:var(--g-fort)}
.menu-perso .tete span{color:var(--c-doux)}
.menu-perso a,.menu-perso button{
  display:flex; align-items:center; gap:var(--e3); width:100%; height:var(--h-ctrl);
  padding:0 var(--e4); border:0; border-bottom:var(--k-trait); background:transparent;
  cursor:pointer; text-align:left; font:var(--t-texte)/var(--lh-nu) var(--f-texte);
  color:var(--c-encre); text-decoration:none;
}
/* La deconnexion est un formulaire POST : son bouton est le dernier
   element du menu, mais le dernier ENFANT est le <form>. */
.menu-perso > :last-child,.menu-perso > form:last-child > button{border-bottom:0}
.menu-perso a:hover,.menu-perso button:hover{background:var(--c-action-voile)}
.menu-perso .ic,.menu-perso .ui-icone{width:var(--ic-petit); height:var(--ic-petit); color:var(--c-action)}

/* ================== 13. LE JOURNAL ================== */

.jour-groupe{padding:var(--k-retrait-serre); background:var(--c-surface-2);
  border-bottom:var(--k-trait)}
.jour-l{display:grid; grid-template-columns:60px minmax(0,1fr) 22px; gap:var(--e3);
  padding:var(--k-retrait-serre); border-bottom:var(--k-trait); align-items:start;
  width:100%; border-left:0; border-right:0; border-top:0;
  background:transparent; cursor:pointer; text-align:left; font-family:var(--f-texte)}
.jour-l:last-child,.jour-l:has(+ .tbl-pied){border-bottom:0}
.jour-l:hover{background:var(--c-accent-voile)}
.jour-l .h{color:var(--c-doux)}
.jour-l .qui{font-family:var(--f-titre); font-weight:var(--g-fort);
  font-size:var(--t-petit); line-height:var(--lh-petit)}
.jour-l .quoi{color:var(--c-doux)}
.jour-l .val{margin-top:2px}
.jour-l .val .fl{color:var(--c-accent-encre); margin:0 var(--e2)}
.jour-l .ic{align-self:center; justify-self:center}

/* ================== RESPONSIVE ==================
   Le rythme se resserre, il ne disparait pas : la hierarchie doit
   survivre au telephone. */

@media (max-width:1080px){
  /* grid-template-rows:auto 1fr, sinon le min-height du cadre etire la
     rangee du menu sur les ecrans courts et la barre devient enorme. */
  .cadre{grid-template-columns:1fr; grid-template-rows:auto 1fr}
  .menu{display:flex; overflow-x:auto; gap:var(--e2); padding:var(--e3);
    align-content:initial; align-items:center;
    scrollbar-width:none; -webkit-overflow-scrolling:touch;
    position:sticky; top:var(--h-barre); z-index:50}
  /* Le degrade ne parait que si .menu deborde reellement, voir
     indiquerMenuDefilable dans ui.js : sans lui, un menu qui tient deja en
     entier affichait quand meme une ombre de scroll a droite. Bug
     signale par Jeromin le 24 septembre 2026, et manque a l'appel ici,
     le premier correctif n'ayant touche que le menu du kit generique
     (templates/kit/nav.html.twig), pas ce menu-ci, le seul rendu par le
     chassis admin (templates/socle/menu-admin.html.twig). */
  .menu.menu--defilable{
    -webkit-mask-image:linear-gradient(to right,#000 calc(100% - 32px),transparent);
    mask-image:linear-gradient(to right,#000 calc(100% - 32px),transparent)}
  .menu::-webkit-scrollbar{display:none}
  .menu-grp{display:flex; gap:var(--e2); flex:none}
  .menu-grp .label{display:none}
  .menu-grp + .menu-grp{border-left:1px solid rgba(255,255,255,.18);
    padding-left:var(--e3); margin-left:var(--e2)}
  .menu-a{white-space:nowrap; flex:none; width:auto}
}

@media (max-width:860px){
  .g2,.g3,.g4{grid-template-columns:1fr}
  .tbl-tete{display:none}
  .tbl-l,.tbl.sans-asso .tbl-l,
  .tbl--repertoire .tbl-l,.tbl--repertoire.sans-asso .tbl-l{
    grid-template-columns:minmax(0,1fr) 40px; row-gap:var(--e2)}
  .tbl--coche .tbl-l,.tbl--coche.sans-asso .tbl-l{
    grid-template-columns:26px minmax(0,1fr) 40px; row-gap:var(--e2)}
  .tbl-l > .cel{grid-column:1/2}
  .tbl-l > .cel-actions{grid-column:2/3; grid-row:1}
  .tbl--coche .tbl-l > .cel{grid-column:2/3}
  .tbl--coche .tbl-l > .cel-actions{grid-column:3/4; grid-row:1}
  .ligne{grid-template-columns:minmax(0,1fr); row-gap:var(--e1)}
  .produit{grid-template-columns:20px var(--h-ctrl-s) minmax(0,1fr) auto auto; row-gap:var(--e2)}
  .produit-image{display:none}
  .tailles{margin-left:var(--e5)}
}


/* ================================================================ *
 * LES DEUX ECHELLES DE TAILLE DE LA MAQUETTE
 *
 * MESURE DU 23 SEPTEMBRE 2026, au navigateur, maquette contre prod a
 * 1440px : quarante-huit selecteurs sur cinquante-cinq divergeaient, et
 * l'essentiel venait d'ici. La maquette porte DEUX regles chapeaux qui
 * donnent leur taille a toute une famille de classes ; le portage a
 * amene les classes et leur couleur, jamais ces deux regles. Resultat,
 * .cel-etat, .cel-ref, .tbl-pied, .att sortaient a 15px au lieu de 12,
 * .cel-lieu, .tri, .contexte et les paragraphes a 15px au lieu de 13.
 *
 * ELLES SONT POSEES ICI, EN FIN DE FEUILLE, et c'est voulu : une classe
 * qui redefinit deja sa taille plus haut garderait la sienne si on les
 * mettait en tete, alors que la maquette, elle, les applique en bloc.
 * ================================================================ */

.u-mini,.cel-etat,.aide,.chiffre .s,.mention,.badge,.poids,.legende,
.jour-l .val,.logo-c .url,.par-page,.tbl-pied,.menu-perso .tete span,
.depot span,.collecte-compte,.jauge-l,.fpil .n,.mini{
  font-size:var(--t-mini); line-height:var(--lh-mini)}

.doux,.sep,.quoi,.dit,.option,.cel-lieu,.cel-collecte,.case .txt,
.bascule .txt,.alerte p,.jour-l .quoi,.menu-perso a,.menu-perso button,
.lien-cadre code,.menu-a,.fil,.collecte-jour,.tri,.contexte,.ligne,
.feuille-l .doux,.taille,.famille-tete .n,.produit-tailles,
.reglage-ouvre .n,.petit{
  font-size:var(--t-petit); line-height:var(--lh-petit)}

/* LA REGLE GENERIQUE « .ui-mode-admin p » EST PLUS SPECIFIQUE QUE LE
   GROUPE CI-DESSUS pour un sélecteur simple comme « .fil » ou
   « .collecte-jour » : classe+balise bat une classe seule. Un <p class=
   "collecte-jour"> ressortait donc a 15px, la maquette le veut a 13px.
   Mesure au navigateur le 25 septembre 2026. */
p.doux,p.fil,p.tri,p.collecte-jour{font-size:var(--t-petit); line-height:var(--lh-petit)}

/* MEME PIEGE DE SPECIFICITE, POUR LE TITRE DE FEUILLE : « .ui-mode-admin
   h2 » (classe+balise) bat « .ui-feuille-titre » (classe seule) du groupe
   ci-dessus. Le <h2 class="ui-feuille-titre"> ressortait a 22px, la
   maquette le veut en petites capitales de 12px comme .feuille-tete h2.
   Mesure au navigateur le 25 septembre 2026. */
h2.ui-feuille-titre{
  font:var(--g-fort) var(--t-mini)/var(--lh-mini) var(--f-titre);
  letter-spacing:var(--ls-capitale); text-transform:uppercase;
  color:var(--c-doux); margin:0}

/* LA CHASSE FIXE DE LA MAQUETTE, meme motif : .cel-ref porte une
   reference de magasin, elle se lit en mono et en 12px. */
code,.mono,.cel-ref,.jour-l .h,.pastille-c .pc-v,.taille .poids,
.logo-c .url{
  font-family:var(--f-code); font-size:var(--t-mini);
  letter-spacing:var(--ls-normal); color:var(--c-doux)}




/* LE RYTHME VERTICAL DE LA MAQUETTE, ENFIN BRANCHE.
   Mesure du 23 septembre 2026 : la maquette rend <main class="page">, une
   grille au gap de 24 px qui porte tout son rythme vertical et ses regles
   de resserrage (.rangee-filtres + .tbl, .tri + *...). Le chassis rendait
   un <main> NU : aucune de ces regles n'atteignait un seul ecran, et
   chaque bloc retombait sur ses marges par defaut. Le gabarit pose
   desormais la classe.
   LE PADDING RESTE A .ui-contenu, qui le portait deja : le cumuler aurait
   double la gouttiere de tous les ecrans d'un coup. */
.ui-contenu > .page{padding:0}


/* « #contenu > * + * » NE S'APPLIQUE PLUS A LA GRILLE DE LA MAQUETTE.
   Mesure du 23 septembre 2026. Cette regle de proximite posait 24 px entre
   tous les freres de <main id="contenu">, ce qui etait juste tant que le
   conteneur etait un bloc. Depuis que l'admin rend <main class="page">, une
   GRILLE au gap de 24 px, les deux se cumulent : l'espace est le bon par
   hasard, mais la marge etant posee APRES dans la feuille, elle ecrase tout
   le resserrage de la maquette (.tri + *, .rangee-filtres + .tbl, la
   remontee du .contexte sous le titre). Mesure avant : .tbl, .tri,
   .contexte et .rangee-filtres sortaient tous a 24 px la ou la maquette
   rend -8, -8, -16 et 0.
   LE PUBLIC N'EST PAS TOUCHE : son <main id="contenu"> ne porte pas .page,
   il garde donc la regle de proximite qui lui donne son rythme. */
#contenu.page > * + *{margin-top:0}
/* ... mais PAS sur les relations que la maquette resserre : elles sont
   redonnees juste apres, et cette neutralisation, posee plus haut dans la
   cascade, ne doit pas leur passer devant a specificite egale. */

/* Puis on redonne SEULEMENT les resserrages de la maquette, que le gap de
   la grille ne peut pas exprimer : un gap est uniforme, ces trois relations
   sont plus serrees que lui. */
#contenu.page > .rangee-filtres + .tbl,
#contenu.page > .rangee-filtres + .bloc,
#contenu.page > .rangee-filtres + .famille,
#contenu.page > .filtres + .tbl,
#contenu.page > .rangee-filtres + .filtres,
#contenu.page > .rangee-filtres + .tri,
#contenu.page > .filtres + .tri,
#contenu.page > .tri + *{margin-top:calc(var(--v-dedans) - var(--v-entre))}
/* LE DEPLIANT DE FILTRES TIENT LE ROLE DE .rangee-filtres. La maquette
   pose ses filtres a plat et resserre « .rangee-filtres + .tri » ; le
   produit les range dans un <details class="bloc"> repliable, que la
   maquette ne connait pas. La RELATION est la meme, le tri commente les
   filtres qui le precedent : il se resserre donc pareil. */
#contenu.page > details.bloc + .tri{margin-top:calc(var(--v-dedans) - var(--v-entre))}
/* LE SPRITE EST UN FRERE INVISIBLE : « .retour + .contexte » ne suffit
   pas, le <svg> des icones s'intercale entre les deux. On vise donc AUSSI
   la relation reelle, « premier element visible de la grille », qui est
   celle que la maquette decrit. */
#contenu.page > svg[aria-hidden="true"] + .contexte,
#contenu.page > svg[aria-hidden="true"] + .page-tete,
#contenu.page > .retour + .contexte,
#contenu.page > .retour + .page-tete,
#contenu.page > .contexte + .ui-onglets,
#contenu.page > .page-tete + .ui-onglets{margin-top:calc(var(--v-colle) - var(--v-entre))}
/* Pas de marge basse propre : le gap de la grille suffit. Mesure du
   24 septembre 2026 : 24 px au lieu de 8 au-dessus, 40 au lieu de 24 en dessous. */
#contenu.page > .ui-onglets{margin-bottom:0}
#contenu.page > p{margin-bottom:0}
#contenu.page > .danger{margin-top:var(--v-colle)}


/* LE SPRITE D'ICONES NE COMPTE PAS COMME UN FRERE.
   Kit::sprite() pose un <svg> invisible (width:0;height:0) en tete du
   contenu : il porte les <symbol> que chaque icone reference. Invisible a
   l'oeil, il est bien present dans le DOM, donc dans « + » et « > * + * ».
   Mesure du 23 septembre 2026 : il s'intercalait entre le titre et le
   .contexte, si bien que « .retour + .contexte » ne s'appliquait jamais et
   que le contexte ne remontait pas sous son titre.
   On le sort de la GRILLE, pas du DOM : display:none sur un sprite empeche
   certains navigateurs de resoudre les <use> qui le referencent. En
   « position:absolute » il ne cree plus de piste de grille et n'est plus un
   frere pour le rythme vertical, tout en restant resolvable. */
#contenu.page > svg[aria-hidden="true"]{position:absolute; width:0; height:0}


/* L'ECHELLE DES ETIQUETTES, troisieme regle chapeau de la maquette.
   Mesure du 23 septembre 2026 sur la fiche magasin : les libelles de champ
   sortaient a 15 px en graisse 400, la maquette les rend a 13 px en 600.
   La maquette pose d'abord une regle commune a toutes les etiquettes
   (12 px capitales espacees), puis rend a « .champ label » sa casse
   normale et son corps de 13 px : un libelle de formulaire se lit, il ne
   se survole pas comme un en-tete de colonne. */
h4,.etiquette,.label,.champ label,.k,.tbl-tete > *,.fait .k,.jour-groupe,
.feuille-tete h2,.danger h3,.famille-tete h3,.bloc-tete h2,details.bloc > summary h2,.chiffre .k,
.ui-feuille-titre{
  font:var(--g-fort) var(--t-mini)/var(--lh-mini) var(--f-titre);
  letter-spacing:var(--ls-capitale); text-transform:uppercase;
  color:var(--c-doux); margin:0}

.champ label{
  text-transform:none; letter-spacing:var(--ls-normal);
  font-size:var(--t-petit); line-height:var(--lh-petit);
  color:var(--c-encre)}

/* LE TITRE DE LA ZONE DE DANGER PORTE L'ENCRE D'ERREUR, pas le gris des
   autres etiquettes : c'est le seul bloc dont l'intitule doit arreter
   l'oeil avant que la main ne clique. */
.danger h3{color:var(--c-erreur-encre)}
.famille-tete h3{color:var(--c-encre)}


/* LE FIL D'ARIANE S'EFFACE DEVANT LE LIEN « RETOUR ». La maquette n'a pas
   de fil : chaque ecran de second niveau porte un .retour vers son parent.
   Les gabarits qui ont deja leur .retour affichaient les deux, l'un sous
   l'autre (mesure du 24 septembre 2026 sur l'ecran Liens). Le fil reste
   pour les ecrans qui n'ont pas encore leur .retour, et disparait des que
   la page en pose un. */
.ui-contenu:has(> .page > .retour) > .ui-fil{display:none}


/* CE QUI EST UNE PILULE OU UN CONTROLE N'EST PAS UN LIEN DE TEXTE. La
   maquette les dessine en <button> ; le produit en fait des <a> parce
   qu'ils naviguent, et le navigateur les souligne alors par defaut.
   Mesure du 24 septembre 2026 sur l'ecran Magasins. .lien-mini reste
   souligne hors des tableaux. */
.statut,.fpil,.pastille-filtre,.org-bouton,.reglage-ouvre,.retour,.afaire-l{text-decoration:none}

/* LE DEPLIANT DE FILTRES, absent de la maquette, prend l'habit de son
   en-tete de bloc : un <summary> nu sortait en liste a puce avec un titre de
   22 px, et son triangle au-dessus du mot. */
details.bloc > summary{
  display:flex; align-items:center; gap:var(--e2); padding:var(--k-retrait-serre);
  background:var(--c-surface-2); cursor:pointer; list-style:none; user-select:none}
details.bloc > summary::-webkit-details-marker{display:none}
details.bloc > summary::after{
  content:""; width:var(--ic-petit); height:var(--ic-petit); margin-left:auto; flex:none;
  background:var(--u-chevron) center/var(--ic-petit) no-repeat; transition:transform .15s}
details.bloc[open] > summary{border-bottom:var(--k-trait)}
details.bloc[open] > summary::after{transform:rotate(180deg)}
details.bloc > summary h2{margin:0}
details.bloc > :not(summary){padding:var(--k-retrait)}

/* UN FIL D'ARIANE SANS AUCUN LIEN NE MENE NULLE PART : sur un ecran de
   premier niveau il repete le titre juste dessous. La maquette n'a pas de
   fil ; il ne reste que la ou il donne un chemin de retour. */
.ui-fil:not(:has(a)){display:none}

/* LA CARTE DE COLLECTE, regles de la maquette qui n'avaient pas suivi le
   HTML (motif des classes orphelines au portage, mesure du 24 septembre
   2026) : le titre de carte tombait en etiquette de 12 px. */
.collecte-nom,.carte-titre{
  font:var(--g-fort) var(--t-titre)/var(--lh-titre) var(--f-titre);
  letter-spacing:var(--ls-normal); color:var(--c-encre); margin:0}
.collecte-jour{color:var(--c-doux); margin:0}
@media (max-width:720px){
  :root{--v-entre:16px; --v-dedans:12px; --v-colle:8px; --k-retrait:var(--e3);
        --k-retrait-serre:var(--e3)}
  /* Recherche sur sa propre ligne ; --h-barre suit, le menu sticky s'y cale. */
  :root{--h-barre:calc(var(--e3) * 2 + var(--h-ctrl-barre) * 2 + var(--e2) + 1px)}
  .barre{grid-template-columns:minmax(0,1fr) auto; gap:var(--e2); padding:var(--e3)}
  .barre > .pastille{grid-column:2; grid-row:1}
  .barre > .barre-rech{grid-column:1 / -1; grid-row:2; max-width:none}
  .page{padding:var(--e3)}
  .barre-actions{flex-direction:column; align-items:stretch; bottom:0;
    border-radius:var(--k-rayon) var(--k-rayon) 0 0}
  .barre-actions .btn{flex:1 1 auto}
  .barre-actions > form > .btn{width:100%}
  .taille{grid-template-columns:minmax(0,1fr) 26px}
  .taille .poids{display:none}
}

@media (max-width:480px){
  h1{font-size:26px}
}
