/* =========================================================
   UI MOLECULES
   Habillage posé sur les composants Bootstrap (dropdown, card,
   accordion) — structure/comportement viennent de Bootstrap,
   ces classes ne portent que le visuel de marque.
   ========================================================= */

/* ---- Sélecteur de langue (Bootstrap Dropdown) ---- */
.ui-language-picker { position: relative; }

.ui-language-toggle {
  min-width: 54px;
  justify-content: center;
  gap: var(--space-2);
}

/* Bootstrap réinitialise fond/texte sur .btn:hover/:focus/:active/.show
   (spécificité 2 classes) au-dessus de .ui-utility-badge (1 classe) :
   on rétablit ici le fond vert avec une spécificité équivalente. */
.ui-language-toggle:hover,
.ui-language-toggle:focus,
.ui-language-toggle:active,
.ui-language-toggle.show {
  background: var(--color-dark-soft);
  color: var(--white);
  border-color: transparent;
}

/* Variante outline (panneau de navigation mobile, sur fond clair) : le chevron
   suit la couleur du texte — l'or, calibré pour la barre verte, manquerait de
   contraste sur blanc. */
.ui-language-toggle.ui-utility-badge--outline::after { color: currentColor; }

.ui-language-toggle::after {
  content: 'expand_more';
  font-family: 'Material Symbols Outlined';
  font-size: var(--fs-base);
  color: var(--on-dark-muted);
  border: 0;
  margin-left: 0;
  vertical-align: -3px;
  transition: transform 0.2s ease;
}

.ui-language-toggle[aria-expanded="true"]::after {
  transform: rotate(180deg);
}

/* LE MENU DE GESTION D'UN BILLET emprunte au sélecteur de langue son PANNEAU
   — cadre, rayon, ombre, rembourrage — et rien d'autre : ses entrées sont
   celles du MENU VERTICAL du socle, « .nav.flex-column.ui-side-nav », posé
   tel quel dans le gabarit. C'est là que vit la typographie d'une liste de
   commandes ; la refaire ici en aurait produit une seconde.
   Seule la largeur diffère du sélecteur de langue : un intitulé comme
   « Afficher pour mes followers » ne tient pas dans les 150px d'un code. */
.ui-menu-actions-menu,
.ui-publication-menu,
.ui-fil-filtrer-menu,
.ui-language-menu {
  width: 150px;
  padding: var(--space-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--white);
  box-shadow: var(--shadow-md);
}

.ui-menu-actions-menu,
.ui-publication-menu { width: 250px; }
.ui-menu-actions-menu--large { width: 320px; }

.ui-language-item {
  width: 100%;
  padding: var(--space-3) var(--space-3);
  border: 0;
  border-radius: var(--radius-sm);
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  display: flex;
  color: var(--color-dark);
  background: transparent;
  font: var(--fw-semibold) var(--fs-caption) / var(--lh-base) var(--font-base);
  text-align: left;
  transition: color 0.2s ease, background 0.2s ease;
}

.ui-language-item strong {
  color: var(--color-primary);
  font-size: var(--fs-caption);
  letter-spacing: 0.1em;
}

.ui-language-item:hover,
.ui-language-item:focus-visible,
.ui-language-item.is-active {
  color: var(--color-dark);
  background: var(--surface-douce);
}

.ui-language-item.is-active::before {
  content: '';
  width: 5px;
  height: 5px;
  flex: 0 0 auto;
  border-radius: 50%;
  background: var(--color-primary);
}

.ui-language-item.is-active span { margin-right: auto; }

/* ---- Bouton panier ---- */
.ui-header-cta {
  align-items: center;
  gap: var(--space-2);
  color: var(--white);
  background: var(--color-dark-soft);
  height: 40px;
  border: 1px solid var(--color-dark-soft);
  border-radius: var(--radius-pill);
  padding: 0 var(--space-3);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  display: inline-flex;
  transition: background-color 0.2s ease;
}

.ui-header-cta:hover { background: var(--color-dark-soft); color: var(--white); }
.ui-header-cta svg { flex-shrink: 0; }@media (width <= 560px) {
  .ui-header-cta-label { display: none; }
  .ui-header-cta { gap: var(--space-2); padding: 0 var(--space-3); }
}/* ---- Carte engagement (quality-grid) ---- */
.ui-carte {
  background: var(--surface-card);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);   /* $card-border-radius */
  padding: var(--space-3);           /* $card-spacer-y / $card-spacer-x */
  display: flex;
  flex-direction: column;
  position: relative;
}

.ui-carte .ui-card-title { margin-bottom: var(--space-2); }  /* $card-title-spacer-y */

.ui-carte p {
  color: var(--text-muted);
  font-size: var(--fs-sm);
  line-height: var(--lh-base);
}

.ui-carte p + p { margin-top: 1.75em; }

.ui-carte > a {
  color: var(--color-primary);
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  display: inline-flex;
  align-self: flex-start;
  margin-top: auto;
  padding-top: var(--space-4);
  transition: color 0.2s ease, transform 0.2s ease;
}

.ui-carte > a:hover {
  color: var(--color-primary-hover);
  transform: translateX(2px);
}

/* ---- Carte savoir-faire ---- */
/* Variante numérotée : hérite de .ui-carte, ne redéclare que ses écarts.
   Seule la hauteur plancher la distingue — c'est le numéro qui la signale,
   pas un ornement. */
.ui-carte--numerotee {
  min-height: 230px;
}

.ui-carte--numerotee .ui-card-title { color: var(--color-titre); }

/* ---- Carte témoignage (carrousel) ----
   Même famille que les cartes engagement / savoir-faire : fond beige,
   filet clair et cadre doré en retrait. Citation puis nom, centrés et
   groupés au milieu de la carte (justify-content) : la hauteur mini
   garde le carrousel stable d'une vue à l'autre sans creuser un vide
   sous le nom. ---- */
.ui-testimonial-card {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 240px;
  margin: 0;
  padding: var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface-card);
  position: relative;
}

.ui-testimonial-quote {
  /* Plafond de largeur : sur une carte 12 colonnes, une citation qui
     court sur toute la laize ne se lit plus comme centrée. */
  max-width: 720px;
  margin: 0 auto;
  text-align: center;
  color: var(--color-dark-soft);
  font-family: var(--font-display);
  font-size: var(--fs-h4);
  font-style: italic;
  font-weight: var(--fw-medium);
  line-height: 1.4;
}

.ui-testimonial-quote p { margin: 0; line-height: inherit; }

.ui-testimonial-name {
  padding-top: var(--space-3);
  text-align: center;
  color: var(--color-primary);
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  letter-spacing: 0.1em;
}

@media (width <= 560px) {
  .ui-testimonial-card { padding: var(--space-4) var(--space-4); }
  .ui-testimonial-quote { font-size: var(--fs-lead); }
}

/* ---- Carte guide/article ----
   Même panneau "verre dépoli" que le paragraphe d'intro des
   sections .ui-showcase (voir ui-organismes.css). Ces deux
   éléments sont internes à leur colonne : posé sur la colonne
   elle-même, ce padding remplacerait celui de la gouttière et la
   carte déborderait des lignes de grille. ---- */
.ui-showcase-lead {
  padding: var(--space-4) var(--space-4);
  border: 1px solid var(--border-on-dark);
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--white) 6%, transparent);
  box-shadow: inset 0 1px color-mix(in srgb, var(--white) 6%, transparent);
  backdrop-filter: blur(5px);
}

/* Un lien de texte dans le chapô suit la taille du texte qui l’entoure.
   (Descendu depuis la couche organismes : la racine du sélecteur est cette
   molécule, la règle lui appartient.) */
.ui-showcase-lead .ui-text-link { font-size: inherit; }

.ui-article-card {
  border: 1px solid var(--border);
  background: var(--surface-card);
  border-radius: var(--radius-sm);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  position: relative;
  height: 100%;
}

.ui-article-card--featured {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0;
}

.ui-article-art {
  height: 220px;
  color: var(--color-dark);
  background: var(--surface-appuyee);
  place-items: center;
  overflow: hidden;
  display: grid;
}

.ui-article-art img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.ui-article-card--featured .ui-article-art {
  height: 100%;
  min-height: 300px;
}

.ui-article-art--alt { color: var(--color-primary); background: var(--surface-appuyee); }

.ui-article-content {
  padding: var(--space-4);
  flex: 1;
  display: flex;
  flex-direction: column;
}

.ui-article-card--featured .ui-article-content { padding: var(--space-4); }

.ui-article-content > a {
  margin-top: auto;
  align-self: flex-start;
  color: var(--color-primary);
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  display: inline-flex;
  transition: color 0.2s ease, transform 0.2s ease;
}

.ui-article-content > a:hover {
  color: var(--color-primary-hover);
  transform: translateX(2px);
}

.ui-article-kicker {
  color: var(--color-primary);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  margin: var(--space-3) 0;
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  display: block;
}

.ui-article-content .ui-card-title { margin-bottom: var(--space-3); }

@media (width <= 560px) {
  .ui-article-card--featured { display: block; }
  .ui-article-card--featured .ui-article-art {
    height: 220px;
    min-height: 0;
    margin-bottom: var(--space-4);
  }
}

/* ---- Panneau service B2B (Bootstrap .card) ---- */
/* ---- Dialogue d'information du configurateur ----
   Porté depuis l'ancien assets/css/molecules.css (.product-info-dialog), qui
   n'était chargé que via le hub style.css. Élément natif <dialog> : le fond
   est posé par ::backdrop, sans conteneur supplémentaire. ---- */
.ui-info-dialog {
  width: min(560px, calc(100% - var(--space-5)));
  padding: var(--space-3);           /* $modal-inner-padding */
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  color: var(--color-dark);
  background: var(--white);
  box-shadow: var(--shadow-md);
}

.ui-info-dialog::backdrop { background: color-mix(in srgb, var(--shadow-color) 40%, transparent); backdrop-filter: blur(3px); }

.ui-info-dialog h2 {
  padding-right: var(--space-5);
  margin-bottom: var(--space-3);
  font: var(--fw-medium) var(--fs-h4) / var(--lh-heading) var(--font-display);
}

.ui-info-dialog p + p { margin-top: var(--space-3); }

.ui-steps-panel {
  padding: var(--space-4);
  border: 1px solid var(--border-on-dark-strong);
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--white) 6%, transparent);
  box-shadow: inset 0 1px color-mix(in srgb, var(--white) 12%, transparent), var(--shadow-photo);
  backdrop-filter: blur(9px);
}

.ui-steps-panel-label {
  margin-bottom: var(--space-4);
  color: var(--on-dark-muted);
  font-size: var(--fs-caption);
  font-weight: var(--fw-bold);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.ui-steps-panel ul {
  margin: 0 0 var(--space-4);
  list-style: none;
  padding: 0;
}

.ui-steps-panel li {
  min-height: 52px;
  border-bottom: 1px solid color-mix(in srgb, var(--white) 12%, transparent);
  align-items: center;
  gap: var(--space-3);
  display: flex;
}

.ui-steps-panel li:first-child {
  border-top: 1px solid color-mix(in srgb, var(--white) 12%, transparent);
}

.ui-steps-panel li > span {
  color: var(--on-dark-muted);
  font-size: var(--fs-caption);
  font-weight: var(--fw-bold);
  letter-spacing: 0.1em;
}

.ui-steps-panel li strong {
  color: var(--white);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
}

/* ---- Accordéon FAQ ----
   Accordéon Bootstrap natif : la boîte, les filets partagés entre items, le
   chevron et son animation viennent tous de bootstrap.min.css. Ce bloc ne
   fait que rebrancher les couleurs et les espacements du système sur les
   variables --bs-accordion-*, sans déplacer la géométrie.

   Pas de data-bs-parent : plusieurs questions peuvent rester ouvertes en
   même temps — dans une FAQ, on compare souvent deux réponses. */
.ui-faq-list {
  --bs-accordion-color: var(--text);
  --bs-accordion-bg: var(--white);
  --bs-accordion-border-color: var(--border);
  --bs-accordion-border-radius: var(--radius-sm);
  --bs-accordion-btn-padding-y: var(--space-3);      /* $accordion-padding-y */
  --bs-accordion-btn-padding-x: 1.25rem;             /* $accordion-padding-x */
  --bs-accordion-btn-color: var(--text);
  --bs-accordion-btn-bg: var(--white);
  --bs-accordion-btn-focus-box-shadow: 0 0 0 0.25rem var(--focus-ring);
  --bs-accordion-active-color: var(--color-primary);
  --bs-accordion-active-bg: var(--surface-douce);
  --bs-accordion-body-padding-y: var(--space-3);
  --bs-accordion-body-padding-x: 1.25rem;
}

/* Chevron : Material Symbols au lieu du SVG en background-image de Bootstrap.
   Le comportement reste natif — c'est Bootstrap qui pivote l'icône à
   l'ouverture, via son propre --bs-accordion-btn-icon-transform. */
.ui-faq-list .accordion-button::after {
  content: 'expand_more';
  font-family: 'Material Symbols Outlined';
  font-size: var(--fs-lead);
  line-height: 1;
  width: auto;
  height: auto;
  background-image: none;
  color: var(--text-muted);
}

.ui-faq-list .accordion-button:not(.collapsed)::after {
  background-image: none;
  color: var(--color-primary);
}

/* Le corps occupe toute la largeur de son conteneur : c'est la colonne qui
   décide de la longueur de ligne, pas le composant. */
.ui-faq-body {
  color: var(--text-muted);
  font-size: var(--fs-base);
  line-height: var(--lh-base);
}

/* ---- Modal (Bootstrap .modal) ---- */
.ui-modal-content {
  position: relative;
  padding: var(--space-3);           /* $modal-inner-padding = $spacer */
  border: 1px solid var(--border);
  border-radius: var(--radius-md);   /* $modal-content-border-radius */
  color: var(--color-dark);
  background: var(--white);
  box-shadow: var(--shadow-md);
}

.ui-modal-content .ui-close-button {
  position: absolute;
  top: var(--space-3);
  right: var(--space-3);
}

.ui-modal-title {
  padding-right: var(--space-5);
  margin-bottom: var(--space-3);
  font: var(--fw-medium) var(--fs-h4) / var(--lh-heading) var(--font-display);
}

.ui-modal-body p + p { margin-top: var(--space-3); }

/* ---- Pagination : liens Bootstrap habillés via ses variables
   (--bs-pagination-*) — vert pour l'état actif / survol, fond neutre pour
   le bouton inactif. ---- */
.ui-pagination {
  --bs-pagination-color: var(--color-dark);
  --bs-pagination-bg: var(--white);
  --bs-pagination-border-color: var(--border);
  --bs-pagination-border-radius: var(--radius-sm);
  --bs-pagination-hover-color: var(--white);
  --bs-pagination-hover-bg: var(--color-dark-soft);
  --bs-pagination-hover-border-color: var(--color-dark-soft);
  --bs-pagination-focus-color: var(--color-dark);
  --bs-pagination-focus-bg: var(--border);
  --bs-pagination-focus-box-shadow: none;
  --bs-pagination-active-color: var(--white);
  --bs-pagination-active-bg: var(--color-dark);
  --bs-pagination-active-border-color: var(--color-dark);
  --bs-pagination-disabled-bg: var(--surface-douce); /* bouton inactif : fond neutre, distinct du blanc des pages cliquables */
  --bs-pagination-font-weight: var(--fw-semibold);
}

/* ---- Stepper (tunnel de commande) ----
   Pastilles numérotées reliées par un trait qui se colore au fur et à mesure :
   le segment qui MÈNE à une étape franchie ou en cours passe au vert, ce qui
   dessine la progression sans avoir à teinter les étapes à venir.
   Le trait vit sur le ::before de chaque étape sauf la première, et c'est lui
   qui absorbe la largeur restante (flex:1) — les étapes gardent donc leur
   largeur de contenu et restent lisibles quel que soit le nombre d'étapes. */
.ui-stepper-list {
  display: flex;
  align-items: center;
  min-height: 68px;
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: none;
}

.ui-stepper-step {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex: 0 0 auto;
}

.ui-stepper-step + .ui-stepper-step {
  flex: 1 1 auto; /* c'est le trait, à l'intérieur, qui prend la place */
}

.ui-stepper-step + .ui-stepper-step::before {
  content: '';
  flex: 1 1 auto;
  min-width: 20px;
  height: 1px;
  margin: 0 var(--space-3);
  background: var(--border);
}

.ui-stepper-step--done::before,
.ui-stepper-step--current::before { background: var(--color-dark); }

.ui-stepper-marker {
  flex: 0 0 auto;
  display: inline-grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border: 1px solid var(--border);
  border-radius: 50%;
  background: var(--white);
  color: var(--text-muted);
  font-size: var(--fs-caption);
  font-weight: var(--fw-bold);
  line-height: 1;
}

.ui-stepper-step--done .ui-stepper-marker {
  border-color: transparent;
  background: var(--color-dark);
  color: var(--white);
}

.ui-stepper-step--current .ui-stepper-marker {
  border-color: transparent;
  background: var(--color-primary);
  color: var(--white);
}

.ui-stepper-label {
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-size: var(--fs-caption);
  font-weight: var(--fw-semibold);
  white-space: nowrap;
  text-decoration: none;
}

.ui-stepper-step--done .ui-stepper-label { color: var(--color-dark); }
.ui-stepper-step--current .ui-stepper-label { color: var(--color-primary); font-weight: var(--fw-bold); }

a.ui-stepper-label:hover { color: var(--color-primary); text-decoration: underline; text-underline-offset: 4px; }

@media (width <= 640px) {
  /* Place comptée : seule l'étape en cours garde son libellé, les autres se
     réduisent à leur pastille — le rang et la progression restent lisibles. */
  .ui-stepper-label { display: none; }
  .ui-stepper-step--current .ui-stepper-label { display: inline; }
  .ui-stepper-step + .ui-stepper-step::before { margin: 0 var(--space-3); }
}

/* Le même repli, MAIS PLUS TÔT, pour une suite de cinq étapes : mesurée à
   869px libellés compris, elle ne tient dans aucune largeur de tablette, là où
   quatre étapes tiennent encore à 700. Le palier de 960px est celui où le
   conteneur cesse d'offrir ces 869px (960 moins les deux gouttières de 32).
   Un modificateur et non un déplacement du palier commun : à quatre étapes,
   effacer les libellés dès 960px retirerait une information que la place
   permettait encore d'afficher. */
@media (width <= 960px) {
  .ui-stepper--serre .ui-stepper-label { display: none; }
  .ui-stepper--serre .ui-stepper-step--current .ui-stepper-label { display: inline; }
  .ui-stepper--serre .ui-stepper-step + .ui-stepper-step::before { margin: 0 var(--space-3); }
}

/* SOUS 560px, le repli ne suffit plus : une pastille de 28px et quatre traits
   à 20px de large plus leurs marges font 474px de suite pour 343 disponibles —
   mesuré, et deux étapes tombaient hors du cadre. On resserre ce qui ne porte
   aucune information, la pastille, le trait et l'écart ; on garde ce qui en
   porte, le rang de chaque étape et le libellé de celle en cours.
   Réduire le CORPS du chiffre aurait été le premier réflexe : 12px est déjà le
   plus petit de l'échelle, et un rang qu'on ne lit pas ne vaut pas la place
   qu'il prend. C'est donc le cercle autour qui maigrit, pas le chiffre. */
@media (width <= 560px) {
  .ui-stepper--serre .ui-stepper-step { gap: var(--space-2); }
  .ui-stepper--serre .ui-stepper-marker { width: 24px; height: 24px; }
  .ui-stepper--serre .ui-stepper-step + .ui-stepper-step::before {
    min-width: 8px;
    margin: 0 var(--space-2);
  }
  .ui-stepper--serre .ui-stepper-label { letter-spacing: 0.04em; }
}

/* ---- Calendrier de sélection ----
   Deux mois côte à côte tant que la place le permet, empilés en dessous.
   Grille de sept colonnes égales : les en-têtes de semaine et les jours
   partagent le même gabarit, seule façon d'aligner les colonnes quel que
   soit le décalage du 1er. ---- */
.ui-calendar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
}

 /* La grille commande la largeur du mois : ses colonnes font la taille d'un
    bouton, le mois ne fait que les contenir. Sans ça, un mois étiré étirait
    les cases avec lui. */
.ui-calendar-month { flex: 0 0 auto; }

.ui-calendar-caption {
  margin-bottom: var(--space-3);
  color: var(--color-dark);
  font: var(--fw-medium) var(--fs-h5) / var(--lh-heading) var(--font-display);
}

/* Sept colonnes à la taille d'un contrôle : une case de calendrier est un
   bouton carré, elle prend donc la hauteur d'un .btn. Colonnes en largeur
   fixe plutôt qu'en 1fr — avec 1fr + aspect-ratio, la case grandissait avec
   la largeur disponible et le calendrier enflait sans raison. */
.ui-calendar-grid {
  display: grid;
  grid-template-columns: repeat(7, var(--control-h));
  align-items: start;
  gap: var(--space-1);
}

.ui-calendar-weekday {
  display: grid;
  place-items: center;
  height: var(--space-4);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-size: var(--fs-caption);
  font-weight: var(--fw-bold);
}

.ui-calendar-day {
  display: grid;
  place-items: center; /* le chiffre est centré par la grille, pas par le centrage natif du <button> */
  width: var(--control-h);
  height: var(--control-h);
  padding: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--white);
  color: var(--color-dark);
  font-family: var(--font-base);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums;
  transition: background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}

.ui-calendar-day:hover:not(:disabled) {
  border-color: var(--color-dark-soft);
  background: var(--color-dark-soft);
  color: var(--white);
}

/* JOUR DE FERMETURE — hors tournée ou férié : on ne passe pas, il n'y a rien
   à espérer. Teinte neutre (--border-soft), filet du système : le jour reste
   affiché pour que la grille du mois soit lisible, mais il s'efface. */
.ui-calendar-day:disabled {
  border-color: var(--border);
  background: var(--surface-douce);
  color: var(--text-muted);
  cursor: not-allowed;
}

/* JOUR COMPLET — on passe ce jour-là, mais la journée est prise. Ce n'est pas
   la même chose qu'une fermeture, et ça ne se lit pas pareil : couleur
   --color-danger et barré, là où la fermeture s'efface en neutre.
   Posée après la règle ci-dessus, à spécificité égale, elle l'emporte. */
.ui-calendar-day.is-full {
  border-color: var(--color-danger);
  background: var(--danger-subtle);
  color: var(--color-danger);
  text-decoration: line-through;
  text-decoration-thickness: 1px;
}

.ui-calendar-day.is-selected,
.ui-calendar-day.is-selected:hover {
  border-color: transparent;
  background: var(--color-primary);
  color: var(--white);
  box-shadow: var(--shadow-md);
}

@media (width <= 560px) {
  .ui-calendar { gap: var(--space-4); }
  .ui-calendar-month { flex: 1 1 100%; }
}

/* ---- Grille de champs ----
   Deux champs par ligne en desktop, un seul sous 768px. La gouttière
   verticale porte l'espacement entre lignes : une marge sur les champs
   déborderait de la dernière. ---- */
.ui-field-grid {
  --bs-gutter-x: 1.5rem;
  --bs-gutter-y: 1.25rem;
}

.ui-field .ui-form-label { margin-bottom: var(--space-2); }
.ui-field .ui-form-control { width: 100%; }
.ui-field .ui-form-text { margin: var(--space-2) 0 0; }

/* Une case à cocher est un champ de la grille comme un autre. Elle n'a pas de
   libellé au-dessus : on la recale sur la hauteur des champs voisins pour que
   sa ligne ne paraisse pas remontée. */
.ui-field .form-check {
  padding-left: 0;
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  min-height: 44px;
  margin: 0;
}

.ui-field .form-check-input {
  float: none;
  flex: 0 0 auto;
  margin: 2px 0 0;
  width: 20px;
  height: 20px;
}

/* L'INTERRUPTEUR garde sa forme : la case carrée de 20px juste au-dessus en
   faisait un rond, qu'on ne distinguait plus d'un bouton radio. */
.ui-field .form-switch .form-check-input {
  width: 36px;
  border-radius: var(--radius-pill);
}

.ui-field .form-check-label {
  color: var(--color-dark);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  line-height: 1.5;
}

/* ---- Carte communauté ----
   Photo de tête coiffée du lieu, corps, puis un pied en deux temps séparé par
   un filet. La carte est en colonne et le corps pousse : deux cartes voisines
   dont les textes n'ont pas la même longueur gardent leurs pieds alignés,
   à condition qu'on leur donne la même hauteur (h-100 via « fill »).

   Le préfixe est .ui-groupe- et non .ui-communaute- : ce dernier appartient
   déjà à la carte embarquée dans organismes/section-communautes. ---- */
.ui-groupe-carte {
  position: relative;                /* repère de la nappe cliquable */
  display: flex;
  flex-direction: column;
  overflow: hidden;                  /* la photo suit le rayon de la carte */
  border-radius: var(--radius);
  background: var(--surface-card);
  /* --shadow-sm au repos. La carte portait --shadow-md, qui la décollait de
     la page comme une carte isolée ; en grille de trois ou quatre, la même
     ombre répétée assombrit toute la bande et les cartes flottent au lieu de
     s'aligner. */
  box-shadow: var(--shadow-md);
  transition: box-shadow 0.2s ease, transform 0.2s ease;
}

/* La carte ENTIÈRE est cliquable, sans second lien dans le balisage : le lien
   du titre étale un pseudo-élément par-dessus toute la carte. Un seul lien
   donc, et son libellé annoncé reste le titre de la communauté — la variante
   « carte enveloppée dans un <a> » ferait lire la photo, le texte et le pied
   d'un seul tenant aux lecteurs d'écran.

   Contrepartie assumée : la nappe couvre le texte, qui ne se sélectionne plus
   à la souris. Sur une carte d'aperçu — trois lignes qu'on ne copie pas — le
   gain de cible vaut mieux que la sélection perdue.

   Rien n'est cliquable dessous : le lieu, les avatars et le compteur sont des
   <span>. Si un jour la carte gagne un bouton, il lui faudra position:relative
   et un z-index supérieur à 1 pour repasser devant la nappe. */
.ui-groupe-titre a::after {
  content: "";
  position: absolute;
  z-index: 1;
  inset: 0;
}

/* Survol et focus se lisent sur la CARTE, pas sur le seul titre : c'est elle
   qu'on vise. Les deux règles sont gardées par :has(...a) — une carte sans
   href n'est pas cliquable et ne doit donc rien promettre. */
.ui-groupe-carte:has(.ui-groupe-titre a):hover {
  box-shadow: var(--shadow-md);
  transform: translateY(-2px);
}

.ui-groupe-carte:has(.ui-groupe-titre a:focus-visible) {
  outline: 2px solid var(--color-primary);
  outline-offset: 3px;
}

/* L'anneau est porté par la carte : le garder aussi sur le lien en tracerait
   un second autour du seul titre. */
.ui-groupe-titre a:focus-visible { outline: none; }

@media (prefers-reduced-motion: reduce) {
  .ui-groupe-carte { transition: box-shadow 0.2s ease; }
  .ui-groupe-carte:has(.ui-groupe-titre a):hover { transform: none; }
}

.ui-groupe-photo {
  position: relative;                /* repère du lieu, posé par-dessus */
}

/* Le cadrage est porté par l'IMAGE, pas par son cadre. Posé sur le cadre avec
   une image en height:100%, il ne tient pas : la hauteur du cadre se déduit du
   ratio, celle de l'image du cadre — le navigateur casse le cycle en retombant
   sur la hauteur intrinsèque du fichier, et une photo carrée redevenait carrée.
   Sur l'image, la chaîne est droite : largeur → ratio → hauteur. */
.ui-groupe-photo > img {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 10;
  object-fit: cover;
}

/* Le lieu mord sur la photo. Fond plein et non voile : sur une photo claire
   comme sur une photo sombre, le libellé doit se lire sans qu'on ait à
   parier sur ce qu'il y a dessous. */
/* En haut à gauche, comme le lieu d'une carte évènement (3 oct. 2026). */
.ui-groupe-lieu {
  position: absolute;
  inset-block-start: var(--space-3);
  inset-inline-start: var(--space-3);
}


.ui-groupe-corps {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  padding: var(--space-4);
}

.ui-groupe-titre {
  margin: 0 0 var(--space-2);
  font-size: var(--fs-h5);
  font-weight: var(--fw-bold);
  line-height: var(--lh-heading);
  color: var(--color-titre);
  font-family: var(--font-titre);
}

.ui-groupe-titre a {
  color: inherit;
  text-decoration: none;
}

/* Le titre vire au primaire quand la CARTE est survolée, et non le seul
   titre : la cible étant la carte entière, la réponse doit l'être aussi. */
.ui-groupe-carte:hover .ui-groupe-titre a,
.ui-groupe-titre a:focus-visible { color: var(--color-primary); }

/* margin-bottom: auto — c'est lui qui pousse le pied en bas de la carte. */
.ui-groupe-texte {
  margin: 0 0 var(--space-4);
  color: var(--text-muted);
}

.ui-groupe-corps > .ui-groupe-texte:last-of-type { margin-bottom: auto; }

/* flex-wrap sans palier : dans une rangée de quatre, la carte tombe à ~300px
   et le pied n'a plus la place de tenir sur une ligne. Mieux vaut que la pile
   de membres passe DESSOUS, en bloc, plutôt que de laisser « Groupe public »
   se couper en deux — un libellé de deux mots ne se casse pas. */
.ui-groupe-pied {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  /* min-height et non height : ce pied est la RÉFÉRENCE dont --carte-pied-h
     tire sa valeur, il l'atteint donc déjà tout seul. Le jeton est posé ici
     pour que la référence soit lisible — et en minimum, parce que le retour
     à la ligne ci-dessus doit pouvoir le faire grandir. */
  min-height: var(--carte-pied-h);
  margin-top: var(--space-4);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border);
}

.ui-groupe-acces {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  white-space: nowrap;
  color: var(--text-muted);
  font-size: var(--fs-sm);
}

.ui-groupe-acces .material-symbols-outlined { font-size: var(--icon-md); }

.ui-groupe-membres {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

.ui-groupe-membres > span { display: inline-flex; }

/* Les avatars se chevauchent, et le liseré les détache les uns des autres :
   il reprend le fond de la carte, pas une couleur en dur — sur une carte
   d'une autre teinte, la découpe suivrait. */
/* 30px et non 34 : dans une rangée de quatre, la carte tombe à 300px et le
   pied manquait d'une vingtaine de pixels pour tenir sur une ligne. Quatre par
   jeton et huit de gouttière suffisent à les rendre — la pile reste lisible,
   et le libellé cesse de se faire pousser à la ligne suivante. */
.ui-groupe-membres img {
  width: 30px;
  height: 30px;
  margin-left: calc(var(--space-2) * -1);
  border: 3px solid var(--surface-card);
  border-radius: var(--radius-photo);
  object-fit: cover;
}

.ui-groupe-membres img:first-child { margin-left: 0; }

/* Le compteur ferme la pile : même gabarit qu'un avatar, mais plein. */
.ui-groupe-compteur {
  margin-left: var(--space-1);   /* à côté de la pile, plus dessus : le compteur n'a plus d'anneau blanc */
}

/* ---- Barre de recherche ----
   Une carte qui porte une ligne de listes déroulantes et son bouton. Les
   groupes se partagent la ligne à parts égales en xl (col-xl sans chiffre) ;
   le bouton prend la largeur qu'il lui faut (col-xl-auto) et rien de plus.

   align-items: end et non center : les groupes n'ont pas tous la même
   hauteur — un libellé sur deux lignes en rallonge un — et c'est la LIGNE DES
   COMMANDES qui doit s'aligner, pas le milieu des blocs. Centrer ferait
   flotter les <select> à des hauteurs différentes.

   Cette molécule ne connaît pas le hero : c'est l'organisme qui la superpose
   (voir .ui-hero-recherche dans ui-organismes.css). Elle tient donc seule
   dans n'importe quelle section. ---- */
/* Plus arrondie que les cartes (2 oct. 2026) : c'est un grand panneau de
   saisie posé sur le hero, pas une carte du rang. */
.ui-search-bar {
  padding: var(--space-5);
  border-radius: var(--radius-lg);
  background: var(--surface-card);
  box-shadow: var(--shadow-md);
}

.ui-search-row {
  --bs-gutter-x: 1.5rem;
  --bs-gutter-y: 1.5rem;
  align-items: end;
}

.ui-search-label {
  margin: 0 0 var(--space-2);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  color: var(--color-dark);
}

/* Un groupe peut porter DEUX listes — les deux bornes d'un âge. Elles se
   partagent la largeur du groupe : flex: 1 1 0 les rend égales quel que soit
   le contenu, et min-width: 0 les autorise à rétrécir sous leur largeur
   intrinsèque, sans quoi elles déborderaient du groupe. */
.ui-search-controls {
  display: flex;
  gap: var(--space-2);
}

.ui-search-controls > .ui-form-select {
  flex: 1 1 0;
  min-width: 0;
}

.ui-search-submit {
  width: 100%;
  white-space: nowrap;
}

@media (width <= 575px) {
  .ui-search-bar { padding: var(--space-4); }
}

/* ---- Liste (list-group) : Bootstrap pose la boîte et les filets ; on ne
   reprend que les couleurs et le rayon, via ses propres variables. ---- */
.ui-list-group {
  --bs-list-group-color: var(--text);
  --bs-list-group-bg: var(--white);
  --bs-list-group-border-color: var(--border);
  --bs-list-group-border-radius: var(--radius-sm);
  --bs-list-group-item-padding-y: var(--space-2);      /* $list-group-item-padding-y */
  --bs-list-group-item-padding-x: var(--space-3);      /* $list-group-item-padding-x */
  --bs-list-group-action-hover-bg: var(--surface-douce);
  --bs-list-group-active-color: var(--white);
  --bs-list-group-active-bg: var(--color-primary);
  --bs-list-group-active-border-color: var(--color-primary);
}

/* ---- Onglets et pastilles : deux formes du même .nav. Les onglets marquent
   un changement de volet (filet sous l'actif), les pastilles un filtre
   (fond plein). Le reste vient de Bootstrap. ---- */
.ui-nav-tabs {
  --bs-nav-tabs-border-color: var(--border);
  --bs-nav-tabs-border-radius: var(--radius-sm);
  --bs-nav-tabs-link-hover-border-color: var(--surface-appuyee) var(--surface-appuyee) var(--border);
  --bs-nav-tabs-link-active-color: var(--color-dark);
  --bs-nav-tabs-link-active-bg: var(--white);
  --bs-nav-tabs-link-active-border-color: var(--border) var(--border) var(--white);
  --bs-nav-link-color: var(--text-muted);
  --bs-nav-link-hover-color: var(--color-dark);
  --bs-nav-link-font-size: var(--fs-base);
}

.ui-nav-pills {
  --bs-nav-pills-border-radius: var(--radius-sm);
  --bs-nav-pills-link-active-color: var(--white);
  --bs-nav-pills-link-active-bg: var(--color-primary);
  --bs-nav-link-color: var(--text-muted);
  --bs-nav-link-hover-color: var(--color-dark);
  --bs-nav-link-font-size: var(--fs-base);
}

/* ---- Notification (toast) ---- */
.ui-toast {
  --bs-toast-bg: var(--white);
  --bs-toast-border-color: var(--border);
  --bs-toast-border-radius: var(--radius-sm);
  --bs-toast-header-bg: var(--surface-douce);
  --bs-toast-header-color: var(--color-dark);
  --bs-toast-color: var(--text-muted);
  --bs-toast-font-size: var(--fs-sm);
  --bs-toast-padding-y: var(--space-2);
  --bs-toast-padding-x: var(--space-3);
  --bs-toast-box-shadow: var(--shadow-md);
}

/* ---- Grille d’articles et menu vertical (descendus depuis la couche organismes :
     produits par molecules/grille-articles.snip et molecules/menu-vertical.snip) ---- */
/* ---- Menu vertical (Bootstrap Nav + utilitaire .flex-column) ----
   Bootstrap n'a pas de composant "menu vertical" séparé : c'est le
   pattern officiel .nav.flex-column + .nav-link (voir doc Bootstrap
   "Navs — Vertical"). Réutilisable au-delà du DS (ex. une future
   navigation "Mon compte"). */
/* Bootstrap pose flex-wrap:wrap sur .nav par défaut : avec beaucoup
   de liens dans une sidebar à hauteur contrainte (100vh), ça faisait
   wrapper le menu sur plusieurs colonnes côte à côte au lieu de rester
   une seule colonne scrollable — d'où l'alignement et le survol cassés.
   VRAIE cause trouvée : assets/css/organismes.css (ancien système,
   toujours chargé sur 4 des 6 pages du DS pour leurs démos) définit
   aussi un .nav brut — align-items:center + margin-left:auto — pour
   l'ancien nav du site. Comme cette classe est réutilisée telle
   quelle par le composant Bootstrap, les deux entrent en collision :
   align-items:center empêchait .nav-link de s'étirer sur toute la
   largeur (d'où le survol trop étroit) et le centrait au lieu de
   l'aligner à gauche. On neutralise les deux explicitement ici. */
.ui-side-nav {
  flex-wrap: nowrap;
  align-items: stretch;
  margin-left: 0;
  gap: 1px;
}

/* Le padding ne déplace pas la boîte du lien (posée par le flex column
   .ui-side-nav, donc toujours calée sur le bord de la colonne comme
   l'eyebrow/le bouton replier) : il ne fait qu'espacer le texte du bord
   du rectangle de survol, qui lui reste bien sur toute la largeur. */
/* ═══ TOUS LES MENUS VERTICAUX DU PRODUIT PARTAGENT CETTE RÈGLE ═══
   Il y en a trois, et ils s'étaient écrits séparément :
     · le MENU DU SOCLE — « .nav.flex-column.ui-side-nav » — qui sert les
       panneaux déroulants (gérer une publication, bloquer/signaler, compte) ;
     · le RAIL DE GAUCHE de l'espace membre — « .ui-membre-onglet » — sur le fil
       et sur les quatre pages de liste ;
     · les PANNEAUX THÉMATIQUES de l'en-tête — « .ui-membre-sous-nav-lien ».
   Trois listes verticales de liens, trois graisses, trois rayons et trois
   couleurs d'entrée active. C'est le DS qui tranche : le menu du socle est la
   référence, les deux autres s'y alignent, et les trois sont désormais dans le
   même sélecteur — la dérive n'est plus possible.

   CE QUE ÇA CHANGE, et il faut le dire parce que ça se voit :
     · graisse 400 au repos (elle était à 600 sur le rail, 500 sur les
       panneaux) ; c'est l'entrée ACTIVE qui passe à 600, et elle seule. Une
       liste dont chaque ligne est en demi-gras n'a plus de moyen de désigner
       celle où l'on est.
     · rayon 8px au lieu de 13.
     · rembourrage 8px/12px au lieu de 10px/12px sur le rail.

   ET SURTOUT, LE FOND DE L'ENTRÉE ACTIVE DEVIENT TRANSLUCIDE. Le rail employait
   « --surface-appuyee », un aplat opaque — la même couleur, exactement, que le
   bas du dégradé de fond de l'espace membre. Le rail étant collant, le fond
   derrière lui s'assombrit à mesure qu'on descend : mesuré sur le fil,
   l'entrée active passait d'un écart de 33·21·19 en RVB en haut de page à
   6·4·3 en bas — elle disparaissait. Un voile à 5,88 % d'encre adoucie tient
   9 à 11 points d'écart d'un bout à l'autre du dégradé, parce qu'il assombrit
   ce qu'il recouvre au lieu de le remplacer. */
.ui-side-nav .nav-link,
.ui-membre-onglet,
.ui-membre-sous-nav-lien {
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  font-size: var(--fs-sm);
  font-weight: var(--fw-normal);
  color: var(--text);
  text-decoration: none;
  transition: background 0.15s ease, color 0.15s ease;
}

/* assets/css/organismes.css (legacy) définit `.nav a:after` — un trait
   rouge animé sous les liens de l'ancien header — qui s'applique aussi
   ici puisque .ui-side-nav porte la classe Bootstrap .nav. On le
   neutralise pour ce menu. */
.ui-side-nav .nav-link:after { content: none; }

.ui-side-nav .nav-link:hover,
.ui-side-nav .nav-link:focus,
.ui-membre-onglet:hover,
.ui-membre-onglet:focus-visible,
.ui-membre-sous-nav-lien:hover,
.ui-membre-sous-nav-lien:focus-visible { background: var(--teinte-neutre); color: var(--color-dark-soft); text-decoration: none; }

.ui-side-nav .nav-link.active,
.ui-membre-onglet.is-current,
.ui-membre-onglet.is-current:hover,
.ui-membre-sous-nav-lien.is-current {
  background: var(--teinte-neutre);
  color: var(--color-dark-soft);
  font-weight: var(--fw-semibold);
}

/* LES ENTRÉES DU MENU DE GESTION, écrites ICI et non plus haut : elles ne
   font qu'ajouter au menu vertical — un pictogramme aligné, et le rouge de la
   suppression. Le sélecteur reprend « .ui-side-nav » pour peser le même poids
   que « .ui-side-nav .nav-link », sans quoi la couleur de l'entrée dangereuse
   perdrait contre celle du menu. */
.ui-side-nav .ui-menu-actions-item,
.ui-side-nav .ui-publication-menu-item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  border: 0;
  background: transparent;
  text-align: left;
}

.ui-side-nav .ui-menu-actions-item > .material-symbols-outlined,
.ui-side-nav .ui-publication-menu-item > .material-symbols-outlined {
  font-size: var(--icon-md);
  color: var(--color-primary);
}

/* La suppression est la seule action irréversible du menu : elle se signale
   par la couleur, mais garde le même dessin — un aplat rouge dans une liste de
   gris se lirait comme un avertissement, alors que c'est un choix. */
.ui-side-nav .ui-menu-actions-item--danger,
.ui-side-nav .ui-menu-actions-item--danger > .material-symbols-outlined,
.ui-side-nav .ui-publication-menu-item--danger,
.ui-side-nav .ui-publication-menu-item--danger > .material-symbols-outlined {
  color: var(--color-danger);
}

/* Second niveau du menu vertical : l'item parent est un bouton de repli. On
   neutralise l'apparence de bouton (Bootstrap ne cible que .btn) et on aligne
   le chevron à droite. */
.ui-side-nav .ui-side-nav-toggle {
  justify-content: space-between;
  align-items: center;
  background: none;
  text-align: left;
  border: 0;
  width: 100%;
  font: inherit;
  display: flex;
}

/* Le chevron doit l'emporter sur `.ui-side-nav .nav-link:after { content: none }`
   (0,2,1), qui neutralise le trait rouge hérité de l'ancienne nav — d'où le
   sélecteur en deux classes plutôt qu'une. */
.ui-side-nav .ui-side-nav-toggle::after {
  content: 'expand_more';
  font-family: 'Material Symbols Outlined';
  font-size: var(--fs-lead);
  line-height: 1;
  transition: transform 0.2s ease;
}

.ui-side-nav .ui-side-nav-toggle[aria-expanded="true"]::after { transform: rotate(180deg); }

/* Le .collapse ne reçoit aucun display (Bootstrap le pilote) : la mise en
   colonne vit sur la liste interne. */
.ui-side-nav-sub-list {
  flex-direction: column;
  margin-left: var(--space-3);
  padding-top: 1px;
  gap: 1px;
  display: flex;
}

.ui-side-nav-sub-list .nav-link { font-size: var(--fs-sm); }

/* Variante du menu vertical posée sur fond gris : au repos, rendu identique au
   menu principal. Seuls le survol et l'entrée active prennent un fond --border,
   plus dense que le survol standard, qui se perdrait sur le gris. */
.ui-side-nav--filled .nav-link:hover,
.ui-side-nav--filled .nav-link:focus,
.ui-side-nav--filled .nav-link.active { background: var(--border); color: var(--color-dark-soft); }

/* wrapper de la grille d'articles (le titre et la carte intro ont aussi col-12) */

.ui-article-grid {
  --bs-gutter-x: 1.25rem;
  --bs-gutter-y: 1.25rem;
}

/* Plusieurs groupes de 3 cartes empilés sous un même en-tête : la gouttière
   entre deux groupes doit valoir celle entre deux cartes (1.25rem), pour que
   les 6 cartes se lisent comme une seule grille. */
.ui-article-grid + .ui-article-grid { margin-top: 0; }

@media (min-width: 576px) {
  .ui-article-grid + .ui-article-grid { margin-top: var(--space-4); } /* grille CSS (margin:0) : gouttière explicite */
}

@media (min-width: 992px) {
  .ui-article-grid {
    display: grid;
    grid-template-columns: repeat(12, 1fr);
    column-gap: var(--space-4);
    row-gap: var(--space-4);
    margin: 0; /* annule les marges négatives du row Bootstrap → aligné sur le wrapper (comme le titre/carte intro) */
  }
  .ui-article-grid > .ui-grid-item { padding: 0; width: auto; max-width: none; } /* annule la largeur des col Bootstrap (50%/25%) pour remplir la cellule ; gouttière = grid gap */
  .ui-article-grid > .ui-grid-item:nth-child(1) { grid-column: 1 / 7; grid-row: 1 / 3; } /* vedette : 6 col, 2 lignes */
  .ui-article-grid > .ui-grid-item:nth-child(2) { grid-column: 7 / 13; grid-row: 1; }    /* news 1 : 6 col, à droite */
  .ui-article-grid > .ui-grid-item:nth-child(3) { grid-column: 7 / 13; grid-row: 2; }
  /* Toute carte NON vedette → carte horizontale alignée sur la grille : elle
     occupe 6 col → grille interne de 6 col identiques (même gouttière) ; photo
     sur 3 col, texte sur 3 col, gouttière entre les deux. Le critère est la
     carte elle-même, pas son rang : dans un groupe quad la 1re est déjà une brève. */
  .ui-article-grid > .ui-grid-item .ui-article-card:not(.ui-article-card--featured) {
    display: grid;
    grid-template-columns: repeat(6, 1fr);
    column-gap: var(--space-4);
    height: 100%;
  }
  /* la photo occupe les 3 col + déborde sur la gouttière (margin-right négatif)
     pour rejoindre le texte → alignée sur la grille, sans gouttière visible. */
  .ui-article-grid > .ui-grid-item .ui-article-card:not(.ui-article-card--featured) .ui-article-art { grid-column: 1 / 4; margin-right: calc(var(--space-4) * -1); height: 100%; min-height: 0; }
  .ui-article-grid > .ui-grid-item .ui-article-card:not(.ui-article-card--featured) .ui-article-content { grid-column: 4 / 7; }
}

@media (min-width: 576px) and (max-width: 991.98px) {
  .ui-article-grid {
    display: grid;
    grid-template-columns: 1fr;
    row-gap: var(--space-4);
    margin: 0; /* annule les marges négatives du row Bootstrap → aligné sur le wrapper */
  }
  .ui-article-grid > .ui-grid-item { padding: 0; width: auto; max-width: none; } /* annule la largeur des col Bootstrap (col-md-6 = 50%) → pleine largeur */
  .ui-article-grid > .ui-grid-item .ui-article-card {
    display: grid;
    grid-template-columns: repeat(12, 1fr); /* aligné sur la grille 12 col */
    column-gap: var(--space-4);
    height: auto;
  }
  /* photo : 4 col (1-4), déborde sur la gouttière pour rejoindre le texte (sans gouttière visible) */
  .ui-article-grid > .ui-grid-item .ui-article-art { grid-column: 1 / 5; margin-right: calc(var(--space-4) * -1); height: 100%; min-height: 160px; }
  .ui-article-grid > .ui-grid-item .ui-article-content { grid-column: 5 / 13; } /* texte : 8 col */
}


/* ---------- Carte profil ----------
   Un portrait pleine largeur, surmonté d'une mention d'état et d'un bouton
   cœur, puis un bloc de texte : nom, score, lieu et mots-clés.

   Composant propre à cette charte — il n'existe pas dans le socle neutre.
   Son CSS vit donc ici et non dans ui-charte.css : cette couche-là sert à
   corriger des composants existants, pas à en héberger de nouveaux.
   Toutes les valeurs passent par les tokens. */
.ui-profil {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius);
  background: var(--surface-card);
  box-shadow: var(--shadow-md);
  transition: transform 0.25s ease;
}

.ui-profil:hover {
  transform: translateY(-6px);
}

/* CARRÉE, comme toutes les photographies du produit. Elle valait 325px de haut
   pour une largeur variable, donc un format qui changeait avec la colonne : à
   trois cartes par rangée un portrait, à deux un presque-carré. Les portraits
   sont recadrés au carré à l'envoi — c'est la règle du site —, et une hauteur
   fixe rognait ce carré au lieu de le montrer.
   « aspect-ratio » plutôt qu'une hauteur : la carte suit sa colonne, à toutes
   les largeurs, sans media query. */
.ui-profil-photo {
  position: relative;
  aspect-ratio: 1;
}

.ui-profil-photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Mention d'état, posée sur la photo. Le fond blanc à 90 % garde le texte
   lisible quel que soit le portrait en dessous. */
.ui-profil-statut {
  position: absolute;
  top: 0.9375rem;
  left: 0.9375rem;
}

.ui-profil-statut > span {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--color-success);
}

/* La pastille dit la disponibilité par la COULEUR ; le mot qui suit la dit en
   toutes lettres. Les deux sont donnés, la couleur ne portant jamais seule
   l'information (WCAG 1.4.1). Le libellé reste à la charge des données. */
.ui-profil-statut--hors-ligne > span {
  background: var(--color-danger);
}

/* Trois actions, un seul jeton : la croix écarte, l'étoile et le cœur sont des
   interrupteurs. Elles partagent leur dessin, seule l'icône les distingue.
   L'ancrage revient au GROUPE et non à chaque bouton : c'est la rangée qui est
   posée au bas de la photo, et l'ordre du balisage suffit à la ranger. */
.ui-profil-actions {
  position: absolute;
  right: 1rem;
  bottom: 1rem;
  left: 1rem;
  display: flex;
  gap: var(--space-2);
}

/* La croix tient le bord gauche, les deux interrupteurs restent à droite.
   Une marge automatique suffit : elle absorbe l'espace libre et évite
   d'envelopper les deux autres dans un conteneur qui n'existerait que pour
   cet alignement. « space-between » ne conviendrait pas — il écarterait aussi
   l'étoile du cœur, que l'on veut voisines. */
.ui-profil-passe {
  margin-right: auto;
}

/* Le bouton « Message » est le QUATRIÈME rond de la rangée : même dessin, même
   taille, même ombre que les trois autres — il est de la même famille et n'a
   pas à s'en distinguer par la forme. Ce qui le distingue est sa couleur, que
   la charte lui donne, et son glyphe.
   C'est un LIEN et non un bouton : il ne bascule rien, il emmène ailleurs. */
.ui-profil-passe,
.ui-profil-favori,
.ui-profil-like,
.ui-profil-message {
  display: grid;
  place-items: center;
  width: var(--control-h-sm);
  height: var(--control-h-sm);
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--white);
  /* Gris de repos, celui du lieu : la croix et l'étoile sont des actions
     secondaires. Seul le cœur porte le corail, qui distingue l'action
     principale — et sur blanc le gris donne 5,89:1 contre 3,62:1. */
  color: var(--text-muted);
  cursor: pointer;
  box-shadow: var(--shadow-md);
}

.ui-profil-like {
  color: var(--color-primary);
}

/* État actif : le jeton s'inverse — fond plein, pictogramme blanc et PLEIN.
   Le remplissage passe par l'axe « FILL » de la fonte variable Material
   Symbols, déjà chargée en 0..1 ; aucune seconde icône n'est nécessaire.
   L'état est lu sur aria-pressed, pas sur une classe : la vérité reste dans
   l'attribut que les technologies d'assistance annoncent, et le style ne peut
   plus mentir sur l'état réel du bouton.
   Le blanc sur corail donne 3,62:1 — sous le seuil des TEXTES, mais au-dessus
   des 3:1 exigés des objets graphiques (WCAG 1.4.11), ce qu'est une icône. */
/* Chaque bouton garde AU FOND sa couleur de repos : l'étoile s'allume en gris.
   Le cœur, lui, s'allume au ROSE DE MARQUE — c'est la charte qui le pose, et
   la raison y est écrite : un match n'est pas une commande ordinaire.
   L'état inverse le jeton, il ne change pas d'identité. */
.ui-profil-favori[aria-pressed="true"],
.ui-profil-like[aria-pressed="true"] {
  color: var(--white);
}

.ui-profil-favori[aria-pressed="true"] {
  background: var(--text-muted);
}

.ui-profil-like[aria-pressed="true"] {
  background: var(--color-primary);
}

.ui-profil-favori[aria-pressed="true"] .material-symbols-outlined,
.ui-profil-like[aria-pressed="true"] .material-symbols-outlined {
  /* Les trois autres axes sont REPRIS du socle (ui-atomes) : la propriété est
     unique, n'y écrire que « FILL » effacerait la graisse et ferait épaissir
     le trait de l'icône active sans qu'on l'ait demandé. */
  font-variation-settings: 'FILL' 1, 'wght' 300, 'GRAD' 0, 'opsz' 20;
}

/* Le pictogramme est dimensionné à part : la police d'icônes lit sa taille
   dans « font-size », que le bouton n'hérite pas du corps de la carte. */
.ui-profil-passe .material-symbols-outlined,
.ui-profil-favori .material-symbols-outlined,
.ui-profil-like .material-symbols-outlined,
.ui-profil-message .material-symbols-outlined {
  font-size: var(--icon-md);
}

.ui-profil-corps {
  position: relative;
  padding: var(--space-4) var(--space-4) var(--space-4);
}

/* TOUT LE BAS DE LA CARTE MÈNE AU PROFIL (3 oct. 2026) : le lien du nom
   s'étend à la zone blanche entière, par un calque posé sur elle. Les
   boutons qui s'y trouvent (rencontre, organisation, flèches du swipe) et
   les pastilles à infobulle passent au-dessus et gardent leur geste. */
.ui-profil-corps .ui-card-title a::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
}

.ui-profil-corps :is(button, .ui-profil-form, .ui-profil-glisse, [data-bs-toggle="tooltip"]) {
  position: relative;
  z-index: 2;
}



.ui-profil-titre {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
}

/* Chasse resserrée du modèle, et marge remise à zéro : l'écart est tenu par
   le « gap » de la rangée, pas par la marge basse héritée du titre de carte. */
.ui-profil-titre .ui-card-title {
  margin: 0;
  letter-spacing: -0.025em;
}

/* DEUX TAUX, PAS UN, ET SUR LA PHOTO. La carte portait un pourcentage unique
   à droite du nom, qui ne disait pas de quel sens il parlait. La réciprocité
   étant le sujet du produit, les deux sens sont montrés — et posés en haut à
   DROITE de la photo, en vis-à-vis de la mention d'état qui occupe la gauche
   au même retrait de 15px. Le corps de la carte est ainsi rendu au prénom, au
   lieu et aux centres d'intérêt ; les chiffres, eux, se lisent d'un coup d'œil
   sur la grille sans qu'on ait à parcourir chaque carte.
   En colonne et non côte à côte : deux pastilles alignées horizontalement
   auraient buté sur la mention d'état dès qu'un pourcentage passe à trois
   chiffres, et l'ordre vertical fixe — le profil au-dessus, vous en dessous —
   est lui-même une clé de lecture. */
/* DEUX ESSAIS ÉCARTÉS, notés pour qu'on ne les refasse pas. Les taux sont
   l'objet propre du produit — la seule chose qu'aucun autre réseau ne sait
   afficher — et il a semblé anormal qu'ils soient la plus petite chose de la
   carte, plus discrets que le nom, le lieu et les mots-clés.
   1. LES AGRANDIR de 12 à 16px, avec rembourrage et pictogramme à l'avenant.
      Refusé : à 85×38 les deux pastilles mangeaient le haut de la
      photographie et ne laissaient que 25px à la mention d'état voisine.
   2. LES DESCENDRE DANS LE CORPS en bande pleine largeur, les deux nombres se
      faisant face autour d'un connecteur. Refusé aussi : la bande prenait une
      ligne entière et repoussait le nom, le lieu et les mots-clés.
   Ils reviennent donc à 12px sur la photographie, dans le coin haut droit.
   L'échelle n'était pas le bon levier — s'il faut un jour les faire compter
   davantage, ce sera par autre chose que par la taille. */
.ui-profil-scores {
  position: absolute;
  top: 0.9375rem;
  right: 0.9375rem;
  z-index: 1;
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--space-1);
}

.ui-profil-score { /* le dessin vient de .ui-badge */ }

/* LA COULEUR SUIT LE GENRE de la personne dont le taux parle — rose pour une
   femme, bleu pour un homme — et non le sens du taux. Elle porte le
   pictogramme ET le pourcentage ; le pictogramme hérite du parent, rien à
   redéclarer.

   Chacune prend en plus SON voile : le rose de marque à 4 % — la valeur que le
   site emploie déjà pour les étiquettes de match — et le #83deed à 10 %. Les
   deux libellés y descendent de 4,89 et 4,81 à 4,57 et 4,62. Ils passent, mais
   la moitié de leur marge y est passée : un fond d'un cran plus soutenu ferait
   basculer les pastilles sous les 4,5. */
.ui-profil-score--elle {
  color: var(--genre-elle);
  background: var(--genre-elle-voile);
}

.ui-profil-score--lui {
  color: var(--genre-lui);
  background: var(--genre-lui-voile);
}

/* Sélecteur d'ENFANT DIRECT, et rien d'autre : « .ui-profil-score span »
   attraperait aussi le pictogramme, qui deviendrait à son tour un conteneur
   flex — et le nom de glyphe cesserait de se replier en ligature. On lirait
   « woman » écrit en toutes lettres. Déjà rencontré sur les cases de critères. */



.ui-profil-corps > p,
.ui-profil-entete > p {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  margin: var(--space-2) 0 var(--space-3);
  color: var(--text-muted);
  font-size: var(--fs-sm);
}

/* L'icône suit la ligne : 16px contre 14px de texte, le rapport que le socle
   tient partout ailleurs entre un pictogramme et le mot qu'il accompagne. */
.ui-profil-corps > p .material-symbols-outlined,
.ui-profil-entete > p .material-symbols-outlined {
  font-size: var(--icon-sm);
}

/* CE QUE LA PERSONNE A ENVOYÉ, sur « Mes demandes » : un badge en haut à
   gauche de la photo (29 sept. 2026), dans la couleur de son intention — le
   rose du like, l'encre des contacts. Le statut, s'il y en a un, passe
   dessous. */
.ui-profil-demande {
  position: absolute;
  top: 0.9375rem;
  left: 0.9375rem;
  z-index: 1;
}
/* Sur une grande carte, les badges tiennent le coin haut gauche : le second
   (« Profil signalé ») se pose sous le premier. */
.ui-profil-demande + .ui-profil-demande { top: 3.25rem; }
.ui-profil:not(.ui-profil--compacte) .ui-profil-demande + .ui-profil-demande + .ui-profil-statut { top: 5.5625rem; }
.ui-profil-demande + .ui-profil-statut { top: 3.25rem; }


.ui-profil-tags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

/* Enfant DIRECT : « .ui-profil-tags span » attrapait aussi le pictogramme placé
   dans la pastille, qui héritait alors du cadre et du rayon — un rond tracé
   autour de l'icône, dans une pastille déjà cerclée. Le cadre appartient à la
   pastille, pas à ce qu'elle contient. */
.ui-profil-tags > span {
  padding: var(--space-2) var(--space-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  color: var(--text-muted);
  font-size: var(--fs-sm);
  font-weight: 600;
}

/* .ui-profil--compacte — LA CARTE À QUATRE PAR RANGÉE --------------------------
   LA MESURE EST CONNUE, et elle est écrite dans data/profils.yml : « à quatre
   colonnes dans les 984px de la zone de contenu, elle tombait à 228px et les
   mots-clés passaient à la ligne un par un ». C'est pour cela que la page des
   profils est restée à trois par rangée. Ce modificateur est la réponse à ce
   constat : il rend la carte tenable à 228px, largeur à laquelle elle perd un
   tiers de ce qu'elle avait à 330.

   CE QU'IL NE TOUCHE PAS, ET C'EST L'ESSENTIEL : la photographie (toujours
   carrée, donc toujours proportionnée à sa colonne), la mention d'état et les
   trois interrupteurs, qui gardent leurs 47px. Une carte plus petite qui
   rendrait ses boutons plus petits enseignerait deux gestes pour un seul, et
   47px est la mesure de tous les ronds du produit. Ce qui se réduit est le
   TEXTE, qui est ce qui déborde.

   Trois réglages, dans l'ordre de ce qu'ils gagnent :
   1. les MOTS-CLÉS et LES DEUX TAUX tombent — c'est le snippet qui ne les rend
      pas, pas le CSS qui les cache : une pilule masquée reste lue à voix
      haute. Trois pilules de mots-clés sur 228px passent sur deux lignes et
      fixent alors la hauteur de toute la grille ; les deux taux, eux, font
      132px côte à côte et recouvraient la mention d'état de quatorze pixels.
      Les empiler réglait la collision — c'est ce qui avait été fait d'abord —
      mais leur donnait la moitié du coin droit de la photographie. Ils ont
      été retirés à la demande, et le coin haut droit est rendu à l'image.
   2. le REMBOURRAGE du corps passe de 20/20/23 à 14/15/16 : l'écart du modèle
      est calculé pour une carte large, il ronge la mesure du texte sur une
      étroite.
   3. le NOM descend d'un cran sur l'échelle, de --fs-h5 (20px) à --fs-base
      (16px). VINGT ET NON VINGT-QUATRE est bien le point de départ : le socle
      pose .ui-card-title à --fs-h4, et ui-charte.css l'a déjà ramené à --fs-h5
      pour toutes les cartes du produit. On part donc de ce que la charte rend,
      pas de ce que le socle déclare — la règle ci-dessous l'emporte sur les
      deux, ayant trois classes de spécificité contre une.
      À 16px, « Charlotte-Marie, 38 » tient sur une ligne là où il en prenait
      deux, et c'était le titre qui décidait de la hauteur restante. */
.ui-profil--compacte .ui-profil-corps {
  /* 30 sept. 2026 : en haut et en bas, les marges de la grande carte (20 et
     23px) ; sur les côtés, 15px restent — la carte est étroite. */
  padding: var(--space-4) var(--space-3) var(--space-4);
}

.ui-profil--compacte .ui-profil-titre .ui-card-title {
  font-size: var(--fs-base);
}

/* Le lieu se recolle au nom, et ne laisse plus rien sous lui : la marge basse
   de 15px ouvrait l'espace des mots-clés, qui ne sont plus là. */
.ui-profil--compacte .ui-profil-corps > p {
  margin: var(--space-1) 0 0;
}

/* LE BOUTON À TROIS POINTS, dans le coin haut droit — celui que les deux taux
   occupaient avant d'être retirés. Il remplace la rangée de trois disques que
   la carte large pose au bas de sa photographie : sur 238px, ces trois-là
   couvraient la moitié de la largeur de l'image et se posaient sur le visage.
   RIEN N'EST REDESSINÉ ICI : le bouton est .ui-icon-button-slim (40px, le rang
   « small » du produit) et le panneau est celui de molecules/menu-actions, à
   la classe près. Ce bloc ne fait que le POSER — quatre déclarations.
   Pourquoi le gabarit ne fait pas appel à menu-actions par « include » : ce
   composant prend une LISTE d'entrées uniformes, et les trois commandes d'une
   carte n'en sont pas une — chacune porte son état (aria-pressed), sa phrase
   de confirmation et deux libellés selon qu'elle est allumée ou éteinte. Le
   gabarit écrit donc le menu lui-même, avec les classes de menu-actions ;
   c'est le dessin qu'on partage, pas la donnée. */
.ui-profil-menu {
  position: absolute;
  top: 0.9375rem;
  right: 0.9375rem;
  z-index: 2;
}

/* LA PASTILLE DE MATCH, au BAS GAUCHE de la photographie — le coin que la
   rangée de trois disques occupait avant de passer en menu. Les quatre angles
   de l'image se répartissent ainsi sans qu'aucun ne se dispute : la
   disponibilité en haut à gauche, les trois points en haut à droite, le match
   en bas à gauche. Le bas droit reste vide, et c'est voulu — une image a
   besoin d'un angle où rien ne se pose.
   LE DESSIN EST EMPRUNTÉ AU FIL, .ui-publication-etiquette--match : même
   aplat rose dilué, même encre, même cœur plein, même phrase. Ce bloc ne fait
   que la POSER. La seule chose qui s'ajoute est l'ombre : la pastille du fil
   vit sur du blanc, celle-ci sur une photographie dont on ne sait rien, et
   son aplat très dilué ne s'en détache pas tout seul. */
.ui-profil-match {
  position: absolute;
  bottom: 0.9375rem;
  left: 0.9375rem;
  z-index: 1;
  margin: 0;
  box-shadow: var(--shadow-md);
}

/* LE PANNEAU DOIT POUVOIR SORTIR DE LA CARTE, et .ui-profil la ferme à clé :
   « overflow: hidden » y rogne la photographie sur le rayon, et rognerait le
   menu déroulant avec elle — mesuré, le panneau apparaissait coupé au bord de
   l'image. Le rognage descend donc d'un cran, de la CARTE à la PHOTOGRAPHIE,
   qui est la seule chose qui débordait.
   Le fond de la carte, lui, reste arrondi sans rien demander : un
   « border-radius » découpe toujours le fond d'un élément, même sans
   overflow. Seuls les ENFANTS qui débordent avaient besoin d'être tenus. */
.ui-profil--compacte {
  overflow: visible;
}

.ui-profil--compacte .ui-profil-photo {
  overflow: hidden;
  border-radius: var(--radius) var(--radius) 0 0;
}

/* LE PANNEAU EST DANS LA PHOTOGRAPHIE, et elle le rognait à son tour
   (30 sept. 2026) — mesuré, le menu coupé au bord de l'image. Le rognage
   descend encore d'un cran : sur l'IMAGE seule, par son propre arrondi. */
.ui-profil--compacte .ui-profil-photo:has(> .ui-profil-menu) { overflow: visible; }
.ui-profil--compacte .ui-profil-photo:has(> .ui-profil-menu) > img {
  border-radius: var(--radius) var(--radius) 0 0;
}
/* Menu ouvert, la carte passe devant ses voisines : sans cela, la carte
   suivante, peinte après elle, recouvrait le panneau. */
.ui-profil--compacte:has(.ui-menu-actions-menu.show) { z-index: 5; }

/* LES STATUTS EN HAUT À GAUCHE, SUR LA CARTE RÉDUITE AUSSI (1er oct. 2026) :
   Contact, Vous a liké, C'est un match, signalé, bloqué. Ce que j'ai envoyé
   tient le bas gauche (.ui-profil-envoye--coin), et la disponibilité
   s'empile au-dessus. (Ils étaient descendus en bas le 30 sept. : le « … »
   les recouvrait sur une carte étroite.) */
.ui-profil--compacte .ui-profil-statut {
  top: auto;
  bottom: 0.9375rem;
}
.ui-profil--compacte .ui-profil-envoye--coin + .ui-profil-statut { bottom: 3.25rem; }



/* Vignette de profil — .ui-profil-mini ---------------------------------------
   À ne pas confondre avec .ui-profil--compacte juste au-dessus : celle-là est
   la MÊME carte à une autre largeur, celle-ci est un autre objet.
   Le hero de « Découvrir » présente une sélection en vignettes. Même sujet que
   .ui-profil, mais un autre objet : ni score, ni mots-clés, ni bouton cœur, et
   une ligne de confirmation que la grande carte n'a pas. C'est donc une
   molécule à part, non un modificateur de taille — un « --sm » qui retire trois
   éléments et en ajoute un n'est plus une taille.
   Le modèle arrondit à 22px, qui tombe entre deux crans de notre échelle ;
   --radius-md (18px) est le cran retenu, la vignette étant petite. */

.ui-profil-mini {
  position: relative;
  overflow: hidden;
  /* Bordure de 2px dès l'état neutre: la variante « --vous » n'en change que
     la COULEUR. Une épaisseur qui varie décalerait la carte de 2px, et dans
     une grille cela déséquilibre l'écart entre les rangées. */
  border: 2px solid color-mix(in srgb, var(--color-dark) 6%, transparent);
  border-radius: var(--radius-md);
  background: var(--white);
  box-shadow: var(--shadow-md);
}

.ui-profil-mini--vous {
  border-color: var(--color-primary);
}

.ui-profil-mini-marque {
  position: absolute;
  z-index: 2;
  top: 0.625rem;
  left: 0.625rem;
  box-shadow: var(--shadow-md);
}

.ui-profil-mini > img {
  display: block;
  width: 100%;
  height: 145px;
  object-fit: cover;
}

.ui-profil-mini-corps {
  /* Resserré en haut et en bas, inchangé sur les côtés : c'est la hauteur de
     la vignette qu'on réduit, pas sa gouttière de texte. */
  padding: var(--space-2) var(--space-3) var(--space-2);
}

/* Alignement sur la ligne de base, non sur le centre : le nom et le lieu n'ont
   pas la même chasse, c'est leur écriture qui doit s'aligner, pas leur boîte. */
.ui-profil-mini-titre {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
}

.ui-profil-mini-titre strong {
  font-size: var(--fs-base);
}

.ui-profil-mini-titre span {
  color: var(--text-muted);
  font-size: var(--fs-sm);
}

/* Même jeton que les valeurs du tableau de critères : c'est la même nature de
   donnée — une mesure de correspondance — et le lecteur n'a qu'un code à lire. */
/* Sélecteur porté au niveau du parent, à dessein : « .ui-profil-mini-titre
   span » vise le lieu et compte une classe DE PLUS que « .ui-profil-mini-score
   » seul — il l'emportait donc sur la couleur, et le score s'affichait en gris.
   Une classe ne suffit pas quand une règle voisine en aligne deux. */
.ui-profil-mini-titre .ui-profil-mini-score {
  padding: 0.0625rem var(--space-2);
  border-radius: var(--radius-pill);
  background: var(--color-brand-pink-soft);
  color: var(--color-brand-pink-ink);
  font-size: var(--fs-caption);
  font-weight: 550;
}

.ui-profil-mini-note {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  margin: var(--space-2) 0 0;
  padding-top: var(--space-2);
  border-top: 1px solid var(--border);
  color: var(--text-muted);
  font-size: var(--fs-sm);
}

.ui-profil-mini-note .material-symbols-outlined {
  color: var(--color-success);
  font-size: var(--icon-sm);
}

/* Disposition du hero : les vignettes sont légèrement désaxées, ce qui donne
   l'impression d'une pile posée à la main. L'inclinaison appartient à
   l'ARRANGEMENT, pas à la carte — posée ici sur la colonne, la vignette reste
   droite partout ailleurs. La grille elle-même reste celle de Bootstrap. */
/* La pile occupe toute la largeur de son conteneur : c'est au conteneur de la
   borner s'il le faut — le hero du modèle la tient à 530px, ce qui le regarde.

   Les gouttières ne sont pas rondes parce qu'elles COMPENSENT l'inclinaison :
   une carte tournée occupe une boîte plus large que sa mise en page, de
   hauteur × sin(angle) horizontalement, largeur × sin(angle) verticalement.
   Cette croissance mange la gouttière ; s'y ajoute, en vertical, le décalage
   de 4px entre les rangées. On vise 16px d'écart VU des quatre côtés, et on
   passe par les variables de grille pour laisser le calcul à Bootstrap. */
.ui-selection-mini {
  --bs-gutter-x: 1.5rem;
  --bs-gutter-y: 1.5rem;
}

/* Angles de même MAGNITUDE, signes alternés. Les décalages verticaux du modèle
   ont été RETIRÉS : ils supposaient un nombre de colonnes connu — « la rangée
   du bas » n'est pas une notion que CSS puisse lire — et déséquilibraient donc
   les écarts dès que la grille passait de deux à trois colonnes. La rotation
   seule suffit à donner la pile posée à la main, et les écarts restent égaux
   quel que soit le découpage. */
/* Hauteurs égales dans la pile : les mentions n'ont pas toutes le même nombre
   de lignes, et deux vignettes voisines de hauteurs différentes se voient tout
   de suite dans une grille. La colonne Bootstrap s'étire déjà — il suffit que
   la carte la remplisse. */
.ui-selection-mini > [class*="col-"] > .ui-profil-mini {
  height: 100%;
}

.ui-selection-mini > [class*="col-"]:nth-child(odd) .ui-profil-mini { transform: rotate(-2deg); }
.ui-selection-mini > [class*="col-"]:nth-child(even) .ui-profil-mini { transform: rotate(2deg); }

/* ---- Carte évènement --------------------------------------------------------
   Même charpente que la carte communauté — photo de tête, corps en dessous,
   lieu posé sur la photo — parce que les deux se croisent dans les mêmes
   grilles : ce qui les distingue doit être leur CONTENU, pas leur silhouette.
   Ce qui s'ajoute ici est la pastille de date, en vis-à-vis du lieu.

   Préfixe .ui-evenement- : .ui-communaute- est pris par la vignette
   d'activité qu'embarque organismes/section-communautes, dont la pastille de
   date est la parente. */

.ui-evenement-carte {
  position: relative;                /* repère de la nappe cliquable */
  display: flex;
  flex-direction: column;
  overflow: hidden;                  /* la photo suit le rayon de la carte */
  border-radius: var(--radius);
  background: var(--surface-card);
  /* --shadow-sm au repos. La carte portait --shadow-md, qui la décollait de
     la page comme une carte isolée ; en grille de trois ou quatre, la même
     ombre répétée assombrit toute la bande et les cartes flottent au lieu de
     s'aligner. */
  box-shadow: var(--shadow-md);
  transition: box-shadow 0.2s ease, transform 0.2s ease;
}

/* Carte ENTIÈREMENT cliquable, même dispositif que molecules/carte-communaute :
   le lien du titre étale une nappe par-dessus la carte. Un seul lien de plus
   dans l'arbre d'accessibilité — aucun, en fait : c'est celui du titre.

   Ici la carte porte un SECOND lien, celui de la communauté en pied. Il est
   remonté au-dessus de la nappe : sans cela, cliquer « Padel & compagnie »
   ouvrait l'évènement. Deux destinations dans une même carte, c'est voulu —
   le pied dit d'où vient l'évènement, et on doit pouvoir y aller. */
.ui-evenement-titre a::after {
  content: "";
  position: absolute;
  z-index: 1;
  inset: 0;
}

.ui-evenement-communaute a {
  position: relative;
  z-index: 2;
}

.ui-evenement-carte:has(.ui-evenement-titre a):hover {
  box-shadow: var(--shadow-md);
  transform: translateY(-2px);
}

.ui-evenement-carte:has(.ui-evenement-titre a:focus-visible) {
  outline: 2px solid var(--color-primary);
  outline-offset: 3px;
}

.ui-evenement-titre a:focus-visible { outline: none; }

@media (prefers-reduced-motion: reduce) {
  .ui-evenement-carte { transition: box-shadow 0.2s ease; }
  .ui-evenement-carte:has(.ui-evenement-titre a):hover { transform: none; }
}

.ui-evenement-photo {
  position: relative;                /* repères de la date et du lieu */
}

/* Le cadrage est porté par l'IMAGE, pas par son cadre : sur le cadre, avec une
   image en height:100%, la chaîne est circulaire et le navigateur retombe sur
   la hauteur intrinsèque du fichier. Sur l'image, elle est droite —
   largeur → ratio → hauteur. Même valeur que la carte communauté : les deux
   s'alignent dans une grille commune. */
.ui-evenement-photo > img {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 10;
  object-fit: cover;
}

/* La date ferme la photo par le bas, en vis-à-vis du lieu qui la coiffe. Fond
   plein et non voile : au-dessus d'une photo, un voile ne garantit rien.
   Reprise de .ui-communaute-date, en pastille carrée plutôt qu'en jeton de
   liste. */
.ui-evenement-date {
  position: absolute;
  inset-block-end: var(--space-3);
  inset-inline-start: var(--space-3);
  display: grid;
  place-content: center;
  min-width: 56px;
  padding: var(--space-2) var(--space-2);
  border-radius: var(--radius-sm);
  background: var(--surface-card);
  color: var(--color-brand-pink-ink);
  text-align: center;
  line-height: 1.1;
}

.ui-evenement-date b {
  font-size: var(--fs-caption);
  font-weight: var(--fw-bold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.ui-evenement-date strong { font-size: var(--fs-h5); }

.ui-evenement-date i {
  font-size: var(--fs-caption);
  font-style: normal;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* Le lieu coiffe la photo. Il garde la forme et la teinte qu'il a sur la
   carte communauté — même renseignement, même lecture — et n'en change que
   le bord : ici c'est la date qui occupe le bas. */
.ui-evenement-lieu {
  position: absolute;
  inset-block-start: var(--space-3);
  inset-inline-start: var(--space-3);
}


.ui-evenement-corps {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  padding: var(--space-4);
}

/* L'heure coiffe le titre, en corail : c'est la seconde moitié du « quand »,
   et la pastille ne peut pas la porter sans devenir illisible. */
/* L'heure au ROSE DE MARQUE. Une note sur la valeur : une demande antérieure
   portait sur #ff214d, un rose à un niveau de vert et un de bleu de celui du
   système — un écart invisible pour deux jetons à tenir accordés. Tous les
   roses ont depuis été fondus dans #e20138, et c'est --color-brand-pink qui
   est posé ici.
   4,89:1 sur la carte blanche, pour un texte de 14px en gras : au-dessus des
   4,5 du petit texte, là où l'ancien rose plafonnait à 3,77 et obligeait à
   passer par une seconde valeur assombrie. */
.ui-evenement-heure {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0 0 var(--space-2);
  color: var(--color-brand-pink-ink);
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
}

.ui-evenement-heure .material-symbols-outlined { font-size: var(--icon-sm); }

/* La flèche entre les deux heures est décorative — le « à » qu'elle remplace
   est rendu en visually-hidden, à côté d'elle. Elle tient dans un span pour
   recevoir son propre espacement, sans quoi les deux valeurs se collaient à
   elle. L'exclusion de .visually-hidden est nécessaire : cette classe se
   positionne, et lui poser une marge reviendrait à décaler une boîte qu'on
   a justement sortie du flux. */
.ui-evenement-heure > span:not(.material-symbols-outlined):not(.visually-hidden) {
  margin-inline: var(--space-1);
  opacity: 0.6;
}

/* margin-bottom: auto — c'est lui qui pousse le pied au BAS de la carte, et
   c'est le geste de la carte communauté (.ui-groupe-texte:last-of-type), repris
   à l'identique. Sans lui, le filet se posait juste sous le titre : dans une
   même rangée, un titre d'une ligne le laissait à 94px du bas, un titre de deux
   à 70px, et les pieds partaient en escalier.

   L'auto est posée sur le TITRE et non en margin-top sur le pied. C'est la
   correction que l'ancien commentaire de .ui-evenement-communaute appelait
   sans la trouver : une marge automatique vaut zéro quand il n'y a pas de
   place à manger, et le pied se retrouvait alors collé au titre. Portée par
   l'élément du dessus, elle n'entame jamais la marge propre du pied — l'écart
   de 24 au-dessus du filet est tenu dans les deux cas. */
.ui-evenement-titre {
  margin: 0 0 auto;
  font-size: var(--fs-h5);
  font-weight: var(--fw-bold);
  line-height: var(--lh-heading);
  color: var(--color-titre);
  font-family: var(--font-titre);
}

.ui-evenement-titre a {
  color: inherit;
  text-decoration: none;
}

/* Le titre répond au survol de la CARTE — c'est elle la cible. Le lien de la
   communauté est exclu du sélecteur : survoler la carte ne doit pas colorer
   une destination qu'on n'est pas en train de viser. */
.ui-evenement-carte:hover .ui-evenement-titre a,
.ui-evenement-titre a:focus-visible { color: var(--color-primary); }

/* La communauté ferme la carte, séparée par un filet : elle ne décrit pas
   l'évènement, elle dit d'où il vient.

   Le filet est CENTRÉ dans son écart : même valeur au-dessus (marge) et en
   dessous (rembourrage), et la même que le rembourrage de la carte — le
   rythme vertical reste donc 24 partout, titre → filet → mention → bord.

   C'est ce qui a fait tomber le « margin-top: auto » qui collait la ligne au
   bas du corps : une marge automatique mange tout l'espace libre, donc elle
   vaut zéro quand il n'y en a pas — le filet se retrouvait collé au titre,
   à 24px de la mention — et elle grandit sans limite quand il y en a. Un
   écart qu'on veut symétrique ne peut pas être porté par une valeur qui
   dépend de la hauteur des voisines.

   Conséquence assumée : dans une rangée de cartes inégales, les mentions ne
   s'alignent plus d'une carte à l'autre ; l'espace en trop se voit sous la
   ligne, où il se lit comme du rembourrage. */
.ui-evenement-communaute {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  /* Hauteur FIXE, et la même que le pied d'une carte communauté : les deux
     grilles se suivent sur l'accueil, leurs bandes de pied doivent mesurer
     pareil. Fixe et non minimale : ce pied ne porte qu'une ligne, et un nom
     de communauté long ne doit pas la faire grandir pour lui seul — il se
     coupe (voir l'ellipse sur le lien). */
  height: var(--carte-pied-h);
  margin: var(--space-4) 0 0;
  padding-top: var(--space-4);
  border-top: 1px solid var(--border);
  color: var(--text-muted);
  font-size: var(--fs-sm);
}

/* Le pictogramme ne se laisse pas comprimer quand le nom est long. */
.ui-evenement-communaute > .material-symbols-outlined { flex: 0 0 auto; }

.ui-evenement-communaute .material-symbols-outlined {
  font-size: var(--icon-md);
  color: var(--color-primary);
}

/* La hauteur du pied étant fixe, un nom trop long ne peut plus passer à la
   ligne : il serait coupé en deux à mi-hauteur. Il s'abrège donc par une
   ellipse, qui laisse lire le début du nom — et la carte entière reste
   cliquable vers l'évènement de toute façon. */
.ui-evenement-communaute a,
.ui-evenement-communaute strong {
  overflow: hidden;
  color: var(--text);
  font-weight: var(--fw-semibold);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ui-evenement-communaute a:hover,
.ui-evenement-communaute a:focus-visible { color: var(--color-primary); }

/* ---- Barre de filtres -------------------------------------------------------
   La ligne de commandes d'une page de liste, posée sur une carte au-dessus de
   la grille qu'elle filtre. Voisine de .ui-search-bar sans en être une
   variante : celle-là se superpose au bas d'un hero et ne connaît que des
   listes déroulantes ; celle-ci vit dans le flux et porte trois formes de
   commande. D'où une ombre moyenne et non l'ombre haute — elle repose sur la
   section, elle ne flotte pas au-dessus d'elle. */

.ui-filtres {
  padding: var(--space-4);
  border-radius: var(--radius);
  background: var(--surface-card);
  box-shadow: var(--shadow-md);
}

/* Les groupes s'alignent par le BAS : leurs libellés n'ont pas tous le même
   nombre de lignes, et ce sont les commandes qui doivent tomber sur la même
   ligne, pas les titres. */
.ui-filtres-row {
  --bs-gutter-x: 1.5rem;
  --bs-gutter-y: 1.25rem;
  align-items: end;
}

/* Deux groupes se partagent la place que le bouton et les colonnes fixes
   laissent : par défaut à parts égales, puisque « col » les fait croître
   pareillement. Ce modificateur donne 30 % de plus à celui dont le contenu
   est plus long — « Partout en Belgique » tient mal dans ce que « Un titre,
   un mot-clé » remplit à l'aise. La grille en douzièmes ne sait pas exprimer
   ce demi-pas ; la croissance flex, si. */
@media (width >= 1200px) {
  .ui-filtres-groupe--long { flex-grow: 1.3; }
}

.ui-filtres-label {
  display: block;
  margin-bottom: var(--space-2);
  color: var(--text-muted);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
}

/* Plusieurs commandes sous un même libellé : elles se partagent la largeur à
   parts égales, et passent à la ligne plutôt que de se comprimer sous leur
   lisibilité. */
.ui-filtres-controles {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.ui-filtres-controles > * { flex: 1 1 0; min-width: 8rem; }

/* Le champ de recherche porte sa loupe DANS la boîte : le pictogramme est
   posé en absolu et le rembourrage gauche du champ lui fait la place. Un
   pictogramme voisin, en flux, aurait mangé la largeur du champ sur les
   écrans étroits — là où elle manque déjà. */
.ui-filtres-recherche {
  position: relative;
  display: block;
}

.ui-filtres-recherche .material-symbols-outlined {
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 0.875rem;
  transform: translateY(-50%);
  color: var(--text-muted);
  font-size: var(--icon-md);
  pointer-events: none;
}

/* Largeur pleine : le champ n'est pas un enfant flex comme les autres
   commandes — son groupe est en « block », pour que la loupe posée en absolu
   ait un repère — et il retombait donc sur sa largeur intrinsèque, une
   vingtaine de caractères. */
.ui-filtres-recherche .ui-form-control {
  width: 100%;
  padding-inline-start: var(--space-5);
}

/* La borne de date est un BOUTON déguisé en champ : c'est un menu qu'elle
   ouvre, pas une saisie libre. Elle en garde donc la boîte et la hauteur,
   mais aligne son contenu à gauche, comme le ferait un champ rempli. */
/* Le plancher d'une borne est celui d'une DATE ÉCRITE, pas de son libellé :
   « 12/09/2026 » demande 88px, le pictogramme et les rembourrages 48. D'où
   8,5rem, et trois colonnes pour le groupe — c'est la place qu'il faut pour
   que deux bornes remplies tiennent côte à côte sans se tronquer. Une borne
   qui n'affiche que « Du » y est large : c'est le prix à payer pour qu'elle
   ne change pas de taille une fois remplie.
   Le texte y descend d'un cran (--fs-sm) : à 16px, « 12/09/2026 » et son
   pictogramme demandaient 140px, et les deux bornes ne tenaient plus côte à
   côte à 1200px. Une date est une donnée, pas une phrase — elle supporte le
   petit corps mieux qu'un libellé. */
.ui-filtres-date { flex: 1 1 0; min-width: 8rem; }

.ui-filtres-date-bouton {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  text-align: start;
  color: var(--text);
  font-size: var(--fs-sm);
  cursor: pointer;
}

.ui-filtres-date-bouton .material-symbols-outlined {
  color: var(--text-muted);
  font-size: var(--icon-md);
}

/* Le panneau du calendrier n'est pas une liste d'options : il reprend le
   rayon et l'ombre des cartes, et laisse le calendrier gérer sa propre
   grille. Sa largeur suit son contenu — un mois — et non celle de la borne
   qui l'ouvre. */
.ui-filtres-calendrier {
  padding: var(--space-3);
  border: 0;
  border-radius: var(--radius-sm);
  background: var(--surface-card);
  box-shadow: var(--shadow-md);
}

/* Le bouton se cale à DROITE, au bout du geste : la marge automatique pousse
   sa colonne au bord de la ligne, et « justify-content » l'y colle à
   l'intérieur — sans quoi il flotterait au début d'une colonne plus large
   que lui. En dessous de 768px il reprend toute la largeur : sur une colonne
   unique, un bouton à droite n'est plus au bout de rien. */
.ui-filtres-action {
  display: flex;
  justify-content: flex-end;
  margin-inline-start: auto;
}

.ui-filtres-action .ui-btn { width: 100%; }

@media (width >= 768px) {
  .ui-filtres-action .ui-btn { width: auto; }
}

@media (width <= 560px) {
  .ui-filtres { padding: var(--space-3); }

  .ui-filtres-controles > *,
  .ui-filtres-date { flex-basis: 100%; }
}

/* ---- Sélecteur de lieu (combobox à recherche) -------------------------------
   Un champ, une liste sous lui. La liste est en position absolue : dans le
   flux, elle pousserait tout ce qui suit — la barre de filtres grandirait de
   300px à chaque ouverture, et la grille sauterait. */

.ui-lieu {
  position: relative;
  display: block;
}

.ui-lieu-champ {
  width: 100%;
  padding-inline-end: var(--space-5);        /* la place du chevron */
  cursor: pointer;
}

.ui-lieu-fleche {
  position: absolute;
  inset-block-start: 50%;
  inset-inline-end: 0.75rem;
  transform: translateY(-50%);
  color: var(--text-muted);
  font-size: var(--icon-md);
  pointer-events: none;              /* le clic traverse et va au champ */
}

/* z-index : la liste passe DEVANT les commandes voisines, qui ne sont pas
   positionnées. On l'écrit tout de même — c'est une intention, pas un effet
   de bord de l'ordre du document. */
.ui-lieu-liste {
  position: absolute;
  z-index: 20;
  inset-block-start: calc(100% + var(--space-1));
  inset-inline: 0;
  max-height: 17rem;
  overflow-y: auto;
  margin: 0;
  padding: var(--space-1);
  border-radius: var(--radius-sm);
  background: var(--surface-card);
  box-shadow: var(--shadow-md);
  list-style: none;
}

.ui-lieu-option {
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  color: var(--text);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  cursor: pointer;
}

/* La ville est INDENTÉE sous sa région, et allégée : le retrait dit la
   dépendance, la graisse dit le rang. L'indentation ne s'entend pas — c'est
   l'aria-label de l'option qui porte le nom de la région pour la voix. */
.ui-lieu-option--ville {
  padding-inline-start: var(--space-4);
  color: var(--text-muted);
  font-weight: var(--fw-normal);
}

/* Un seul état visuel pour le survol et le parcours au clavier : la souris et
   les flèches désignent la même chose, elles doivent la montrer pareil. */
.ui-lieu-option:hover,
.ui-lieu-option.is-active {
  background: var(--surface-douce);
  color: var(--color-dark);
}

.ui-lieu-option[aria-selected="true"] {
  color: var(--color-primary);
}

.ui-lieu-vide {
  padding: var(--space-3);
  color: var(--text-muted);
  font-size: var(--fs-sm);
  text-align: center;
}

/* ---- Publication du fil ----
   La carte d'un billet du mur. Elle reprend le vocabulaire de .ui-carte —
   blanc, filet minéral, ombre légère — mais ne s'appuie pas dessus : une
   publication n'est pas une carte de catalogue, elle a un en-tête d'auteur,
   un pied d'actions et un rythme vertical propre. Hériter aurait obligé à
   défaire la moitié de ce qu'on hérite. ---- */
.ui-publication {
  background: var(--surface-card);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  /* La marge standard, 24px (2 oct. 2026 : 56 le temps des billets sans
     carte autour de l'en-tête, revenue à 24 avec la carte classique). */
  margin-bottom: var(--space-4);
  padding: var(--space-4) var(--space-4) var(--space-2);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

/* ---- LES PHOTOS D'UN BILLET (2 oct. 2026) ----
   Des TIRAGES POSÉS, comme les photos du profil (« l'inclinaison d'album »,
   ui-organismes.css) : penchés de quelques dixièmes de degré, ombrés, le rayon
   standard, et redressés au survol. Chacun est un bouton qui ouvre la
   visionneuse du billet.
   LA FORME SUIT LE NOMBRE (2 oct. 2026), en vignettes CARRÉES sur douze
   colonnes, la première la plus grande dès que c'est possible : une photo,
   pleine largeur (16/9) ; deux, une vedette (7 colonnes) et une carrée ;
   trois et plus, une ample (8 × 2) et deux moyennes carrées empilées. Le
   serveur choisit les formes (serveur/mur.js, disposition). La vedette et
   l'ample n'ont pas de format : elles prennent la hauteur de leurs voisines.
   L'IMAGE EST EN ABSOLU dans son bouton : ce sont les petits formats (en
   4/3 ou carrés) qui donnent leur hauteur aux rangées ; les grands s'étirent
   à cette hauteur au lieu d'imposer la leur.
   LES ANGLES : cinq en cycle pour les petits formats, sous 2° ; les grands
   ne penchent que de 0,6° — à leur largeur, 1,6° pousserait leur coin dans
   la gouttière. ---- */
.ui-publication-galerie {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
    gap: var(--space-3);
}

.ui-publication-galerie-ouvre {
  position: relative;
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  transform: rotate(var(--inclinaison, 0deg));
  transition: transform 0.25s ease;
  cursor: zoom-in;
}

.ui-publication-galerie-image {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: var(--radius-md);
  display: block;
}

/* LE CALQUE « +N » (2 oct. 2026, comme Facebook) : au-delà de trois photos,
   la troisième dit combien il en reste ; la visionneuse les montre toutes. */
.ui-publication-galerie-reste {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--color-dark) 55%, transparent);
  color: var(--white);
  font-size: var(--fs-h3);
  font-weight: var(--fw-semibold);
}

.ui-publication-galerie-ouvre--pleine { grid-column: 1 / -1; aspect-ratio: 16 / 9; }
.ui-publication-galerie-ouvre--vedette { grid-column: span 7; }
.ui-publication-galerie-ouvre--cote { grid-column: span 5; aspect-ratio: 1; }
.ui-publication-galerie-ouvre--ample { grid-column: span 8; grid-row: span 2; }
.ui-publication-galerie-ouvre--moyenne { grid-column: span 4; aspect-ratio: 1; }

.ui-publication-galerie-ouvre:nth-child(5n+1) { --inclinaison: -1.6deg; }
.ui-publication-galerie-ouvre:nth-child(5n+2) { --inclinaison: 1.1deg; }
.ui-publication-galerie-ouvre:nth-child(5n+3) { --inclinaison: -0.7deg; }
.ui-publication-galerie-ouvre:nth-child(5n+4) { --inclinaison: 1.8deg; }
.ui-publication-galerie-ouvre:nth-child(5n+5) { --inclinaison: -1.2deg; }

.ui-publication-galerie-ouvre.ui-publication-galerie-ouvre--pleine,
.ui-publication-galerie-ouvre.ui-publication-galerie-ouvre--ample,
.ui-publication-galerie-ouvre.ui-publication-galerie-ouvre--vedette { --inclinaison: -0.6deg; }

/* ── LE BILLET À VITRINE (2 oct. 2026, --vitrine) : celui d'une sortie créée
   (« a créé un évènement », sa carte jointe), une seule carte. Le billet à
   PHOTOS, lui, est REDEVENU UNE CARTE CLASSIQUE le même jour — en-tête,
   texte, photos, gestes —, ses photos dans un cadre vert pâle (--photos).
   ÉCARTÉS : le billet coupé en deux cartes autour des photos ; la carte
   entièrement transparente ; les photos débordant sur la carte ; la carte en
   dégradé remontant sous les photos ; l'en-tête hors de la carte pour TOUS
   les billets.
   Les photos sont des tirages à CADRE BLANC (--photos : 6px, 3px au
   téléphone), l'arrondi de l'image réduit d'autant pour rester
   concentrique. ── */
.ui-publication--photos { --cadre-billet: 6px; }

@media (width <= 767px) {
  .ui-publication--photos { --cadre-billet: 3px; }
}

/* Le cadre des photos, VERT PÂLE — la couleur du fond de page — et non plus
   blanc (2 oct. 2026) : dans la carte blanche du billet, un cadre blanc ne
   se voyait pas. */
.ui-publication--photos .ui-publication-galerie-ouvre {
  border: var(--cadre-billet) solid var(--surface-appuyee);
  background: var(--surface-appuyee);
}

.ui-publication--photos .ui-publication-galerie-image,
.ui-publication--photos .ui-publication-galerie-reste {
  border-radius: calc(var(--radius-md) - var(--cadre-billet));
}

.ui-publication.ui-publication--vitrine {
  background: none;
  border: 0;
  padding: 0;
  gap: 0;
}

.ui-publication--vitrine { box-shadow: none; }

/* L'en-tête garde le retrait d'une carte — même marge, filet compris,
   transparent — : la vignette de l'auteur tombe au même aplomb que sur
   tous les autres billets du fil. */
.ui-publication--vitrine > .ui-publication-entete {
  margin-bottom: var(--space-3);
  padding-inline: var(--space-4);
  border-inline: 1px solid transparent;
}

/* La vitrine, sur le fond : les photos, ou la carte de l'évènement. */
.ui-publication--vitrine > .ui-publication-galerie,
.ui-publication--vitrine > .ui-publication-jointe {
  margin: 0 0 var(--space-3);
  width: 100%;
}

/* LA SORTIE CRÉÉE (2 oct. 2026) : une seule carte, posée comme un tirage —
   la carte de l'évènement, et dans son prolongement les compteurs et les
   gestes du billet (publication.snip les range dans la vitrine) ; le texte
   du billet n'est pas montré. Le rayon standard des cartes (18px), DROITE
   (l'inclinaison des photos, essayée, est écartée pour elle). La carte de
   l'évènement y perd son propre rayon, son ombre et son soulèvement. */
/* Sélecteur à deux classes : la règle du bas de carte (« :last-child »)
   lui imposait son rayon du bas seulement, son filet et son retrait. */
.ui-publication.ui-publication--vitrine > .ui-publication-jointe {
  margin-bottom: 0;
  padding: 0;
  border: 0;
  overflow: hidden;
  background: var(--surface-card);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
}

.ui-publication--vitrine > .ui-publication-jointe .ui-evenement-carte,
.ui-publication--vitrine > .ui-publication-jointe .ui-evenement-carte:hover {
  border-radius: 0;
  box-shadow: none;
  transform: none;
}

/* « Ouvert à tous » (ou la communauté) sur la ligne de l'heure, à droite ;
   le titre dessous, sur toute la largeur. */
.ui-publication--vitrine .ui-evenement-corps {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  column-gap: var(--space-3);
  padding-bottom: var(--space-3);
}

.ui-publication--vitrine .ui-evenement-heure { grid-area: 1 / 1; margin: 0; }

.ui-publication--vitrine .ui-evenement-communaute {
  grid-area: 1 / 2;
  justify-self: end;
  height: auto;
  margin: 0;
  padding: 0;
  border: 0;
}

.ui-publication--vitrine .ui-evenement-titre {
  grid-area: 2 / 1 / 3 / 3;
  margin-top: var(--space-2);
}

/* Le mot de l'auteur, sous le titre de la sortie : dans la même carte, au
   retrait de son corps, en texte courant. */
.ui-publication--vitrine > .ui-publication-jointe > .ui-publication-legende {
  margin: 0;
  padding: 0 var(--space-4) var(--space-3);
}

.ui-publication-legende > p:last-child { margin-bottom: 0; }

.ui-publication--vitrine > .ui-publication-jointe > .ui-publication-compteurs {
  padding: 0 var(--space-4) var(--space-3);
}

.ui-publication--vitrine > .ui-publication-jointe > .ui-publication-actions {
  padding: var(--space-1) var(--space-4) var(--space-2);
}

/* LA CARTE COMMENCE SOUS LE PROFIL (2 oct. 2026) : pour une sortie créée,
   l'en-tête rentre dans la carte — une seule carte, de l'avatar aux gestes.
   C'est l'article qui la porte (fond, rayon, ombre) ; la carte de
   l'évènement, dedans, n'a plus ni rayon ni ombre à elle. */
.ui-publication.ui-publication--sortie {
  overflow: hidden;
  background: var(--surface-card);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
}

.ui-publication--sortie > .ui-publication-entete {
  margin-bottom: 0;
  padding-block: var(--space-4) var(--space-3);
}

.ui-publication.ui-publication--sortie > .ui-publication-jointe {
  border-radius: 0;
  box-shadow: none;
}

@media (width <= 560px) {
  .ui-publication--sortie > .ui-publication-entete { padding-top: var(--space-3); }
  .ui-publication--vitrine > .ui-publication-jointe > .ui-publication-legende,
  .ui-publication--vitrine > .ui-publication-jointe > .ui-publication-compteurs,
  .ui-publication--vitrine > .ui-publication-jointe > .ui-publication-actions { padding-inline: var(--space-3); }
}


.ui-publication--vitrine > :is(.ui-publication-galerie, .ui-publication-jointe) ~ * {
  margin: 0;
  padding-inline: var(--space-4);
  background: var(--surface-card);
  border-inline: 1px solid var(--border);
}

.ui-publication--vitrine > :is(.ui-publication-galerie, .ui-publication-jointe) + * {
  padding-top: var(--space-4);
  border-top: 1px solid var(--border);
  border-radius: var(--radius-md) var(--radius-md) 0 0;
}

.ui-publication--vitrine > .ui-publication-texte,
.ui-publication--vitrine > .ui-publication-compteurs { padding-bottom: var(--space-3); }

.ui-publication--vitrine > :last-child {
  padding-bottom: var(--space-2);
  border-bottom: 1px solid var(--border);
  border-radius: 0 0 var(--radius-md) var(--radius-md);
}

@media (width <= 560px) {
  .ui-publication--vitrine > .ui-publication-entete,
  .ui-publication--vitrine > :is(.ui-publication-galerie, .ui-publication-jointe) ~ * { padding-inline: var(--space-3); }
}

.ui-publication-galerie-ouvre:hover,
.ui-publication-galerie-ouvre:focus-visible {
  transform: rotate(0) translateY(-3px);
}

.ui-publication-galerie-ouvre:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  .ui-publication-galerie-ouvre { transition: none; }
}

.ui-publication-entete {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
}

/* LA PORTÉE, à la suite de la date et dans la même encre discrète : c'est une
   mention d'état, pas une commande. Elle ne paraît que sur ses propres billets
   — la portée du billet d'un autre ne nous regarde pas, et l'on n'y peut rien.
   Le point médian sépare les deux sans ponctuer : la date et la portée sont
   deux faits de même rang sur la même ligne. */
.ui-publication-portee {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
}

.ui-publication-portee::before {
  content: '·';
  margin-inline-end: var(--space-1);
}

.ui-publication-portee > .material-symbols-outlined { font-size: var(--icon-sm); }

/* LE MENU DE GESTION, poussé au bout de la ligne d'en-tête. « margin-inline-
   start: auto » plutôt qu'une position absolue : le billet n'a pas de repère
   positionné, et un absolu s'y calerait sur la carte entière — donc au-dessus
   de l'étiquette de provenance, quand il y en a une. En bout de ligne, les
   deux cohabitent d'elles-mêmes. */
.ui-publication-gerer {
  margin-inline-start: auto;
  flex: 0 0 auto;
}

/* La chevron de Bootstrap n'a pas lieu d'être : le bouton porte déjà les trois
   points, qui disent la même chose. */
.ui-menu-actions-bouton::after,
.ui-publication-gerer-bouton::after { display: none; }

/* LE MENU EST TOUJOURS EN BOUT DE SA RANGÉE. « auto » sur la marge de début
   plutôt qu'un « space-between » posé sur le conteneur : celui-ci écarterait
   aussi les éléments voisins les uns des autres, alors qu'on ne veut détacher
   que le menu. Il ne fait pas partie de ce qui décrit la chose — il porte ce
   qu'on peut en faire. */
.ui-menu-actions {
  margin-inline-start: auto;
  /* Centré sur sa rangée : il se pose souvent dans une ligne de texte alignée
     sur la ligne de base, où un rond se calerait sur le bas de la lettre. */
  align-self: center;
}

.ui-menu-actions-bouton,
.ui-publication-gerer-bouton {
  /* FOND BLANC, comme les trois ronds d'une carte profil. L'atome est
     transparent — il a été dessiné pour une page claire — et le rond
     paraissait alors vide, un simple cerne posé sur le fond. */
  background: var(--white);
  border-color: var(--border);
  color: var(--text-muted);
}

/* LE ROND À TROIS POINTS D'UN BILLET NE PARAÎT QU'AU SURVOL DE SA CARTE, comme
   les boutons d'édition du profil. Dix cartes, dix ronds allumés en
   permanence : on lisait les commandes avant les publications, alors qu'un fil
   se lit.
   « opacity » et non « display: none » — un bouton retiré du rendu sort du
   parcours au clavier et le menu deviendrait inatteignable sans souris.
   LA RÈGLE EST À PART, et c'est une correction : posée dans le bloc au-dessus,
   elle valait aussi pour « .ui-menu-actions-bouton », qui partage ce dessin
   sans partager cet usage — le rond des actions lourdes d'un profil et celui
   de la visionneuse s'éteignaient avec lui, alors qu'ils sont seuls de leur
   espèce sur leur page et n'ont pas de carte à survoler. */
.ui-publication-gerer-bouton {
  opacity: 0;
  transition: opacity 0.15s ease, background 0.2s ease, color 0.2s ease;
}

/* « aria-expanded » EST INDISPENSABLE ICI, et c'est ce qui distingue ce cas de
   celui du profil : le menu ouvert déborde de la carte, et la souris qui va le
   chercher quitte le billet. Sans cette ligne, le bouton s'éteignait sous le
   curseur au moment même où l'on choisissait dans son menu. */
.ui-publication:hover .ui-publication-gerer-bouton,
.ui-publication-gerer-bouton:focus-visible,
.ui-publication-gerer-bouton[aria-expanded="true"] {
  opacity: 1;
}

.ui-menu-actions-bouton > .material-symbols-outlined,
.ui-publication-gerer-bouton > .material-symbols-outlined { font-size: var(--icon-md); }

.ui-publication-avatar {
  width: 44px;
  height: 44px;
  border-radius: var(--radius-photo);
  object-fit: cover;
  flex-shrink: 0;
}

.ui-publication-identite { flex: 1; min-width: 0; }

.ui-publication-auteur {
  margin: 0;
  color: var(--text);
  font-weight: 700;
  line-height: 1.3;
}

/* Le mot de liaison — « a publié dans » — n'est pas un nom propre : il revient
   au poids et à la couleur d'un texte courant pour que les deux noms qui
   l'encadrent ressortent seuls. */
.ui-publication-auteur span {
  color: var(--text-muted);
  font-weight: 400;
}

.ui-publication-source {
  color: var(--color-primary);
  font-weight: 700;
}

.ui-publication-source:hover { text-decoration: underline; }

.ui-publication-meta {
  margin: 0;
  color: var(--text-muted);
  font-size: var(--fs-caption);
}

/* Étiquette de PROVENANCE. Trois teintes pour trois origines, prises dans la
   palette existante : le pétrole pour ce qu'on suit, la sauge pour les
   communautés, le rose pour les évènements — l'évènement est le seul des
   trois qui appelle une action datée, il porte donc la couleur d'appel.
   Libellés en encre sur des aplats très dilués : mesurés au-dessus de 4,5. */
/* LE CŒUR DU MATCH, en petit badge rond juste après le nom de l'auteur —
   à la place de la pastille « Vous avez matché » posée en bout de ligne, qui
   flottait sans rien pour la borner (décision du 27 sept. 2026). Le sens
   est dans l'infobulle et le libellé masqué. */
/* Sélecteur à deux classes : « .ui-publication-auteur span » (le mot de
   liaison, gris) l'emportait sinon et éteignait le cœur. */
.ui-publication-auteur .ui-publication-match {
  display: inline-grid;
  place-items: center;
  width: 22px;
  height: 22px;
  margin-inline-start: var(--space-2);
  border-radius: 50%;
  background: color-mix(in srgb, var(--color-brand-pink-ink) 12%, var(--white));
  color: var(--color-brand-pink-ink);
  font-weight: 400;
}

/* Le nom et le cœur, centrés l'un sur l'autre. Un réglage de
   « vertical-align » au pixel ne tenait pas : le badge, plus haut que la
   lettre, déplaçait la ligne elle-même. Un billet de match n'a que le nom
   sur cette ligne (pas de « a publié dans »), la rangée flexible est donc
   sans risque. */
.ui-publication-auteur .ui-publication-match--contact {
  background: color-mix(in srgb, var(--color-primary) 12%, var(--white));
  color: var(--color-primary);
}

.ui-publication-auteur:has(.ui-publication-match) {
  display: flex;
  align-items: center;
}

.ui-publication-auteur .ui-publication-match > .material-symbols-outlined {
  color: inherit;
  font-size: var(--icon-sm);
  font-variation-settings: 'FILL' 1;
}

.ui-publication-etiquette {
  flex-shrink: 0;
  border-radius: var(--radius-pill);
  padding: var(--space-1) var(--space-2);
  font-size: var(--fs-caption);
  font-weight: 600;
  white-space: nowrap;
}

.ui-publication-etiquette--profil {
  background: color-mix(in srgb, var(--color-titre) 12%, var(--white));
  color: var(--color-titre);
}

.ui-publication-etiquette--match {
  background: var(--color-brand-pink-soft);
  color: var(--color-brand-pink-ink);
}

/* Le MATCH est la seule pastille rose, et la seule à porter un pictogramme.
   Trois autres variantes ont existé — communauté, évènement, mes propres
   billets — puis ont été retirées une à une : chacune reprenait ce que
   l'en-tête du billet disait déjà un centimètre plus haut. Il ne reste que
   les deux qui apprennent quelque chose, « Vous avez matché » et « Vous le
   suivez », et une seule d'entre elles appelle le regard.

   Mesuré : encre rose sur rose dilué, 4,65:1 — au-dessus des 4,5 d'un petit
   texte. Le rose PLEIN en fond avec un libellé blanc n'aurait donné que 3,77,
   ce qui passe pour un bouton mais pas pour une pastille de 12px. */
.ui-publication-etiquette--match {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
}

.ui-publication-etiquette--match .material-symbols-outlined {
  font-size: var(--icon-sm);
  font-variation-settings: 'FILL' 1, 'wght' 500;
}

.ui-publication-texte {
  margin: 0;
  color: var(--text);
  line-height: var(--lh-base);
}

/* Le visuel sort du rembourrage de la carte et va d'un bord à l'autre : une
   photo encadrée de blanc paraît collée dessus, une photo à fleur de bord
   appartient au billet. */
/* Le visuel sort du rembourrage de la carte et va d'un bord à l'autre : une
   photo encadrée de blanc paraît collée dessus, une photo à fleur de bord
   appartient au billet. */
.ui-publication-visuel,
.ui-publication-jointe {
  margin-inline: calc(var(--space-4) * -1);
  width: calc(100% + var(--space-4) * 2);
}

.ui-publication-visuel {
  display: block;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}

/* La carte d'évènement JOINTE, elle, garde sa marge : c'est un objet
   autonome, avec son propre bord et sa propre ombre. La sortir à fleur du
   billet en aurait fait un fond. */
.ui-publication-jointe {
  margin-inline: 0;
  width: 100%;
}

.ui-publication-compteurs {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
  color: var(--text-muted);
  font-size: var(--fs-sm);
}

.ui-publication-compteurs .material-symbols-outlined {
  color: var(--color-brand-pink);
  font-size: var(--icon-sm);
  font-variation-settings: 'FILL' 1;
}

/* La mention qui REMPLACE les compteurs quand les réactions sont privées.
   Cadenas gris et non cœur rose : ce n'est pas une réaction, c'est une règle.
   Le rose aurait suggéré qu'il se passe quelque chose là où justement on ne
   montre rien. */
.ui-publication-compteurs--prive { color: var(--text-muted); }

.ui-publication-compteurs--prive .material-symbols-outlined {
  color: var(--text-muted);
  font-size: var(--icon-sm);
  font-variation-settings: 'FILL' 0;
}

/* La même mention, devenue le bouton qui ouvre la discussion. Elle garde
   exactement son apparence au repos : rien n'a été ajouté à la carte, une
   ligne qui s'y trouvait déjà est devenue cliquable. C'est au survol qu'elle
   se déclare — soulignée et au pétrole, cadenas compris. */
/* La mention privée cliquable est au PÉTROLE en permanence, cadenas compris.
   Elle était grise et ne se déclarait qu'au survol : c'est le seul chemin
   vers la discussion sur ces billets — il n'y a ni décompte ni annonce
   nommée à côté — et un chemin unique ne peut pas dépendre d'un survol pour
   se faire connaître. Rien au clavier ni au doigt ne survole.
   Pétrole sur le blanc de la carte : 7,64:1. */
.ui-publication-compteurs--ouvre {
  border: 0;
  background: transparent;
  padding: 0;
  color: var(--color-titre);
  font-family: inherit;
  text-align: start;
  cursor: pointer;
}

.ui-publication-compteurs--ouvre .material-symbols-outlined { color: var(--color-titre); }

/* Pas de soulignement : le trait passait sous le cadenas comme il passait
   sous le cœur du décompte. La mention étant déjà au pétrole en permanence,
   c'est un assombrissement qui répond au survol. */
.ui-publication-compteurs--ouvre { transition: color 0.2s ease; }

.ui-publication-compteurs--ouvre:hover,
.ui-publication-compteurs--ouvre:focus-visible,
.ui-publication-compteurs--ouvre:hover .material-symbols-outlined,
.ui-publication-compteurs--ouvre:focus-visible .material-symbols-outlined {
  color: var(--color-primary-hover);
}

/* « Nora, Léa et 10 autres personnes ont commenté » — visages compris — ouvre
   la fenêtre. Le nombre de j'aime qui la précède reste hors du bouton : on ne
   clique pas sur un décompte de likes pour lire des commentaires.
   Elle hérite tout de la ligne qui la porte et ne se déclare qu'au survol,
   comme la mention privée. Rendue en <span> quand il n'y a pas de fil, avec
   les mêmes valeurs : la ligne ne change pas d'aspect selon qu'elle mène
   quelque part ou non, elle change seulement de comportement. */
.ui-publication-commente {
  display: inline-flex;
  align-items: center;
  border: 0;
  background: transparent;
  padding: 0;
  color: inherit;
  font: inherit;
  text-align: start;
}

button.ui-publication-commente { cursor: pointer; transition: color 0.2s ease; }

button.ui-publication-commente:hover,
button.ui-publication-commente:focus-visible {
  color: var(--color-titre);
  text-decoration: underline;
}

/* Pile d'avatars des personnes qui ont aimé. Elles se CHEVAUCHENT de dix
   pixels : trois vignettes de vingt-deux qui se recouvrent occupent la place
   de deux, et le chevauchement dit « un groupe » là où trois vignettes
   alignées auraient dit « trois personnes ». Le liseré blanc les détache les
   unes des autres, faute de quoi la pile se lit comme une tache. */
.ui-publication-avatars {
  display: inline-flex;
  flex-shrink: 0;
  margin-inline-end: 0.125rem;
}

.ui-publication-avatars img {
  width: 22px;
  height: 22px;
  border-radius: var(--radius-photo);
  border: 2px solid var(--surface-card);
  object-fit: cover;
}

.ui-publication-avatars img + img { margin-inline-start: calc(var(--space-2) * -1); }

.ui-publication-actions {
  display: flex;
  border-top: 1px solid var(--border);
  padding-top: var(--space-1);
}

.ui-publication-action {
  flex: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  border: 0;
  border-radius: var(--radius-md);
  background: transparent;
  min-height: var(--control-h-sm);
  padding: 0 var(--space-2);
  color: var(--text-muted);
  font-family: inherit;
  font-size: var(--fs-sm);
  font-weight: 600;
  cursor: pointer;
  transition: background 0.2s ease, color 0.2s ease;
}

.ui-publication-action .material-symbols-outlined { font-size: var(--icon-md); }

.ui-publication-action:hover,
.ui-publication-action:focus-visible {
  background: var(--surface-douce);
  color: var(--color-titre);
}

/* Le like qu'on a laissé : cœur plein et libellé à l'encre rose. C'est le
   seul endroit du billet où l'état d'une action se lit sur l'action
   elle-même — et c'est bien là qu'on le cherche. 5,42:1 sur le blanc de la
   carte. Écrit après le survol pour qu'il le reprenne au lieu de l'effacer. */
.ui-publication-action.is-active,
.ui-publication-action.is-active:hover,
.ui-publication-action.is-active:focus-visible {
  color: var(--color-brand-pink-ink);
}

.ui-publication-action.is-active .material-symbols-outlined {
  font-variation-settings: 'FILL' 1;
}

/* « Message privé » : le libellé au pétrole, tout le reste identique à ses
   voisins. C'est l'action la plus forte du billet — écrire à quelqu'un avec
   qui la réciprocité est faite — et la seule qui mène ailleurs que dans le
   fil. La couleur suffit à le dire : un cadre ou un fond en aurait fait un
   bouton d'une autre espèce, posé au milieu de trois commandes de même rang.
   Pétrole sur le blanc de la carte : 7,10:1. */
.ui-publication-action--prive { color: var(--color-titre); }

.ui-publication-action--prive:hover,
.ui-publication-action--prive:focus-visible {
  background: color-mix(in srgb, var(--color-titre) 12%, var(--white));
  color: var(--color-primary-hover);
}

@media (width <= 560px) {
  .ui-publication { padding: var(--space-3) var(--space-3) var(--space-2); }

  .ui-publication-visuel {
    margin-inline: calc(var(--space-3) * -1);
    width: calc(100% + var(--space-3) * 2);
  }

  /* Le libellé de l'action tombe, le pictogramme reste : à trois boutons sur
     une ligne étroite, c'est le texte qui casse en premier. */
  .ui-publication-action { font-size: 0; gap: 0; }
  .ui-publication-action .material-symbols-outlined { font-size: var(--icon-md); }
}

/* ---- Discussion d'une publication (fenêtre modale) ----
   Bootstrap fournit le voile, le centrage et le défilement ; on n'habille ici
   que ce qu'il laisse en style « admin ». « modal-dialog-scrollable » fait
   défiler le CORPS seul : c'est ce qui rend l'en-tête et le pied collants
   sans une ligne de position. ---- */
.ui-discussion-dialog {
  max-width: 640px;
  /* Elle ne monte jamais plus haut que 88 % de l'écran : au-delà, le champ de
     saisie touche le bord et l'on ne voit plus qu'elle est une fenêtre. */
  max-height: 88vh;
}

.ui-discussion-content {
  border: 0;
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  overflow: hidden;
}

.ui-discussion-entete {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  border-bottom: 1px solid var(--border);
  padding: var(--space-3) var(--space-4);
  flex-shrink: 0;
}

.ui-discussion-titre {
  margin: 0;
  color: var(--text);
  font-size: var(--fs-h5);
  font-weight: 700;
}

.ui-discussion-corps { padding: var(--space-4); }

.ui-discussion-billet { margin-bottom: var(--space-3); }

.ui-discussion-billet-entete {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}

.ui-discussion-billet-entete img {
  width: 40px;
  height: 40px;
  border-radius: var(--radius-photo);
  object-fit: cover;
  flex-shrink: 0;
}

.ui-discussion-billet-entete > div { display: flex; flex-direction: column; }
/* LA FENÊTRE D'UNE PUBLICATION : le rayon standard sur écran large (2 oct.
   2026, après des angles droits partout le 29 sept.) ; au téléphone, en
   plein écran, ses angles restent droits (ui-organismes.css). */

/* Dans l'en-tête de la fenêtre, à côté de la croix : plus de marge basse, le
   filet de l'en-tête fait la séparation. */
.ui-discussion-entete > .ui-discussion-billet-entete { margin-bottom: 0; min-width: 0; }

.ui-discussion-billet-texte {
  margin: 0;
  color: var(--text);
  line-height: var(--lh-base);
}

/* La photo sort du rembourrage, comme sur la carte du fil : c'est le même
   billet, il ne peut pas se cadrer autrement d'un endroit à l'autre. */
.ui-discussion-billet-visuel {
  display: block;
  margin: var(--space-3) calc(var(--space-4) * -1) 0;
  width: calc(100% + var(--space-4) * 2);
  max-height: 380px;
  object-fit: cover;
}

.ui-discussion-compte {
  border-bottom: 1px solid var(--border);
  margin: 0 0 var(--space-3);
  padding-bottom: var(--space-3);
  color: var(--text-muted);
  font-size: var(--fs-sm);
}

.ui-discussion-fil,
.ui-discussion-reponses {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

/* LES RÉPONSES SE CALENT SOUS L'HEURE du commentaire — 0,875rem, le même
   retrait que .ui-discussion-meta, et pas un autre nombre : « Il y a 4 jours »
   et la première réponse partent de la même abscisse, si bien que le fil
   dessine une seule verticale au lieu de deux.
   ÉCARTÉ : 2,75rem, l'avatar plus son écart (32 + 12), qui alignait les
   réponses sur le bord de la BULLE. C'est le retrait des messageries, mais il
   ouvrait une troisième colonne à l'œil, entre le bord du commentaire et
   celui de ses réponses. */
.ui-discussion-reponses {
  margin-top: var(--space-2);
  padding-inline-start: var(--space-3);
}

.ui-discussion-message {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
}

.ui-discussion-message > img {
  width: 32px;
  height: 32px;
  border-radius: var(--radius-photo);
  object-fit: cover;
  flex-shrink: 0;
}

.ui-discussion-message > div { flex: 1; min-width: 0; }

/* La BULLE : un fond doux et un rayon, la convention du message. Elle
   s'arrête à son contenu (width: fit-content) — une bulle qui court sur toute
   la largeur pour trois mots se lit comme un paragraphe, pas comme une
   parole. */
.ui-discussion-bulle {
  width: fit-content;
  max-width: 100%;
  border-radius: var(--radius-md);
  background: var(--surface-douce);
  padding: var(--space-2) var(--space-3);
}

.ui-discussion-nom {
  color: var(--text);
  font-size: var(--fs-sm);
  font-weight: 700;
  line-height: 1.3;
}

.ui-discussion-bulle p {
  margin: 0.125rem 0 0;
  color: var(--text);
  font-size: var(--fs-sm);
  line-height: 1.5;
}

.ui-discussion-meta {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin: var(--space-1) 0 0;
  padding-inline-start: var(--space-3);
  color: var(--text-muted);
  font-size: var(--fs-caption);
}

.ui-discussion-billet-entete .ui-discussion-meta { padding-inline-start: 0; }

.ui-discussion-lien {
  border: 0;
  background: transparent;
  padding: 0;
  color: var(--text-muted);
  font-family: inherit;
  font-size: var(--fs-caption);
  font-weight: 700;
  cursor: pointer;
}

.ui-discussion-lien:hover,
.ui-discussion-lien:focus-visible { color: var(--color-titre); text-decoration: underline; }

/* ---- Le pied collant ---- */
.ui-discussion-pied {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  border-top: 1px solid var(--border);
  background: var(--surface-card);
  padding: var(--space-3) var(--space-4);
  flex-shrink: 0;
}

.ui-discussion-pied > img {
  width: 36px;
  height: 36px;
  border-radius: var(--radius-photo);
  object-fit: cover;
  flex-shrink: 0;
}

.ui-discussion-champ {
  position: relative;
  flex: 1;
}

/* « width: 100% » ET C'EST UN CORRECTIF. Un <input> ne s'étire pas tout seul :
   sans largeur, il prend celle de son attribut « size » — vingt caractères,
   soit 226px mesurés dans une colonne de 250. Le champ, lui, faisait bien ses
   250px, et le bouton d'envoi, calé sur SON bord droit, tombait 20px au-delà
   du bord arrondi de l'input : l'avion de papier était coupé par la bordure.
   Le creux réservé à droite (padding-inline-end) ne servait à rien tant que
   les deux bords ne coïncidaient pas. */
.ui-discussion-champ input {
  width: 100%;
  border-radius: var(--radius-pill);
  background: var(--surface-douce);
  padding-inline-end: var(--space-5);
}

/* LE CREUX RÉSERVÉ AUX COMMANDES suit leur NOMBRE, et le nombre dépend de
   l'hôte : la fenêtre d'une publication n'a que l'envoi, la visionneuse a
   aussi la palette d'emojis. « :has » lit le balisage au lieu d'un
   modificateur qu'il aurait fallu poser à la main — et qu'on aurait oublié le
   jour où une troisième commande arrive. */
/* L'EMOJI À GAUCHE, l'envoi à droite (29 sept. 2026) : ce qui enrichit le
   texte se tient à son début, ce qui le conclut à sa fin — la même
   disposition que la barre d'envoi de la messagerie. */
.ui-discussion-champ:has(.ui-discussion-emoji) input { padding-inline-start: var(--space-5); }

.ui-discussion-emoji {
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 0.25rem;
  transform: translateY(-50%);
  z-index: 1;
}

/* LES COMMANDES SONT DANS LE CHAMP, alignées à droite. Elles étaient posées
   une par une en absolu tant qu'il n'y en avait qu'une ; à deux, chacune
   aurait porté son propre décalage — et le troisième bouton aurait demandé un
   troisième nombre. Une rangée les range toute seule. */
.ui-discussion-outils {
  position: absolute;
  inset-block-start: 50%;
  inset-inline-end: 0.25rem;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  gap: 0.125rem;
}

/* Le bouton des emojis, au dessin de la flèche d'envoi : ni rond foncé ni
   bordure, même ouvert (29 sept. 2026). */
.ui-discussion-emoji .ui-icon-button,
.ui-discussion-emoji .ui-icon-button.show,
.ui-discussion-emoji .ui-icon-button:active,
.ui-discussion-emoji .ui-icon-button:focus {
  width: 34px;
  height: 34px;
  border: 0;
  background: transparent;
  color: var(--color-titre);
  box-shadow: none;
}
.ui-discussion-emoji .ui-icon-button:hover { background: var(--surface-douce); }

.ui-discussion-envoi {
  display: inline-grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--color-titre);
  cursor: pointer;
  transition: background 0.2s ease;
}

.ui-discussion-envoi:hover,
.ui-discussion-envoi:focus-visible { background: var(--surface-appuyee); }

@media (width <= 560px) {
  .ui-discussion-dialog { max-height: 94vh; }
  .ui-discussion-corps,
  .ui-discussion-entete,
  .ui-discussion-pied { padding-inline: var(--space-3); }
  .ui-discussion-billet-visuel {
    margin-inline: calc(var(--space-3) * -1);
    width: calc(100% + var(--space-3) * 2);
  }
  .ui-discussion-reponses { padding-inline-start: var(--space-4); }
}


/* Le décompte de j'aime, devenu bouton. Mêmes valeurs que le texte qui
   l'entoure — il ne doit pas se distinguer de la ligne au repos, pas plus
   que l'annonce des commentaires à sa droite. Le cœur reste rose : c'est sa
   couleur partout ailleurs, et elle ne signale pas ici l'interactivité. */
.ui-publication-jaime {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  border: 0;
  background: transparent;
  padding: 0;
  color: inherit;
  font: inherit;
  cursor: pointer;
  transition: color 0.2s ease;
}

/* Pas de soulignement au survol, contrairement à l'annonce des commentaires
   à sa droite : le trait passait SOUS LE CŒUR autant que sous le chiffre, et
   un pictogramme souligné se lit comme un défaut de rendu. Le changement de
   couleur suffit à dire que la ligne répond. */
.ui-publication-jaime:hover,
.ui-publication-jaime:focus-visible {
  color: var(--color-titre);
}

/* ---- Liste des j'aime (fenêtre modale) ----
   Plus petite que la fenêtre de discussion, et sans pied : on y vient
   chercher une réponse d'un mot — « qui ? » — et on repart. ---- */
.ui-jaime-dialog { max-width: 420px; max-height: 78vh; }

.ui-jaime-content {
  border: 0;
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  overflow: hidden;
}

.ui-jaime-entete {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  border-bottom: 1px solid var(--border);
  padding: var(--space-3) var(--space-4);
  flex-shrink: 0;
}

.ui-jaime-titre {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
  color: var(--text);
  font-size: var(--fs-h5);
  font-weight: 700;
}

.ui-jaime-titre .material-symbols-outlined {
  color: var(--color-brand-pink);
  font-size: var(--icon-md);
  font-variation-settings: 'FILL' 1;
}

.ui-jaime-corps { padding: var(--space-2) var(--space-4) var(--space-4); }

.ui-jaime-liste { list-style: none; margin: 0; padding: 0; }

.ui-jaime-personne {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding-block: var(--space-2);
}

.ui-jaime-avatar {
  width: 44px;
  height: 44px;
  border-radius: var(--radius-photo);
  object-fit: cover;
  flex-shrink: 0;
}

.ui-jaime-corps-texte { flex: 1; min-width: 0; display: flex; flex-direction: column; }

.ui-jaime-nom {
  color: var(--text);
  font-weight: 700;
  line-height: 1.3;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ui-jaime-nom:hover { color: var(--color-titre); }

.ui-jaime-meta {
  color: var(--text-muted);
  font-size: var(--fs-sm);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ui-jaime-action {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: transparent;
  padding: var(--space-2) var(--space-3);
  color: var(--color-titre);
  font-family: inherit;
  font-size: var(--fs-sm);
  font-weight: 600;
  cursor: pointer;
  transition: background 0.2s ease, border-color 0.2s ease, color 0.2s ease;
}

.ui-jaime-action .material-symbols-outlined { font-size: var(--icon-md); }

.ui-jaime-action:hover,
.ui-jaime-action:focus-visible {
  background: var(--color-titre);
  border-color: var(--color-titre);
  color: var(--white);
}

@media (width <= 560px) {
  .ui-jaime-entete,
  .ui-jaime-corps { padding-inline: var(--space-3); }
  .ui-jaime-action { font-size: 0; gap: 0; padding: var(--space-2); }
  .ui-jaime-action .material-symbols-outlined { font-size: var(--icon-md); }
}

/* ---- Mes critères (fenêtre modale) ----
   Une fenêtre de réglages, plus large que celle de discussion : elle porte des
   champs par rangées de deux, et non une colonne de messages. ---- */
.ui-criteres-dialog { max-width: 720px; max-height: 88vh; }

.ui-criteres-content {
  border: 0;
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  overflow: hidden;
}

/* ── LA FENÊTRE D'UN GROUPE, AÉRÉE (espace Paramètres) ───────────────────
   Un groupe seul n'a pas besoin d'être dense : un bloc par critère, sous un
   filet, avec de l'air autour ; le nom en encre de titre ; « Important » à
   droite du nom plutôt que sur une ligne à lui, perdu sous les pastilles. */
.ui-criteres--aere .ui-criteres-corps { padding: var(--space-3) var(--space-5) var(--space-4); }

.ui-criteres--aere .ui-criteres-entete,
.ui-criteres--aere .ui-criteres-pied { padding-inline: var(--space-5); }

/* La grille seule : le corps d'un groupe REPLIABLE est un « collapse » de
   Bootstrap, que « display: flex » tiendrait ouvert. */
.ui-criteres--aere .ui-criteres-grille {
  display: flex;
  flex-direction: column;
  gap: 0;
}

.ui-criteres--aere .ui-criteres-champ { padding: var(--space-4) 0; }

.ui-criteres--aere .ui-criteres-champ + .ui-criteres-champ { border-top: 1px solid var(--border); }

/* La tête d'un critère : son nom à gauche, « Important » à droite. */
.ui-criteres-champ-tete {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}

.ui-criteres .ui-criteres-champ-nom {
  color: var(--color-titre);
  font-size: var(--fs-base);
  font-weight: var(--fw-semibold);
}

.ui-criteres--aere .ui-criteres-cases { gap: var(--space-2); }

.ui-criteres--aere .ui-criteres-champ-tete .ui-criteres-important { margin: 0; }

.ui-criteres--aere .ui-criteres-champ > .ui-form-select { max-width: 360px; }

/* EN PAGE (« Mes critères ») : la même carte, posée dans la page au lieu
   de flotter — même largeur que la fenêtre, fond de carte, ombre légère. Le
   pied colle au bas de l'écran : on enregistre sans remonter. */
/* La largeur est celle du contenu de l'espace (.ui-espace-contenu). */

.ui-criteres--page .ui-criteres-content {
  display: flex;
  flex-direction: column;
  background: var(--surface-card);
  /* « hidden » (la fenêtre) empêcherait le pied de coller au bas de l'écran. */
  overflow: visible;
}

.ui-criteres--page .ui-criteres-pied {
  position: sticky;
  bottom: 0;
  z-index: 2;
  border-top: 1px solid var(--border);
  background: var(--surface-card);
}

.ui-criteres-retour { margin-inline-end: auto; }

.ui-criteres-entete,
.ui-criteres-pied {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  flex-shrink: 0;
}

.ui-criteres-entete {
  justify-content: space-between;
  border-bottom: 1px solid var(--border);
}

.ui-criteres-titre {
  margin: 0;
  color: var(--text);
  font-size: var(--fs-h5);
  font-weight: 700;
}

.ui-criteres-corps { padding: var(--space-4); }

.ui-criteres-intro {
  margin: 0 0 var(--space-4);
  color: var(--text-muted);
  font-size: var(--fs-sm);
  line-height: var(--lh-base);
}

.ui-criteres-groupe + .ui-criteres-groupe { border-top: 1px solid var(--border); }

/* ---- En-tête de groupe ----
   Toute la ligne est le déclencheur : nom, résumé et chevron. Une zone de
   clic qui court d'un bord à l'autre se vise sans regarder, là où un chevron
   seul demande d'y aller. ---- */
.ui-criteres-groupe-tete { margin: 0; }

.ui-criteres-groupe-bouton {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  border: 0;
  background: transparent;
  padding: var(--space-3) 0;
  color: inherit;
  font: inherit;
  text-align: start;
  cursor: pointer;
}

/* Le titre de groupe EN GRAS, à l'encre des titres (30 sept. 2026) : en
   petites capitales grises, il se lisait à peine. Le résumé à côté garde
   son gris et son poids normal — c'est un détail, pas un titre. */
.ui-criteres-groupe-titre {
  flex-shrink: 0;
  color: var(--color-titre);
  font-size: var(--fs-base);
  font-weight: 700;
  transition: color 0.2s ease;
}

.ui-criteres-groupe-bouton:hover .ui-criteres-groupe-titre,
.ui-criteres-groupe-bouton:focus-visible .ui-criteres-groupe-titre { color: var(--color-titre); }

/* Le COMPTE de sélections du groupe, à côté de son nom. Sans lui, un groupe
   replié ne dirait pas s'il est réglé ou vide, et il faudrait les ouvrir tous
   pour le savoir — ce que le repli était censé éviter.

   Un nombre et non la liste des valeurs : « Sports 6 » se lit d'un coup,
   « Course à pied · Vélo · Padel +3 » demande de lire une phrase pour
   apprendre la même chose, et poussait le chevron hors de la fenêtre.

   Pastille pétrole pleine, comme toute sélection de l'espace membre : blanc
   sur pétrole, 7,64:1. Elle reste visible quand le groupe est ouvert — elle
   confirme le compte pendant qu'on coche. */
/* Le choix d'un groupe replié, en une ligne coupée par « … » — comme la
   ligne de « Mes critères de recherche ». Le titre garde sa largeur. */
.ui-criteres-groupe-titre { flex-shrink: 0; }
.ui-criteres-groupe-choix {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  color: var(--text-muted);
  font-size: var(--fs-sm);
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ui-criteres-groupe-bouton .ui-espace-important { flex-shrink: 0; }
.ui-criteres-groupe-bouton .ui-criteres-chevron { margin-inline-start: auto; }
.ui-criteres-groupe-choix + .ui-criteres-chevron,
.ui-espace-important + .ui-criteres-chevron { margin-inline-start: 0; }

.ui-criteres-resume {
  flex-shrink: 0;
  min-width: 1.375rem;
  border-radius: var(--radius-pill);
  background: var(--color-titre);
  padding: 0.0625rem var(--space-2);
  color: var(--white);
  font-size: var(--fs-caption);
  font-weight: 600;               /* aligné sur la pastille de notifications */
  line-height: 1.4;
  text-align: center;
}

.ui-criteres-chevron {
  margin-inline-start: auto;
  flex-shrink: 0;
  color: var(--text-muted);
  font-size: var(--icon-lg);
  transition: transform 0.2s ease;
}

.ui-criteres-groupe-bouton[aria-expanded="true"] .ui-criteres-chevron { transform: rotate(180deg); }

.ui-criteres-groupe-corps { padding-bottom: var(--space-4); }

.ui-criteres-grille {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--space-3);
}

.ui-criteres-champ {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
}

.ui-criteres-champ label {
  color: var(--text-muted);
  font-size: var(--fs-caption);
  font-weight: 700;
}

/* Les actions sont à DROITE et la sortie précède la validation : c'est
   l'ordre où l'on trouve le bouton qui engage sous le pouce, en dernier. */
.ui-criteres-pied {
  justify-content: flex-end;
  border-top: 1px solid var(--border);
  background: var(--surface-card);
}

/* Les marges du plein écran (1er oct. 2026) : 16px dès que la fenêtre prend
   tout l'écran, jusqu'à 767px — la variante aérée comprise, dont les 48px
   l'emportaient. Les fenêtres d'édition suivent, plus bas. */
@media (width <= 767px) {
  .ui-criteres-entete,
  .ui-criteres-corps,
  .ui-criteres-pied,
  .ui-criteres--aere .ui-criteres-entete,
  .ui-criteres--aere .ui-criteres-corps,
  .ui-criteres--aere .ui-criteres-pied { padding-inline: var(--space-3); }
}

@media (width <= 560px) {
  .ui-criteres-grille { grid-template-columns: minmax(0, 1fr); }
  .ui-criteres-pied .ui-btn { flex: 1 1 0; }
}

/* ---- Critères à cases ----
   Un groupe de cases prend toute la largeur de la grille : cinq ou six
   pastilles ne tiennent pas dans la colonne de 200px prévue pour une liste
   déroulante, et s'y empileraient une par ligne. */
.ui-criteres-champ--cases {
  grid-column: 1 / -1;
  border: 0;
  margin: 0;
  padding: 0;
  min-width: 0;
}

.ui-criteres-champ--cases legend {
  float: none;                       /* Bootstrap le passe en 100 % : on annule */
  width: auto;
  margin-bottom: var(--space-2);
  padding: 0;
  color: var(--text-muted);
  font-size: var(--fs-caption);
  font-weight: 700;
}

/* « Important » : un interrupteur discret sous le critère. Il ne change
   pas la liste, seulement le poids du critère dans le taux. */
.ui-criteres-champ .ui-criteres-important {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin: var(--space-2) 0 0;
  padding-left: 2.5em;
  color: var(--text-muted);
  font-size: var(--fs-caption);
  font-weight: 600;
  cursor: pointer;
}

.ui-criteres-important .form-check-input { cursor: pointer; }
.ui-criteres-important:has(input:checked) { color: var(--color-titre); }

.ui-criteres-cases {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

/* La case elle-même est retirée du dessin mais reste dans le document : elle
   garde le focus, le clavier et la sémantique. C'est son étiquette qui prend
   l'apparence d'une pastille — le clic sur la pastille coche donc la case
   sans une ligne de script. */
.ui-criteres-case input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

/* Sélecteur d'ENFANT DIRECT : « .ui-criteres-case span » attrapait aussi le
   pictogramme, qui devenait à son tour un conteneur flex — et le nom de
   glyphe cessait de se replier en ligature, si bien que « directions_run »
   s'affichait en toutes lettres dans la police d'icônes. Mesuré : 46px de
   large pour une pastille qui en attend 18. */
.ui-criteres-case > span {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--surface-card);
  padding: var(--space-2) var(--space-3);
  color: var(--text);
  font-size: var(--fs-sm);
  font-weight: 600;
  cursor: pointer;
  transition: background 0.2s ease, border-color 0.2s ease, color 0.2s ease;
}

/* Le pictogramme de la pastille : plus petit que le libellé et posé devant.
   Il ne prend pas la couleur du texte au repos — un gris plus clair, pour
   qu'il accroche l'œil sans rivaliser avec le mot qu'il annonce. Coché, il
   passe au blanc avec le reste. */
.ui-criteres-case > span .material-symbols-outlined {
  font-size: var(--icon-md);
  color: var(--text-muted);
  flex-shrink: 0;
}

.ui-criteres-case input:checked + span .material-symbols-outlined { color: inherit; }

.ui-criteres-case > span:hover {
  border-color: var(--color-titre);
  color: var(--color-titre);
}

/* Cochée : l'aplat pétrole plein, comme toute sélection du site. 7,64:1. */
.ui-criteres-case input:checked + span {
  background: var(--color-titre);
  border-color: var(--color-titre);
  color: var(--white);
}

/* Le focus se voit sur la pastille, puisque la case ne se voit pas. */
.ui-criteres-case input:focus-visible + span {
  outline: 3px solid color-mix(in srgb, var(--color-titre) 40%, transparent);
  outline-offset: 2px;
}

/* ---- LA FENÊTRE DE CONFIRMATION ----
   Écrite au niveau des MOLÉCULES, avec le gabarit qui la porte. Elle a d'abord
   vécu dans l'organisme du profil, qui était seul à l'employer ; la page des
   profils s'en sert désormais aussi, et le contrôle des couches l'a signalé —
   une classe se définit là où elle est employée, jamais au-dessus.
   Petite, centrée, trois lignes : la question, le nom, deux boutons. Elle ne
   reprend pas le gabarit générique de fenêtre — celui-ci prévoit un corps
   libre, quand celle-ci n'a qu'une phrase à poser et deux issues à offrir.
   Le nom est en GRAND et la question en petit : on relit toujours le nom pour
   vérifier qu'on n'a pas cliqué sur la mauvaise carte, jamais la question. */
.ui-confirmation .ui-modal-content {
  max-width: 420px;
  margin-inline: auto;
}

.ui-confirmation .ui-modal-title {
  font-size: var(--fs-base);
  font-weight: 600;
  color: var(--text-muted);
}

/* La CONSÉQUENCE, en corps de lecture : c'est une phrase qu'on lit, pas un
   nom qu'on repère. Le nom y est déjà, à sa place dans la phrase — l'afficher
   une seconde fois en gros aurait fait dire deux fois la même chose. */
.ui-confirmation-texte {
  margin: var(--space-2) 0 var(--space-3);
  color: var(--text);
  line-height: var(--lh-base);
}

/* La question ne varie jamais : elle est écrite dans le gabarit, pas dans les
   données, et se pose en dessous de la conséquence. */
.ui-confirmation-question {
  margin: 0 0 var(--space-4);
  color: var(--color-titre);
  font-weight: 600;
}

/* Les deux issues à droite, l'annulation d'abord : on lit de gauche à droite,
   et la porte de sortie doit se rencontrer avant l'action. */
.ui-confirmation-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-2);
}

/* ══════════ BULLE DE MESSAGE ══════════
   Une molécule, deux côtés. Ce qui change d'un côté à l'autre tient en trois
   choses — l'alignement, la couleur, le coin laissé carré — et rien d'autre :
   la mesure, le rembourrage, le rayon et la place de l'heure sont communs.
   Deux composants séparés auraient divergé au premier réglage, et une
   conversation où les deux bulles ne se répondent pas exactement se lit mal. */
.ui-msg {
  display: flex;
  align-items: flex-end;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}

/* Le mien part de la droite ; le sien reste à gauche, où son portrait le
   précède. */
.ui-msg--moi { justify-content: flex-end; }

.ui-msg-avatar {
  width: 32px;
  height: 32px;
  border-radius: var(--radius-photo);
  object-fit: cover;
  flex-shrink: 0;
}

/* 78 % DE MESURE MAXIMALE. Une bulle qui prend toute la largeur cesse de se
   lire comme une réplique : c'est le blanc laissé de l'autre côté qui dit à
   qui elle est. 78 laisse de quoi voir le bord opposé même sur le message le
   plus long. */
.ui-msg-corps {
  display: flex;
  flex-direction: column;
  /* 84 % et non 78 : la rangée contient maintenant la bulle ET les deux
     actions, qui occupent leur place même invisibles — sans quoi la bulle
     aurait rétréci d'autant. */
  max-width: 84%;
  min-width: 0;
}

/* LA RANGÉE BULLE + ACTIONS. C'est elle qui centre les actions sur la bulle :
   « align-items: center » dans une rangée qui ne contient que ces deux-là.
   « row-reverse » de mon côté plutôt qu'un « order » sur les actions : la
   bulle reste le premier enfant dans le balisage — donc dans l'ordre de
   lecture d'un lecteur d'écran — et c'est l'affichage seul qui s'inverse. */
.ui-msg-ligne {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  min-width: 0;
}

.ui-msg--moi .ui-msg-ligne { flex-direction: row-reverse; }

/* LE BLOC QUI PORTE LA BULLE, et l'ancre des réactions superposées. Il se
   dimensionne sur la bulle et rien d'autre : c'est ce qui permet d'accrocher la
   pastille au bord de la BULLE et non au bord de la rangée, qui contient aussi
   les deux actions. */
.ui-msg-bloc {
  position: relative;
  min-width: 0;
}

.ui-msg-bulle { min-width: 0; }

/* LA BULLE SE CREUSE QUAND ELLE PORTE UNE RÉACTION. La pastille mord de 14px
   sur le bas de la bulle ; sans place réservée, elle recouvrait le début du
   dernier mot des messages dont la dernière ligne est pleine — visible sur mes
   propres bulles, où la pastille est à gauche et le texte aligné à gauche.
   22px de rembourrage bas au lieu de 9 : le texte s'arrête alors 8px AU-DESSUS
   du haut de la pastille, qui ne recouvre plus que du fond.
   « :has » plutôt qu'une classe posée par le gabarit : c'est la présence de la
   pastille qui commande le creux, et elle est déjà dans le balisage. Une classe
   de plus aurait pu être oubliée le jour où l'on ajoute une réaction ailleurs.
   CE QUE ÇA COÛTE, et il faut le savoir : la bulle grandit de 13px. L'écart à
   l'heure et à l'avatar ne bouge pas — c'était la demande —, mais un message
   avec réaction est un peu plus haut qu'un message sans. On ne peut pas avoir
   à la fois la superposition, le texte intact et la hauteur constante ; c'est
   la hauteur qui a cédé. */
.ui-msg-bloc:has(.ui-msg-reactions) .ui-msg-bulle { padding-bottom: var(--space-4); }

/* LE MESSAGE CITÉ, au-dessus de la bulle qui lui répond : un extrait sur deux
   lignes au plus, sous un filet pétrole. C'est un lien — un clic remonte au
   message d'origine, qui s'allume un instant. */
.ui-msg-cite {
  display: flex;
  flex-direction: column;
  max-width: 100%;
  margin-bottom: var(--space-1);
  border-inline-start: 3px solid var(--color-primary);
  border-radius: var(--radius-sm);
  background: var(--surface-douce);
  padding: var(--space-2) var(--space-2);
  color: var(--text-muted);
  font-size: var(--fs-caption);
  line-height: 1.4;
  text-decoration: none;
}

.ui-msg-cite:hover { background: var(--surface-appuyee); }
.ui-msg-cite-auteur { color: var(--color-titre); font-weight: var(--fw-semibold); }

.ui-msg-cite-texte {
  display: -webkit-box;
  overflow: hidden;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
}

.ui-msg--moi .ui-msg-cite { align-self: flex-end; }

.ui-msg:target .ui-msg-bulle { outline: 2px solid var(--color-brand-pink-ink); outline-offset: 2px; }

.ui-msg-bulle {
  margin: 0;
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3);
  font-size: var(--fs-sm);
  line-height: var(--lh-base);
  overflow-wrap: anywhere;
}

/* LE COIN CARRÉ POINTE VERS SON AUTEUR — en bas à droite pour les miennes, en
   bas à gauche pour les siennes, du côté de l'avatar. C'est ce qui remplace la
   petite flèche des messageries : moins bruyant, et ça ne demande pas un
   pseudo-élément qui se décalerait au moindre changement de rayon. */
.ui-msg--moi .ui-msg-bulle {
  background: var(--color-titre);
  color: var(--white);
  border-end-end-radius: 4px;
}

/* SES BULLES SONT EN SURFACE DOUCE, les miennes en pétrole plein. Elles ont été
   BLANCHES à filet, du temps où la conversation était posée à même le fond
   teinté de la page ; depuis qu'elle vit dans une grande carte blanche, une
   bulle blanche s'y serait effacée — il ne serait resté que son filet, et une
   conversation dessinée au trait se lit deux fois moins vite qu'une
   conversation en aplats.
   Le rose a été écarté pour les deux : il signe les gestes de marque — le
   match, le CTA — et une conversation entière en rose en aurait fait un décor
   au lieu d'un signal. Blanc sur pétrole : 8,59:1 ; encre sur surface douce :
   14,39. */
.ui-msg--lui .ui-msg-bulle {
  background: var(--surface-douce);
  color: var(--text);
  border-end-start-radius: 4px;
}

.ui-msg-meta {
  margin: var(--space-1) 0 0;
  color: var(--text-muted);
  font-size: var(--fs-caption);
}

.ui-msg--moi .ui-msg-meta { text-align: right; }

/* ── RÉACTIONS DÉJÀ POSÉES ────────────────────────────────────────────────
   Sous la bulle et non dessus. Posées à cheval sur son bord — comme le font
   la plupart des messageries — elles auraient demandé une marge négative et un
   z-index, et surtout elles auraient mangé la dernière ligne du texte sur les
   messages courts. Dessous, elles ne disputent rien à personne. */
/* SUPERPOSÉES AU BORD BAS DE LA BULLE, et HORS DU FLUX. Elles ont d'abord été
   posées dessous, dans le flux : un message avec réaction était alors plus haut
   qu'un message sans, l'écart à l'avatar et à l'heure changeait d'un message à
   l'autre, et la colonne perdait son rythme. En absolu, la structure d'un
   message est exactement la même qu'il en porte ou non.
   DU CÔTÉ INTÉRIEUR — à droite des siennes, qui sont à gauche ; à gauche des
   miennes, qui sont à droite. L'heure, elle, est du côté EXTÉRIEUR : les deux
   ne se rencontrent jamais, et c'est ce qui rend la superposition possible sans
   rien masquer.
   -0,6875rem : la pastille descend d'un peu moins que sa demi-hauteur, si bien
   qu'elle mord sur la bulle sans la traverser. */
.ui-msg-reactions {
  position: absolute;
  bottom: -0.6875rem;
  inset-inline-end: 0.625rem;
  z-index: 1;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  margin: 0;
}

.ui-msg--moi .ui-msg-reactions {
  inset-inline-end: auto;
  inset-inline-start: 0.625rem;
}

/* Les formulaires de la palette ne comptent pas dans sa grille. */
.ui-msg-reagir { display: contents; }

/* MA réaction : la pastille prend l'encre rose, comme ce que j'ai aimé. */
.ui-msg-reaction.is-moi {
  border-color: color-mix(in srgb, var(--color-brand-pink-ink) 40%, transparent);
  background: var(--color-brand-pink-soft);
  color: var(--color-brand-pink-ink);
}

.ui-msg-reaction {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--surface-card);
  padding: 0.125rem var(--space-2);
  color: var(--text-muted);
  font-size: var(--fs-caption);
  line-height: 1.6;
}

/* ── LES DEUX ACTIONS AU SURVOL ───────────────────────────────────────────
   À CÔTÉ de la bulle, du côté opposé à l'avatar, jamais dessus : une bulle est
   du texte qu'on relit, et un bouton posé dessus en masque toujours un mot.
   Le côté est donné par « row-reverse » sur la rangée, pas par un « order »
   sur les actions : le balisage garde la bulle en premier, l'affichage seul
   s'inverse. */
.ui-msg-actions {
  display: flex;
  align-items: center;
  gap: 0.125rem;
  flex-shrink: 0;

  opacity: 0;
  transition: opacity 0.15s ease;
}

/* AU SURVOL ET AU FOCUS. « :hover » seul rendrait les deux boutons
   inatteignables au clavier : ils resteraient dans l'ordre de tabulation sans
   jamais devenir visibles. « :focus-within » les fait paraître dès que l'un
   des deux reçoit le focus — et il couvre aussi le cas de la palette ouverte,
   qui garde le focus dans la ligne.
   L'opacité et non « display: none » : une boîte qui apparaît en prenant de la
   place décalerait la bulle au moment où l'on entre dessus, et le survol se
   perdrait de lui-même. */
.ui-msg:hover .ui-msg-actions,
.ui-msg:focus-within .ui-msg-actions { opacity: 1; }

/* LES ACTIONS AU SURVOL D'UNE BULLE, et elles seules. Les outils de la barre
   d'envoi ont partagé cette règle un moment ; c'était une erreur de rangement.
   Ces deux-là sont DISCRETS par nature — ils n'apparaissent qu'au survol et ne
   doivent pas peser sur le fil ; les outils de la barre sont PERMANENTS et
   portent donc « ui-membre-rond », le rond gris de 40px de l'en-tête. */
.ui-msg-action {
  display: inline-grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--text-muted);
  transition: background 0.15s ease, color 0.15s ease;
}

.ui-msg-action:hover,
.ui-msg-action:focus-visible {
  background: var(--teinte-neutre);
  color: var(--color-titre);
}

.ui-msg-action > .material-symbols-outlined { font-size: var(--icon-md); }

/* La palette est une rangée d'emojis, pas une liste : on la parcourt d'un
   coup d'œil et on clique, il n'y a rien à lire. */
/* LE « display » EST SUR « .show » ET NON SUR LA CLASSE NUE, et c'est un piège
   qu'on ne voit qu'à l'écran. Bootstrap ferme ses menus par
   « .dropdown-menu { display: none } » et les ouvre en ajoutant « .show ».
   Écrit « .ui-msg-palette { display: flex } », le mien pèse une classe comme le
   sien mais se charge APRÈS : il gagnait l'arbitrage et les palettes restaient
   ouvertes en permanence — les deux à la fois, celle de la bulle et celle de la
   barre d'envoi. Posé sur « .ui-msg-palette.show », il ne s'applique que
   lorsque Bootstrap a décidé d'ouvrir. */
.ui-msg-palette {
  gap: 0.125rem;
  min-width: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  padding: var(--space-1);
  box-shadow: var(--shadow-md);
}

.ui-msg-palette.show { display: flex; }

.ui-msg-emoji {
  border: 0;
  border-radius: 50%;
  background: transparent;
  padding: var(--space-1);
  font-size: var(--icon-md);
  line-height: 1;
  transition: background 0.15s ease, transform 0.15s ease;
}

.ui-msg-emoji:hover,
.ui-msg-emoji:focus-visible {
  background: var(--teinte-neutre);
  transform: scale(1.15);
}

/* ══════════ LIGNE DE CONVERSATION ══════════
   Elle emprunte au menu vertical du DS ce qu'elle peut — rayon de 8px, fond de
   survol translucide — sans en être une : une conversation n'est pas une
   rubrique, elle porte un portrait, un extrait et une heure. */
.ui-conv {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  border-radius: var(--radius-sm);
  padding: var(--space-2);
  color: var(--text);
  text-decoration: none;
  transition: background 0.15s ease;
}

.ui-conv:hover,
.ui-conv:focus-visible,
.ui-conv.is-current { background: var(--teinte-neutre); }

.ui-conv.is-current .ui-conv-nom { font-weight: var(--fw-semibold); }

.ui-conv-avatar {
  width: 40px;
  height: 40px;
  border-radius: var(--radius-photo);
  object-fit: cover;
  flex-shrink: 0;
}

/* « min-width: 0 » sur la colonne du milieu, sans quoi l'extrait refuse de se
   tronquer : un élément flex ne descend pas sous la largeur de son contenu
   tant qu'on ne l'y autorise pas. Déjà rencontré sur la tête des listes. */
.ui-conv-corps {
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
  flex: 1;
  min-width: 0;
}

.ui-conv-tete {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
}

.ui-conv-nom {
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  color: var(--color-titre);
}

.ui-conv-heure {
  flex-shrink: 0;
  color: var(--text-muted);
  font-size: var(--fs-caption);
}

/* L'EXTRAIT EST TRONQUÉ ICI et non à l'écriture : la longueur qui tient
   dépend de la largeur du rail, pas de la phrase. */
.ui-conv-extrait {
  color: var(--text-muted);
  font-size: var(--fs-caption);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* LA PASTILLE DE NOUVEAUTÉS — un aplat rose, un chiffre blanc de 11px. Elle
   ELLE VIT AU NIVEAU MOLÉCULE et pas plus haut : la ligne de conversation est
   une molécule et l'emploie. Une classe se définit à l'étage où elle est
   employée, jamais au-dessus — sinon une molécule dépendrait d'un organisme, et
   l'ordre des couches CSS s'inverserait. Elle a d'abord été écrite parmi les
   organismes, du temps où seuls des rails et des onglets la portaient.
   Elle s'appelait « ui-fil-filtre-pastille » et ne servait qu'aux onglets du profil ;
   ceux-ci ont perdu leurs comptes en même temps que les likes et les matchs ont
   quitté le profil, et elle est restée orpheline. Elle sert maintenant les DEUX
   RAILS — celui du fil et celui des listes —, qui sont le même rail rendu par
   deux organismes. D'où un nom qui ne nomme plus l'endroit mais la chose.
   C'est la même pastille que celle des ronds de l'en-tête, au pixel et à la
   couleur près : un compte de nouveautés se dit d'une seule façon dans tout le
   produit, où qu'il se pose. */
.ui-pastille-nouveaux {
  margin-inline-start: var(--space-2);
  vertical-align: 1px;
}

/* ══════════ LA VISIONNEUSE ══════════
   PLEINE PAGE, par « modal-fullscreen » de Bootstrap : une photographie
   regardée dans une boîte de mille pixels au milieu de l'écran est une
   photographie regardée deux fois trop petite, et la colonne de droite a
   besoin de toute la hauteur pour dérouler des commentaires sans devenir une
   meurtrière. La fenêtre avait d'abord été centrée, à 1100px de large ; c'est
   ce qu'on a écarté.
   DEUX COLONNES, DEUX FONDS. Le NOIR est le seul qui ne dispute rien à une
   photographie — un fond clair renvoie de la lumière sur l'image et en fausse
   les valeurs, raison pour laquelle toutes les visionneuses du monde sont
   sombres. La colonne de conversation, elle, reste CLAIRE : on n'a jamais lu
   du texte long sur du noir sans fatigue, et c'est du texte long qu'on vient
   y écrire. */
.ui-visionneuse-content {
  flex-direction: row;
  border: 0;
  border-radius: 0;
  background: var(--color-voile);
}

/* La colonne de l'image : la barre en haut, la scène dessous, et rien
   d'autre. « min-width: 0 » sans quoi une image large empêcherait la colonne
   de rétrécir et pousserait le panneau hors de l'écran. */
.ui-visionneuse-vue {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  padding: var(--space-3);
}

/* TROIS PARTS, ET UNE GRILLE PLUTÔT QU'UNE RANGÉE : « 1fr auto 1fr » met le
   compteur au milieu EXACT de la colonne de l'image, quelles que soient les
   largeurs de la marque et des commandes. Une rangée flex avec des marges
   automatiques l'aurait centré entre les deux, c'est-à-dire décalé de la
   moitié de leur différence — 45px mesurés, assez pour que le nombre ne tombe
   plus sur l'axe de la photographie. */
.ui-visionneuse-barre {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: var(--space-2);
  /* 20px SUR LA BARRE ELLE-MÊME, en haut et sur les deux côtés — donc 36 en
     tout depuis le bord de la fenêtre, le rembourrage de la colonne compris.
     La marque était collée au coin, et la fenêtre n'a plus de cadre depuis
     qu'elle est pleine page.
     ÉCARTÉ : les mêmes 20px portés par le logo SEUL. Ils écartaient bien la
     marque, mais grandissaient la rangée sans toucher aux deux autres parts :
     le compteur et les commandes se recentraient dans la hauteur nouvelle et
     tombaient 10px au-dessus de l'axe du logo. Sur la barre, les trois parts
     descendent ensemble et restent alignées.
     Le rembourrage est ÉGAL à gauche et à droite : la grille « 1fr auto 1fr »
     reste symétrique, et le compteur ne quitte pas l'axe de la photographie. */
  padding: var(--space-4) var(--space-4) 0;
  margin-bottom: var(--space-3);
  flex-shrink: 0;
}

/* LA MARQUE, en version claire : la fenêtre est pleine page, il ne reste rien
   d'autre à l'écran qui dise de quel site elle vient. Même largeur que dans
   l'en-tête membre — 132px — pour qu'on la retrouve à l'identique en fermant
   la fenêtre. */
.ui-visionneuse-marque {
  width: 132px;
  height: auto;
  display: block;
}

.ui-visionneuse-compte {
  margin: 0;
  color: var(--on-dark-subtle);
  font-size: var(--fs-sm);
  font-variant-numeric: tabular-nums;
  text-align: center;
}

/* La poubelle (l'auteur d'un billet) : un formulaire, sans marge à lui. */
.ui-visionneuse-supprimer { margin: 0; }

.ui-visionneuse-outils {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  justify-self: end;
}

/* LES COMMANDES SONT CLAIRES SUR LE NOIR. Le rond à trois points et la
   fermeture viennent du socle, où ils sont dessinés pour un fond clair : on ne
   change ici que leurs encres, pas leur dessin — un bouton qui change de forme
   selon le fond n'est plus le même bouton.
   PAS DE « filter: invert » SUR LA CROIX, et c'est une correction : il avait
   été écrit en croyant retourner l'image de fond noire de Bootstrap. Le socle
   ne garde pas cette image — il la remplace par le glyphe Material d'un
   « ::before », qui suit la couleur du texte. La croix était donc déjà
   blanche, et l'inversion la repeignait en NOIR sur du noir : elle avait
   disparu. Une encre suffit. */
.ui-visionneuse .ui-menu-actions-bouton,
.ui-visionneuse .ui-visionneuse-supprimer-bouton,
.ui-visionneuse .ui-close-button {
  /* « border » ET NON « border-color » : le bouton de fermeture du socle est
     tracé sans filet — la couleur seule ne dessinait donc rien, et la croix
     restait nue à côté d'un rond cerclé. Les deux commandes sont voisines et
     font le même genre de chose ; elles doivent avoir le même dessin. */
  border: 1px solid color-mix(in srgb, var(--white) 40%, transparent);
  background: transparent;
  color: var(--white);
}

/* Le glyphe de la croix passe de 18 à 20px, la mesure de celui du rond à trois
   points : à dessin égal, deux tailles d'encre se voient. */
.ui-visionneuse .ui-close-button::before { font-size: var(--icon-md); }

.ui-visionneuse .ui-menu-actions-bouton:hover,
.ui-visionneuse .ui-visionneuse-supprimer-bouton:hover,
.ui-visionneuse .ui-close-button:hover {
  background: color-mix(in srgb, var(--white) 12%, transparent);
  color: var(--white);
}

/* « min-height: 0 » est ce qui fait tenir l'image dans la hauteur : sans lui,
   un enfant de colonne flex ne descend jamais sous sa taille naturelle, la
   scène gardait la hauteur de la photographie et la fenêtre défilait — ce qui
   est le comble pour un objet fait pour regarder une image entière. */
.ui-visionneuse-scene {
  position: relative;
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* ELLE PENCHE COMME SA VIGNETTE. L'angle est posé par le script au moment où
   l'image est remplie, pris dans la même série de cinq que la planche : le
   tirage qu'on vient de sortir de l'album garde son inclinaison, il ne se
   redresse pas en chemin. Sans angle servi — la fenêtre du design system,
   qu'aucun clic n'a remplie — la valeur de repli est 0deg et rien ne bouge.
   La marge de 2 % laisse la place aux coins : à 1,8°, le coin d'une image de
   900px descend de 14px, et sans elle il serait allé chercher la barre. */
/* UN CADRE BLANC, comme un tirage (2 oct. 2026) : 8px, 4 au téléphone. */
.ui-visionneuse-image {
  max-width: 98%;
  max-height: 98%;
  border: var(--space-2) solid var(--white);
  background: var(--white);
  border-radius: var(--radius-sm);
  object-fit: contain;
  transform: rotate(var(--visionneuse-inclinaison, 0deg));
}

/* LES DEUX FLÈCHES CONTRE LES BORDS, jamais sur l'image : posées dessus elles
   masquent ce qu'on est venu regarder, et sur une photographie sombre elles
   disparaissent. Le voile noir les tient lisibles sur n'importe quel cliché. */
.ui-visionneuse-precedent,
.ui-visionneuse-suivant {
  position: absolute;
  /* z-index INDISPENSABLE depuis que l'image PENCHE. Une image transformée
     ouvre un contexte d'empilement et se peint comme un « z-index: 0 » ; à
     rang égal, c'est l'ordre du balisage qui tranche, et l'image vient après
     la flèche gauche. Résultat mesuré : sur une photographie large, la flèche
     gauche passait DERRIÈRE l'image et disparaissait, la droite — écrite
     après — restant visible. */
  z-index: 1;
  top: 50%;
  transform: translateY(-50%);
  display: inline-grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border: 0;
  border-radius: 50%;
  background: color-mix(in srgb, var(--color-voile) 70%, transparent);
  color: var(--white);
  transition: background 0.15s ease;
}

.ui-visionneuse-precedent { inset-inline-start: var(--space-3); }
.ui-visionneuse-suivant { inset-inline-end: var(--space-3); }

.ui-visionneuse-precedent:hover,
.ui-visionneuse-suivant:hover,
.ui-visionneuse-precedent:focus-visible,
.ui-visionneuse-suivant:focus-visible { background: var(--color-voile); }

.ui-visionneuse-precedent > .material-symbols-outlined,
.ui-visionneuse-suivant > .material-symbols-outlined { font-size: var(--icon-lg); }

/* ── LA COLONNE DE CONVERSATION ───────────────────────────────────────────
   380px : la largeur d'une colonne de commentaires, celle de la liste des
   conversations de la messagerie à quelques pixels près. Plus étroite, les
   réponses en retrait n'ont plus de place ; plus large, elle mange l'image
   sans rien donner de plus à lire. Elle ne rétrécit jamais — « flex-shrink: 0 »
   — c'est l'image qui cède. */
.ui-visionneuse-panneau {
  display: flex;
  flex-direction: column;
  width: 380px;
  min-height: 0;
  /* AUCUN FOND : c'est le NOIR de la fenêtre qui court derrière la colonne,
     d'un bord à l'autre. Trois cartes posées sur le même noir que la
     photographie se lisent comme des objets posés sur une table ; une colonne
     qui a son propre fond redevient un panneau collé au bord de l'écran, et
     c'est le dessin de tous les fils du monde.
     ÉCARTÉ, pour qu'on ne le refasse pas : le blanc d'abord, qui donnait la
     copie exacte du réseau qu'on ne veut pas imiter ; puis le dégradé et le
     disque de .ui-page-membre, qui signaient le site mais refermaient la
     colonne sur elle-même.
     TROIS CARTES, ET DONC UN ÉCART ENTRE ELLES : le rembourrage laisse le
     noir passer tout autour, sans quoi elles colleraient aux bords et la
     colonne redeviendrait un bloc. */
  padding: var(--space-3);
  gap: var(--space-3);
  flex-shrink: 0;
}

/* LES DEUX CARTES ont le dessin de celles de l'espace membre — fond de carte,
   filet, --radius-md — repris des encarts du rail et des billets du mur, aux
   mêmes jetons. Une troisième manière d'arrondir un container n'aurait rien
   dit de plus. Posées sur le noir, ce sont elles qui portent toute la clarté
   de la colonne.
   « overflow: hidden » sur la seconde : le fil y porte un dégradé et le champ
   un aplat blanc, qui tous deux iraient carrer les coins arrondis de la carte
   s'ils n'étaient pas rognés par elle. */
.ui-visionneuse-tete,
.ui-visionneuse-echanges {
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface-card);
}

/* LE FIL ET SON CHAMP NE FONT QU'UN BLOC. Ils ont été deux cartes séparées :
   l'écart de noir entre elles faisait du champ une commande venue d'ailleurs,
   alors qu'écrire est la suite de lire. C'est ce bloc-là qui prend la hauteur
   restante ; à l'intérieur, seul le fil défile. */
.ui-visionneuse-echanges {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
  overflow: hidden;
}

/* QUI PUBLIE, ET CE QU'ON EN A DIT : la même carte. Le nom, la date et les
   réactions parlent tous les trois de la photographie affichée ; les séparer
   aurait fait deux cartes qui disent la même chose à deux hauteurs. */
.ui-visionneuse-tete {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  flex-shrink: 0;
}

.ui-visionneuse-auteur {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.ui-visionneuse-auteur img {
  width: 44px;
  height: 44px;
  border-radius: var(--radius-photo);
  object-fit: cover;
  flex-shrink: 0;
}

.ui-visionneuse-nom {
  margin: 0;
  color: var(--color-titre);
  font-weight: 600;
  line-height: 1.3;
}

/* Le texte du billet, sous l'auteur (2 oct. 2026) ; un long texte défile
   dans la carte plutôt que de pousser les échanges hors de l'écran. */
.ui-visionneuse-texte {
  max-height: 40vh;
  overflow-y: auto;
  color: var(--text);
  font-size: var(--fs-sm);
  line-height: var(--lh-base);
}

.ui-visionneuse-texte > :last-child { margin-bottom: 0; }

.ui-visionneuse-texte--brut {
  margin: 0;
  white-space: pre-line;
}

.ui-visionneuse-date {
  margin: 0;
  color: var(--text-muted);
  font-size: var(--fs-caption);
}

/* LES RÉACTIONS DÉJÀ POSÉES, en pastilles empruntées à la messagerie : un
   emoji, un nombre. C'est le même objet qu'au bas d'une bulle, et il n'y a
   aucune raison de le redessiner. Seule la POSE change — là-bas elle chevauche
   la bulle en absolu, ici c'est une rangée dans le fil de la colonne.
   LE BOUTON QUI EN AJOUTE UNE FERME LA RANGÉE, et c'est celui de la bulle —
   même rond de 28px, même « add_reaction », même palette. « align-items:
   center » le tient sur l'axe des pastilles, qui sont plus courtes que lui. */
.ui-visionneuse-reactions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
}

/* SEUL LE FIL DÉFILE : la carte de tête et le champ restent. Écrire un
   commentaire ne doit pas demander de redescendre au bas de deux cents
   réponses — c'est la règle du pied collant de la fenêtre d'une publication,
   obtenue ici par une colonne flex plutôt que par « modal-dialog-scrollable »,
   la fenêtre ayant deux colonnes et non une.
   LE DÉGRADÉ DE L'ESPACE MEMBRE COURT DERRIÈRE LES COMMENTAIRES, et le champ
   reste blanc : dans un même bloc, c'est l'aplat qui dit où l'on écrit.
   Le dégradé se règle sur la HAUTEUR VISIBLE du fil, l'attachement restant à
   « scroll » — c'est-à-dire fixe par rapport au cadre, pas au contenu.
   ÉCARTÉ : « local », qui l'accroche au contenu ; il s'étalait alors sur les
   2146px de commentaires, et les 340px qu'on a sous les yeux n'en montraient
   qu'un aplat presque uni. Un dégradé qu'il faut faire défiler pour le voir
   n'est pas un dégradé. */
.ui-visionneuse-fil {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  background-image:
    radial-gradient(ellipse 260px 220px at 106% 12%, var(--decor-disque) 0 99%, transparent 100%),
    linear-gradient(to bottom, color-mix(in srgb, var(--surface-douce) 50%, var(--white)), var(--surface-appuyee));
  background-repeat: no-repeat;
  padding: var(--space-4);
}

/* LES COMMENTAIRES SONT BLANCS, LES RÉPONSES NON. Sur le dégradé, la bulle
   en --surface-douce n'avait plus assez d'écart avec le fond : elle est claire
   sur clair, et c'est justement ce qu'on venait de mettre derrière elle. Le
   blanc la repose dessus.
   LES RÉPONSES GARDENT LA TEINTE DOUCE, et c'est ce qui les distingue : le
   fil n'a plus qu'un seul retrait depuis qu'elles s'alignent sous l'heure du
   commentaire, la couleur porte donc à elle seule la différence entre ce qu'on
   dit et ce qu'on répond.
   La chaîne d'enfants directs suffit à les séparer — une réponse est toujours
   dans un « .ui-discussion-reponses », jamais fille du fil. Et la règle est
   bornée à la visionneuse : dans la fenêtre d'une publication, le fond est
   déjà blanc et des bulles blanches y disparaîtraient. */
.ui-visionneuse .ui-discussion-fil > .ui-discussion-message > div > .ui-discussion-bulle {
  background: var(--surface-card);
}

/* SOUS 900px, LES DEUX COLONNES SE METTENT L'UNE SOUS L'AUTRE. L'image garde
   45% de la hauteur : assez pour être regardée, assez peu pour qu'on voie
   qu'il y a des commentaires dessous — une image qui prendrait tout l'écran
   laisserait croire que la fenêtre s'arrête là. */
/* L'IMAGE PREND LA PLACE LIBRE (2 oct. 2026) : 45 % au moins de la hauteur,
   davantage quand le panneau du dessous a peu à dire — la visionneuse d'un
   billet n'y montre que l'auteur et le texte, et une photo verticale restait
   petite au-dessus d'un grand vide. Le panneau prend ce qu'il lui faut,
   55 % au plus, et défile au-delà. */
@media (width <= 900px) {
  .ui-visionneuse-content { flex-direction: column; }
  .ui-visionneuse-vue { flex: 1 1 auto; min-height: 45%; }
  .ui-visionneuse-image { border-width: var(--space-1); }
  .ui-visionneuse-panneau {
    width: auto;
    flex: 0 1 auto;
    max-height: 55%;
    overflow-y: auto;
    /* LA CARTE SOUS LA PHOTO (4 oct. 2026) : posée sur le noir, avec sa
       marge et ses coins — essayée en pleine largeur, puis rendue à sa forme
       de carte. Plus de filet clair au-dessus du panneau : il dessinait une
       ligne blanche entre la photo et la carte. */
    border-top: 0;
  }
  .ui-visionneuse-tete,
  .ui-visionneuse-fil { padding-inline: var(--space-3); }
  /* La marque s'efface sous 900px : à trois parts dans 375px de large, elle
     écrasait le compteur contre les commandes. Ce qu'on garde est ce qui
     sert — où l'on en est, et de quoi sortir. */
  .ui-visionneuse-marque { display: none; }
  .ui-visionneuse-barre {
    grid-template-columns: 1fr auto;
    padding: var(--space-2) var(--space-2) 0;
  }
  .ui-visionneuse-compte { text-align: start; }
}

/* Le curseur dit que la vignette s'ouvre. Les six emplacements du profil sont
   des « div » et n'en auraient pas eu ; la planche est faite de liens et en a
   un déjà, mais le déclarer pour les deux évite d'avoir à savoir lequel est
   lequel. */
.ui-monprofil-galerie-case,
.ui-monprofil-zones .ui-monprofil-photo-case,
.ui-monprofil-photos .ui-monprofil-photo-case,
.ui-monprofil-vignette,
.ui-monprofil-avatar { cursor: zoom-in; }

/* ---- Groupe de pastilles à choisir ----
   Le dessin est celui des pastilles de « Mes critères » (.ui-criteres-case,
   plus haut) : la case retirée du dessin mais gardée dans le document, son
   étiquette prenant l'apparence d'une pastille. Les deux ne sont pas encore le
   même composant — la note est dans molecules/choix-pastilles.snip.
   Le fieldset est remis à plat : Bootstrap ne le touche pas, mais les agents
   lui posent une bordure et un rembourrage par défaut. */
.ui-pastilles {
  border: 0;
  margin: 0;
  padding: 0;
  min-width: 0;
}

/* Bootstrap passe les legend en 100 % de large et en 1.5rem : on annule les
   deux.

   LA LÉGENDE EST UN TITRE, et non l'intitulé d'un champ. Elle a d'abord porté
   le corps et le gris d'un .ui-form-label, par alignement sur la grille de
   champs ; mais un groupe de pastilles n'est pas un champ dans une colonne —
   c'est une QUESTION, seule sur sa ligne, avec cinq ou six réponses en
   dessous. Annoncée en 12px gris, elle passait sous la suite de pastilles
   qu'elle commande, et trois questions empilées se lisaient comme une seule.
   Elle reprend donc les valeurs de .ui-inscription-bloc-titre, à quoi elle
   sert de pendant : le titre d'un bloc qui pose une question est la question.

   C'est un <legend> et non un <h2>, et ce n'est pas une approximation : c'est
   l'élément qui NOMME un groupe de contrôles. Un titre posé à côté du fieldset
   aurait laissé le groupe sans nom à la voix, et il aurait fallu l'y répéter
   en double. */
.ui-pastilles-legende {
  float: none;
  width: auto;
  margin-bottom: var(--space-2);
  padding: 0;
  color: var(--color-titre);
  font-size: var(--fs-base);
  font-weight: var(--fw-bold);
}

/* 8px entre deux lignes, 10px entre deux pastilles. L'écart était de 6px dans
   les deux sens, repris des cases de « Mes critères » — mais celles-ci vivent
   dans une fenêtre où vingt-quatre pastilles doivent tenir, et le serrage y est
   le prix de la place. Ici elles sont cinq ou six sur une carte de 640px : rien
   n'oblige à les tasser, et l'œil séparait mal deux réponses voisines.
   Plus large en colonne qu'en ligne : c'est entre deux pastilles côte à côte
   que la confusion se produisait, pas entre deux rangées — celles-ci sont déjà
   séparées par la hauteur de la pastille. */
.ui-pastilles-liste {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-2);
}

/* La case garde le focus, le clavier et la sémantique ; elle ne garde pas de
   place. C'est son étiquette qui se voit, donc le clic sur la pastille coche
   la case sans une ligne de script. */
.ui-pastille input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

/* Enfant DIRECT : « .ui-pastille span » attraperait aussi le pictogramme, qui
   deviendrait un conteneur flex — et le nom de glyphe cesserait de se replier
   en ligature. Le piège est documenté sur .ui-criteres-case, il se reproduit
   ici à l'identique. */
.ui-pastille > span {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--surface-card);
  padding: var(--space-2) var(--space-3);
  color: var(--text);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  cursor: pointer;
  transition: background 0.2s ease, border-color 0.2s ease, color 0.2s ease;
}

.ui-pastille > span .material-symbols-outlined {
  font-size: var(--icon-sm);
  color: var(--text-muted);
  flex-shrink: 0;
}

.ui-pastille input:checked + span .material-symbols-outlined { color: inherit; }

.ui-pastille > span:hover {
  border-color: var(--color-titre);
  color: var(--color-titre);
}

/* Cochée : l'aplat pétrole plein, comme toute sélection du site. */
.ui-pastille input:checked + span {
  background: var(--color-titre);
  border-color: var(--color-titre);
  color: var(--white);
}

/* Le focus se voit sur la pastille, puisque la case ne se voit pas. */
.ui-pastille input:focus-visible + span {
  outline: 3px solid var(--focus-ring);
  outline-offset: 2px;
}

/* L'aide se lit APRÈS les pastilles et non avant : elle dit ce qu'on peut
   faire du groupe — « plusieurs réponses possibles » —, ce qui ne se comprend
   qu'une fois le groupe vu. Même corps et même gris que l'aide d'un champ de
   formulaire : c'est la même fonction, elle n'a pas à s'écrire autrement. */
.ui-pastilles-aide {
  margin: var(--space-2) 0 0;
  color: var(--text-muted);
  font-size: var(--fs-caption);
}

/* ---- Champ de photo de profil ----
   Médaillon à gauche, intitulé et bouton à droite. La rangée s'empile sous
   480px : à 375px, le médaillon de 96px ne laissait au bouton que 200px, où
   « Choisir une photo » passait à la ligne dans le bouton. */
.ui-champ-photo {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

/* Le rond annonce le recadrage AVANT le choix de l'image. Vide, il porte un
   pictogramme sur fond doux ; rempli, la photo le remplit entièrement. */
.ui-champ-photo-apercu {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 96px;
  height: 96px;
  border: 1px solid var(--border);
  border-radius: 50%;
  background: var(--surface-douce);
  overflow: hidden;
}

.ui-champ-photo-apercu img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.ui-champ-photo-apercu .material-symbols-outlined {
  font-size: var(--icon-2xl);
  color: var(--text-muted);
}

.ui-champ-photo-corps {
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-2);
}

/* L'étiquette-bouton : c'est elle qui ouvre le sélecteur de fichiers, le champ
   natif étant retiré du dessin juste en dessous. Elle reprend la mesure d'un
   .ui-btn-slim sans en être un — un bouton de 38px à côté d'un médaillon de 96
   paraissait flotter. */
.ui-champ-photo-bouton {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  border: 1px solid var(--color-titre);
  border-radius: var(--radius-pill);
  background: var(--surface-card);
  padding: var(--space-2) var(--space-3);
  color: var(--color-titre);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  cursor: pointer;
  transition: background 0.2s ease, color 0.2s ease;
}

.ui-champ-photo-bouton .material-symbols-outlined { font-size: var(--icon-md); }

.ui-champ-photo-bouton:hover {
  background: var(--color-titre);
  color: var(--white);
}

/* Le champ natif : hors du dessin, dans le document. « display: none » l'aurait
   aussi retiré de l'ordre de tabulation, et la photo serait devenue
   inaccessible au clavier. */
.ui-champ-photo-fichier {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

/* Le focus se voit sur l'étiquette, puisque le champ ne se voit pas. */
.ui-champ-photo-fichier:focus-visible + .ui-champ-photo-bouton {
  outline: 3px solid var(--focus-ring);
  outline-offset: 2px;
}

.ui-champ-photo-aide {
  margin: 0;
  color: var(--text-muted);
  font-size: var(--fs-caption);
}

/* En colonne sur les TRÈS petits écrans seulement (2 oct. 2026) : à 480 px,
   le bouton passait sous l'aperçu alors qu'il y avait la place à droite. */
@media (width <= 340px) {
  .ui-champ-photo {
    flex-direction: column;
    align-items: flex-start;
  }
}

/* ---- Fenêtre d'édition (molecules/dialogue-edition) ----
   Le dessin de « Mes critères », trait pour trait : même largeur, même ombre,
   mêmes marges d'en-tête, de corps et de pied. C'est la même famille de
   fenêtre — régler quelque chose de SOI —, elle doit se reconnaître d'un coup.
   Seuls s'y ajoutent les blocs, empilés, et la sortie « supprimer ». ---- */
.ui-edition-dialog { max-width: 720px; max-height: 88vh; }

.ui-edition-content {
  border: 0;
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  overflow: hidden;
}

.ui-edition-entete,
.ui-edition-pied {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-5);
  flex-shrink: 0;
}

.ui-edition-entete {
  justify-content: space-between;
  border-bottom: 1px solid var(--border);
}

.ui-edition-titre {
  margin: 0;
  color: var(--text);
  font-size: var(--fs-h5);
  font-weight: 700;
}

/* DE L'AIR (27 sept. 2026) : les fenêtres d'édition se lisaient serrées —
   blocs collés, titres en petites capitales, pastilles contre les champs.
   Même respiration que les fenêtres des critères de l'espace Paramètres. */
.ui-edition-corps { padding: var(--space-4) var(--space-5) var(--space-5); }

.ui-edition-intro {
  margin: 0 0 var(--space-3);
  color: var(--text-muted);
  font-size: var(--fs-sm);
  line-height: var(--lh-base);
}

/* Les blocs se séparent d'un filet, comme les groupes des critères : une
   fenêtre qui porte la ville, l'apparence et les langues se lit en parties. */
.ui-edition-bloc + .ui-edition-bloc {
  margin-top: var(--space-5);
  border-top: 1px solid var(--border);
  padding-top: var(--space-5);
}

/* Dans un bloc, un groupe de pastilles et une grille de champs ne se
   touchent plus : chacun est une question, il a sa marge. Deux groupes de
   pastilles se séparent d'un filet, comme les critères dans leurs fenêtres. */
.ui-edition-bloc .ui-pastilles + .ui-field-grid,
.ui-edition-bloc .ui-field-grid + .ui-pastilles { margin-top: var(--space-4); }

.ui-edition-bloc .ui-pastilles + .ui-pastilles {
  margin-top: var(--space-4);
  border-top: 1px solid var(--border);
  padding-top: var(--space-4);
}

.ui-edition-bloc .ui-pastilles-legende { margin-bottom: var(--space-3); font-size: var(--fs-base); font-weight: var(--fw-semibold); }

.ui-edition-bloc .ui-form-label {
  color: var(--color-titre);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
}

/* Le titre de bloc est un VRAI TITRE, au-dessus des questions qu'il
   rassemble — il était en petites capitales grises, et se perdait. */
.ui-edition-bloc-titre {
  margin: 0 0 var(--space-4);
  color: var(--color-titre);
  font-size: var(--fs-h5);
  font-weight: var(--fw-bold);
}

/* Le titre et, à droite, l'interrupteur « Important » : la même ligne que
   les fenêtres de critères. */
.ui-edition-bloc-tete {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin: 0 0 var(--space-4);
}
.ui-edition-bloc-tete .ui-edition-bloc-titre { margin: 0; }
.ui-edition-bloc-tete .ui-criteres-important {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
  padding-left: 2.5em;
  color: var(--text-muted);
  font-size: var(--fs-caption);
  font-weight: 600;
  cursor: pointer;
}

.ui-edition-pied {
  justify-content: flex-end;
  border-top: 1px solid var(--border);
  background: var(--surface-card);
}

/* « Supprimer » à GAUCHE, loin du bouton qui enregistre : les deux gestes ne
   doivent pas se confondre sous le pouce. En filet rouge, pas en aplat — il
   n'est pas l'action attendue de la fenêtre. */
.ui-edition-supprimer {
  margin-inline-end: auto;
  border-color: var(--color-danger);
  color: var(--color-danger);
}

.ui-edition-supprimer:hover,
.ui-edition-supprimer:focus-visible {
  border-color: var(--color-danger);
  background: var(--color-danger);
  color: var(--white);
}

/* Plein écran jusqu'à 767px : 16px de marge, comme Mes critères. */
@media (width <= 767px) {
  .ui-edition-entete,
  .ui-edition-corps,
  .ui-edition-pied { padding-inline: var(--space-3); }
}

@media (width <= 560px) {
  .ui-edition-entete,
  .ui-edition-corps,
  .ui-edition-pied { padding-inline: var(--space-3); }
  .ui-edition-pied { flex-wrap: wrap; }
  .ui-edition-pied .ui-btn { flex: 1 1 0; }
  .ui-edition-supprimer { flex-basis: 100%; margin-inline-end: 0; }
}


/* ---- Les actions réelles du mur ----
   J'aime, supprimer, retirer, signaler : des formulaires POST, parce qu'ils
   changent des données. Le formulaire s'efface de la mise en page — le
   bouton reste à sa place dans la rangée, comme dans la maquette. ---- */
.ui-publication-action-form,
.ui-publication-menu-form,
.ui-discussion-lien-form { display: contents; }

/* Le j'aime donné, comme celui du billet (.ui-publication-action.is-active). */
.ui-discussion-lien.is-active { color: var(--color-brand-pink-ink); }


/* Les trois gestes d'une carte de profil — masquer, ★, ♥ — sont des
   formulaires POST dans l'application. Le formulaire s'efface : le bouton
   garde sa place dans la rangée ou dans le menu. */
.ui-profil-form { display: contents; }

.ui-menu-actions-form { display: contents; }


/* ══ LES ACTIONS D'UN COMMENTAIRE (29 sept. 2026) ═══════════════════════════
   Celles d'un message : Répondre, Réagir, puis Modifier / Supprimer (l'auteur)
   ou Signaler / Retirer — en icônes discrètes à côté de la bulle, au survol
   et au focus, toujours visibles sur un écran tactile. Les réactions se posent
   sous la bulle, à cheval sur son bord, comme sur un message. */
.ui-discussion-contenu { min-width: 0; }
.ui-discussion-ligne { display: flex; align-items: center; gap: var(--space-1); }
.ui-discussion-bloc { position: relative; min-width: 0; }
.ui-discussion-bloc:has(.ui-msg-reactions) .ui-discussion-bulle { padding-bottom: var(--space-4); }
.ui-discussion-bloc:has(.ui-msg-reactions) { margin-bottom: var(--space-3); }
.ui-discussion-ligne:hover > .ui-msg-actions,
.ui-discussion-ligne:focus-within > .ui-msg-actions { opacity: 1; }
@media (hover: none) {
  .ui-discussion-ligne > .ui-msg-actions { opacity: 1; }
}
.ui-msg-action-form { display: contents; }

.ui-discussion-modifier { display: flex; flex-direction: column; gap: var(--space-2); margin-top: var(--space-2); }
.ui-discussion-modifier[hidden] { display: none; }
.ui-discussion-modifier-pied { display: flex; justify-content: flex-end; gap: var(--space-2); }

/* La vignette et le nom de l'auteur mènent à son profil (29 sept. 2026) :
   le lien ne change pas leur dessin, il se souligne au survol. */
.ui-publication-profil { color: inherit; text-decoration: none; }
.ui-publication-profil:hover { text-decoration: underline; }
.ui-publication-entete > .ui-publication-profil { display: contents; }
.ui-discussion-billet-entete a { color: inherit; text-decoration: none; }
.ui-discussion-billet-entete a.ui-discussion-nom:hover { text-decoration: underline; }

/* MA DEMANDE EN ATTENTE, en badge à la place des boutons contact et like
   (29 sept. 2026) : sur la photo d'une carte, calé à droite ; dans le bandeau
   d'un profil, à la suite de « Masquer ». Rose pour le match, pétrole pour
   le contact — les couleurs des deux intentions. */
.ui-profil-envoye { /* le dessin vient de .ui-badge */ }
/* CE QUE J'AI ENVOYÉ, EN BAS À GAUCHE DE LA PHOTO (1er oct. 2026) — « Like
   envoyé », « Demande envoyée » ; les statuts venus de l'autre tiennent le
   coin haut gauche. Sur une grande carte, la rangée d'actions l'y pose
   déjà ; sur une carte réduite, qui n'a pas de rangée, il est placé ici. */
.ui-profil-envoye--coin {
  position: absolute;
  bottom: 0.9375rem;
  left: 0.9375rem;
  z-index: 1;
}
/* Seul dans la rangée (30 sept. 2026) : ni croix ni boutons, le badge se
   cale en bas à GAUCHE. */
.ui-profil-actions > .ui-profil-envoye { margin-inline-end: auto; align-self: center; }
.ui-monprofil-actions > .ui-profil-envoye { align-self: center; background: var(--surface-douce); }

/* LES BADGES DU NOM (29 sept. 2026) : « photos vérifiées » (selfie pris à
   la caméra et validé) et « rencontré·e en sortie » (la poignée de main).
   Pleins, pétrole, collés au nom — sur la carte comme sur la fiche. */
.ui-badge-verifie,
.ui-badge-rencontre {
  margin-inline-start: var(--space-1);
  color: var(--color-primary);
  font-size: 0.9em;
  font-variation-settings: 'FILL' 1;
  vertical-align: -0.1em;
  cursor: help;
}
/* Après une sortie, sur ses participants : « Je l'ai rencontré·e ». */
.ui-profil-rencontre { align-self: flex-start; }
.ui-profil-rencontre--faite { display: flex; align-items: center; gap: var(--space-2); color: var(--color-primary); font-weight: 600; }
.ui-profil-rencontre .material-symbols-outlined { font-size: var(--icon-sm); }

/* Les images de la modération : le selfie à côté des photos de profil. */
.ui-moderation-photos { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.ui-moderation-photos > img { width: 160px; height: 200px; object-fit: cover; border-radius: var(--radius-sm); }
.ui-moderation-photos > img:first-child { outline: 3px solid var(--color-primary); }

/* LE MENU « … » : un trait entre deux groupes d'entrées, et des entrées qui
   ne paraissent qu'en mobile — les boutons d'un bandeau qui s'y replient
   (29 sept. 2026). */
.ui-menu-actions-separateur { margin: var(--space-1) 0; border: 0; border-top: 1px solid var(--border); opacity: 1; }
@media (width >= 768px) {
  .ui-menu-actions--mobile { display: none; }
}

/* LE TAUX À DÉCOUVRIR (29 sept. 2026) : « ? » quand MON profil est
   incomplet — un lien vers « Mon profil », en pointillé pour dire qu'il
   manque quelque chose. */
.ui-profil-score--a-completer { outline: 1px dashed currentColor; outline-offset: -3px; text-decoration: none; }
.ui-profil-score--a-completer:hover { text-decoration: none; filter: brightness(0.97); }

/* Un interrupteur allumé et figé (en contact, match) ne se clique pas. */
.ui-profil-favori[aria-disabled="true"],
.ui-profil-like[aria-disabled="true"] { cursor: default; }

/* L'ÉCRAN « C'EST UN MATCH ! » (30 sept. 2026) — .ui-ecran-match ----------
   Posé par le script PAR-DESSUS la page courante, au-dessus des fenêtres
   Bootstrap : un voile aux deux couleurs de la marque, les deux portraits qui
   se rejoignent, le cœur entre eux, puis deux gestes — écrire, ou continuer.
   L'animation est courte et s'efface si le système demande moins de
   mouvement. */
.ui-ecran-match {
  position: fixed;
  inset: 0;
  z-index: 1090;
  display: grid;
  place-items: center;
  padding: var(--space-3);
  background: linear-gradient(160deg,
    color-mix(in srgb, var(--color-bouton) 94%, transparent) 0%,
    color-mix(in srgb, var(--color-brand-pink-ink) 92%, transparent) 100%);
  backdrop-filter: blur(6px);
  opacity: 0;
  transition: opacity 0.25s ease;
}
.ui-ecran-match.is-visible { opacity: 1; }

.ui-ecran-match-boite {
  width: min(100%, 420px);
  text-align: center;
  color: var(--white);
}

.ui-ecran-match-photos {
  position: relative;
  display: flex;
  justify-content: center;
  align-items: center;
  margin-bottom: var(--space-4);
}
.ui-ecran-match-photo {
  width: 132px;
  height: 132px;
  border: 5px solid var(--white);
  border-radius: 50%;
  object-fit: cover;
  background: var(--white);
  box-shadow: var(--shadow-md);
  transition: transform 0.5s cubic-bezier(0.2, 0.8, 0.2, 1.2);
}
.ui-ecran-match-photo--moi { transform: translateX(-60px) rotate(-8deg); margin-right: calc(var(--space-3) * -1); }
.ui-ecran-match-photo--autre { transform: translateX(60px) rotate(8deg); margin-left: calc(var(--space-3) * -1); }
.ui-ecran-match.is-visible .ui-ecran-match-photo--moi { transform: translateX(0) rotate(-6deg); }
.ui-ecran-match.is-visible .ui-ecran-match-photo--autre { transform: translateX(0) rotate(6deg); }

.ui-ecran-match-coeur {
  position: absolute;
  left: 50%;
  bottom: -14px;
  z-index: 1;
  display: grid;
  place-items: center;
  width: 52px;
  height: 52px;
  border-radius: 50%;
  background: var(--white);
  color: var(--color-brand-pink-ink);
  font-size: var(--icon-xl);
  font-variation-settings: 'FILL' 1, 'wght' 400, 'GRAD' 0, 'opsz' 24;
  transform: translateX(-50%) scale(0);
  transition: transform 0.35s cubic-bezier(0.2, 0.8, 0.2, 1.4) 0.35s;
}
.ui-ecran-match.is-visible .ui-ecran-match-coeur { transform: translateX(-50%) scale(1); }

.ui-ecran-match-surtitre {
  margin: 0 0 var(--space-1);
  font-weight: 600;
  opacity: 0.9;
}
.ui-ecran-match-surtitre:empty { display: none; }
.ui-ecran-match-titre {
  margin: 0 0 var(--space-2, 0.5rem);
  color: var(--white);
  font-family: var(--font-display);
  font-size: var(--fs-display-5);
  line-height: 1.05;
}
.ui-ecran-match-texte { margin: 0 0 var(--space-4); opacity: 0.95; }

.ui-ecran-match-actions {
  display: flex;
  flex-direction: column;
  gap: var(--space-2, 0.5rem);
  align-items: center;
}
.ui-ecran-match-actions .ui-btn { min-width: 240px; justify-content: center; }
/* Encre NOIRE (l'encre du texte) : le rose sur blanc se lisait comme un
   message d'erreur, le pétrole n'allait pas au voile (30 sept. 2026). */
.ui-ecran-match-ecrire.ui-btn-primary {
  border-color: var(--white);
  background: var(--white);
  color: var(--color-dark);
}
.ui-ecran-match-ecrire.ui-btn-primary:hover,
.ui-ecran-match-ecrire.ui-btn-primary:focus-visible { border-color: var(--color-dark); background: var(--color-dark); color: var(--white); }
/* « Voir le profil » : un contour blanc, le second geste. */
.ui-ecran-match .ui-ecran-match-actions .ui-ecran-match-profil { border-color: var(--white); background: transparent; color: var(--white); box-shadow: none; }
.ui-ecran-match .ui-ecran-match-actions .ui-ecran-match-profil:hover,
.ui-ecran-match .ui-ecran-match-actions .ui-ecran-match-profil:focus-visible { background: var(--white); color: var(--color-dark); }
/* « Continuer » en simple lien clair : le seul geste qui compte est écrire.
   Deux classes d'avance sur le dessin des boutons fantômes de la charte. */
.ui-ecran-match .ui-ecran-match-actions .ui-ecran-match-continuer { color: var(--white); border-color: transparent; background: transparent; box-shadow: none; }
.ui-ecran-match .ui-ecran-match-actions .ui-ecran-match-continuer:hover,
.ui-ecran-match .ui-ecran-match-actions .ui-ecran-match-continuer:focus-visible { color: var(--white); background: color-mix(in srgb, var(--white) 12%, transparent); }

@media (prefers-reduced-motion: reduce) {
  .ui-ecran-match,
  .ui-ecran-match-photo,
  .ui-ecran-match-coeur { transition: none; }
}

/* LES GESTES D'UNE CARTE PROFIL AU SURVOL (30 sept. 2026) : la photo d'abord,
   les boutons quand on s'y arrête. « opacity » et non « display » : un bouton
   caché reste atteignable au clavier, et la carte les montre dès que le focus
   y entre. Un menu ouvert les garde. Sur un écran tactile, qui n'a pas de
   survol, ils restent affichés. */
/* Les BADGES DE STATUT de la rangée (« Like envoyé », « Demande de contact
   envoyée ») restent, eux, toujours visibles : seuls les boutons se cachent. */
/* Les boutons sont visés EUX-MÊMES : leurs formulaires sont en
   « display: contents », et l'opacité n'a pas d'effet sur une boîte qui
   n'existe pas. */
/* ET JAMAIS AU SMARTPHONE, même si l'appareil se dit capable de survol
   (30 sept. 2026) : sous 576px, les trois boutons sont toujours là. */
@media (hover: hover) and (width > 576px) {
  .ui-profil-actions :is(.ui-profil-passe, .ui-profil-favori, .ui-profil-like, .ui-profil-message),
  .ui-profil .ui-profil-menu {
    opacity: 0;
    transition: opacity 0.2s ease;
  }
  .ui-profil:is(:hover, :focus-within) .ui-profil-actions :is(.ui-profil-passe, .ui-profil-favori, .ui-profil-like, .ui-profil-message),
  .ui-profil:is(:hover, :focus-within) .ui-profil-menu,
  .ui-profil:has(.ui-menu-actions-menu.show) .ui-profil-menu {
    opacity: 1;
  }
}

/* À QUATRE PAR RANGÉE (228px), « Demande envoyée » écrasait la croix de
   « Masquer » à 35px de large (30 sept. 2026). La croix garde ses 47px ; la
   pastille se resserre, et coupe son texte en dernier recours. */
.ui-profil-actions .ui-profil-passe { flex-shrink: 0; }
.ui-profil-actions > .ui-profil-envoye {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* LA PRÉSENCE, sous le lieu (30 sept. 2026) : même ligne, même gris que le
   lieu ; pour « En ligne », le pictogramme plein en vert. Collée au lieu,
   sans marge dessus ni dessous : le lieu perd la sienne quand elle le suit. */
:is(.ui-profil-corps, .ui-profil-entete) > p:has(+ .ui-profil-presence) { margin-bottom: 0; }
:is(.ui-profil-corps, .ui-profil-entete) > .ui-profil-presence { margin-top: 0; margin-bottom: 0; }
/* « En ligne » : le texte reste gris comme le lieu, seul le pictogramme est vert. */
.ui-profil-presence--en-ligne .material-symbols-outlined { color: var(--color-success); font-variation-settings: 'FILL' 1, 'wght' 400, 'GRAD' 0, 'opsz' 20; }

/* molecules/agenda.snip — déplacé d'ui-organismes.css le 30 sept. 2026, quand
   la carte a servi aussi la colonne de gauche d'une sortie. */
/* ---- AGENDA DU RAIL ----
   Remplacé par la molécule encart (3 oct. 2026) : « Mes prochains
   évènements » est une carte de colonne, la photo de chaque sortie dans le
   carré. */

/* Palier téléphone du titre de l'écran « C'est un match ! » (échelle du DS). */
@media (width <= 560px) {
  .ui-ecran-match-titre { font-size: var(--fs-h1); }
}

/* LES PHOTOS ET LES RENSEIGNEMENTS D'UNE CARTE EN MODE SWIPE (30 sept.
   2026). Ils n'existent que si le serveur les fournit (swipe choisi), et ne
   se montrent qu'au smartphone — la liste de bureau garde sa carte. */
.ui-profil-diapos,
.ui-profil-infos,
.ui-profil-glisse,
.ui-profil-fiche-lien { display: none; }

@media (width <= 576px) {
  /* Les photos, empilées sur le portrait : une seule visible. */
  .ui-profil-diapos { display: block; position: absolute; inset: 0; }
  .ui-profil-diapo {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    opacity: 0;
  }
  .ui-profil-diapo.is-current { opacity: 1; }
  /* Les barres, en haut : une par photo, la courante pleine. */
  /* 10px du haut, et des traits courts, centrés (30 sept. 2026). */
  .ui-profil-diapos-barres {
    position: absolute;
    inset: 10px var(--space-2) auto;
    z-index: 1;
    display: flex;
    justify-content: center;
    gap: var(--space-1);
  }
  .ui-profil-diapos-barres > span {
    flex: 0 0 var(--space-4);
    height: 3px;
    border-radius: var(--radius-pill);
    background: color-mix(in srgb, var(--white) 40%, transparent);
  }
  .ui-profil-diapos-barres > .is-current { background: var(--white); }
  /* Les deux moitiés de la photo : un tap à gauche recule, à droite avance.
     Sous les badges et les boutons, qui restent cliquables. */
  .ui-profil-diapo-prec,
  .ui-profil-diapo-suiv {
    position: absolute;
    inset-block: 0;
    width: 50%;
    padding: 0;
    border: 0;
    background: none;
  }
  .ui-profil-diapo-prec { inset-inline-start: 0; }
  .ui-profil-diapo-suiv { inset-inline-end: 0; }
  .ui-profil-diapos ~ * { z-index: 1; }
  /* Les taux et les badges du haut descendent de 10px sous les barres. */
  .ui-profil-photo:has(> .ui-profil-diapos) > :is(.ui-profil-scores, .ui-profil-demande) { top: calc(0.9375rem + 10px); }
  .ui-profil-photo:has(> .ui-profil-diapos) > .ui-profil-demande + .ui-profil-demande { top: calc(3.25rem + 10px); }

  /* LES RENSEIGNEMENTS, SANS CARTE (1er oct. 2026), à même le fond de
     page : le haut de la fiche (relation souhaitée, personnalité) en pleine
     largeur, le reste en DEUX COLONNES. Le nom du renseignement en
     sur-titre gris, la valeur dessous, sans gras. (Essayés avant : des
     mini-cartes blanches, une liste sur fond foncé, des cartes sombres.) */
  .ui-profil-infos {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-4) var(--space-3);
    margin: 0;
  }
  /* TROIS CARTES (1er oct. 2026) : la relation souhaitée, la personnalité,
     chacune dans sa carte, puis tout le reste dans une troisième — blanches,
     comme la carte du profil au-dessus. (Avant : une seule carte, le haut
     séparé du reste par un filet.) */
  .ui-profil-infos--haut {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-3);
  }
  .ui-profil-infos--haut > .ui-profil-info,
  .ui-profil-fiche > .ui-profil-infos:not(.ui-profil-infos--haut) {
    border-radius: var(--radius);
    background: var(--surface-card);
    padding: var(--space-4);
    box-shadow: var(--shadow-md);
  }
  .ui-profil-infos--haut + .ui-profil-infos { margin-top: var(--space-3); }
  .ui-profil-info dt {
    display: flex;
    align-items: center;
    gap: var(--space-1);
    color: var(--text-muted);
    font-size: var(--fs-caption);
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
  }
  .ui-profil-info dt .material-symbols-outlined { font-size: var(--icon-sm); color: var(--color-primary); }
  .ui-profil-info dd { margin: var(--space-1) 0 0; color: var(--text); font-size: var(--fs-base); }
  /* Relation et personnalité en badges à icône, comme sur la fiche
     (1er oct. 2026) : 14px sous l'intitulé. */
  .ui-profil-info dd.ui-profil-tags { margin-top: calc(var(--space-3) - 2px); }
  /* Le dessin des badges de la fiche : texte de 16 en pétrole, picto de 16. */
  .ui-profil-info dd.ui-profil-tags > span {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    color: var(--color-titre);
    font-size: var(--fs-base);
    font-weight: 600;
  }
  .ui-profil-info dd.ui-profil-tags > span > .material-symbols-outlined {
    font-size: var(--icon-sm);
    color: var(--color-titre);
  }

  /* « Glisse pour changer de profil », sous la photo, entre deux flèches
     qui font passer au profil d'avant ou d'après (setupSwipe). */
  .ui-profil-glisse {
    display: flex;
    align-items: center;
    justify-content: space-between;
    /* Centrée entre la carte du profil et celle des renseignements : la
       même marge dessus et dessous (les flèches de 48px donnent l'air). */
    margin: var(--space-2) 0;
    color: var(--text-muted);
    font-size: var(--fs-caption);
  }
  .ui-profil-glisse-bouton {
    display: grid;
    place-items: center;
    width: var(--control-h-tactile);
    height: var(--control-h-tactile);
    border: 0;
    background: none;
    padding: 0;
    color: var(--color-titre);
  }
  /* Le nom, le lieu et la présence : le bloc blanc de la carte, sous la
     photo (ui-organismes.css). */
  .ui-profil-entete { color: inherit; }
  /* 30px au-dessus de « Voir le profil complet ». */
  .ui-profil-fiche-lien { display: flex; width: 100%; margin-top: calc(var(--space-4) + var(--space-2) - 2px); }
}

/* ══════════ LE TABLEAU — LES LISTES DE L'ADMINISTRATION (1er oct. 2026) ══════════
   molecules/tableau : la recherche et les filtres au-dessus, une carte qui
   porte le tableau, la pagination dessous. Au téléphone, chaque ligne devient
   un bloc, l'intitulé de colonne devant sa valeur. */
.ui-tableau { display: flex; flex-direction: column; gap: var(--space-3); }
.ui-tableau-outils { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3); }
.ui-tableau-recherche {
  position: relative;
  flex: 1 1 260px;
  max-width: 420px;
  margin: 0;
}
.ui-tableau-recherche > .material-symbols-outlined {
  position: absolute;
  top: 50%;
  left: var(--space-3);
  transform: translateY(-50%);
  color: var(--text-muted);
  font-size: var(--icon-md);
  pointer-events: none;
}
.ui-tableau-recherche > .form-control { padding-left: calc(var(--space-3) + var(--space-4)); }
.ui-tableau-filtres {
  display: flex;
  gap: var(--space-2);
  overflow-x: auto;
  scrollbar-width: none;
}
.ui-tableau-filtres::-webkit-scrollbar { display: none; }
/* Le dessin des filtres du fil (.ui-fil-filtre). */
.ui-tableau-filtre {
  display: inline-flex;
  align-items: center;
  min-height: var(--control-h-sm);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--surface-card);
  padding: 0 var(--space-3);
  color: var(--text-muted);
  font-size: var(--fs-sm);
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
  transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease;
}
.ui-tableau-filtre:hover { border-color: var(--color-titre); color: var(--color-titre); }
.ui-tableau-filtre.is-current { border-color: var(--color-titre); background: var(--color-titre); color: var(--white); }
.ui-tableau-nombre {
  margin-left: var(--space-2);
  color: inherit;
  font-weight: var(--fw-bold);
  opacity: 0.7;
}
.ui-tableau-compte { margin: 0; color: var(--text-muted); font-size: var(--fs-sm); }
/* « .ui-tableau > » : ui-charte.css, chargée après, rembourre toute .ui-carte. */
.ui-tableau > .ui-tableau-cadre { border-radius: var(--radius-md); padding: 0; overflow: hidden; }
/* L'arrondi STANDARD des cartes (18px) : ui-charte.css pose 28 sur .ui-carte. */
.ui-tableau > .ui-tableau-vide { border-radius: var(--radius-md); }
.ui-tableau-table { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); }
.ui-tableau-table th {
  /* La hauteur d'une ligne du tableau (57px : portrait de 40 + 2 × 8). */
  height: 57px;
  border-bottom: 1px solid var(--border);
  background: var(--surface-douce);
  padding: var(--space-2) var(--space-3);
  color: var(--text-muted);
  font-size: var(--fs-caption);
  font-weight: var(--fw-bold);
  letter-spacing: 0.08em;
  text-align: left;
  text-transform: uppercase;
  white-space: nowrap;
}
.ui-tableau-table td {
  border-bottom: 1px solid var(--border);
  padding: var(--space-2) var(--space-3);
  color: var(--text);
  vertical-align: middle;
}
.ui-tableau-table tbody tr:last-child > td { border-bottom: 0; }
.ui-tableau-table tbody tr:hover > td { background: var(--teinte-neutre); }
.ui-tableau-actions { width: 1%; text-align: right; white-space: nowrap; }
.ui-tableau-personne { display: flex; align-items: center; gap: var(--space-2); color: inherit; text-decoration: none; }
a.ui-tableau-personne:hover .ui-tableau-nom { text-decoration: underline; }
.ui-tableau-personne > span { display: flex; flex-direction: column; min-width: 0; }
.ui-tableau-avatar {
  width: 2.5rem;
  height: 2.5rem;
  flex-shrink: 0;
  border-radius: var(--radius-photo);
  object-fit: cover;
}
.ui-tableau-nom { color: var(--color-titre); font-weight: var(--fw-semibold); }
.ui-tableau-sous { color: var(--text-muted); font-size: var(--fs-caption); }
.ui-tableau-etat {
  display: inline-block;
  margin: 2px var(--space-1) 2px 0;
  border-radius: var(--radius-pill);
  background: var(--surface-douce);
  padding: 2px var(--space-2);
  color: var(--color-titre);
  font-size: var(--fs-caption);
  font-weight: var(--fw-semibold);
  white-space: nowrap;
}
.ui-tableau-etat--ok { background: color-mix(in srgb, var(--color-success) 12%, var(--white)); color: var(--color-success); }
.ui-tableau-etat--alerte { background: var(--color-brand-pink-soft); color: var(--color-brand-pink-ink); }
.ui-tableau-etat--attente { background: var(--teinte-neutre); color: var(--text-muted); }
.ui-tableau-vide {
  flex-direction: row;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
  color: var(--text-muted);
}
.ui-tableau-pied { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); }
/* Les actions groupées : la barre au-dessus du tableau, et la colonne des
   cases. */
.ui-tableau-groupe { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); margin: 0; }
.ui-tableau-groupe-compte { margin-right: auto; color: var(--text-muted); font-size: var(--fs-sm); }
.ui-tableau-groupe > .form-select { width: auto; min-width: 200px; }
.ui-tableau-table .ui-tableau-coche { width: 1%; padding-right: 0; }
/* La même case dans l'entête (12px) et dans les lignes (14px) : 1em de 14. */
.ui-tableau-coche > .form-check-input { margin: 0; font-size: var(--fs-sm); cursor: pointer; }
.ui-tableau-table tbody tr:has(.ui-tableau-coche > :checked) > td { background: var(--teinte-neutre); }

@media (width <= 767px) {
  .ui-tableau-recherche { max-width: none; }
  .ui-tableau-table thead { display: none; }
  .ui-tableau-table,
  .ui-tableau-table tbody,
  .ui-tableau-table tr,
  .ui-tableau-table td { display: block; width: 100%; }
  .ui-tableau-table tr {
    position: relative;
    border-bottom: 1px solid var(--border);
    padding: var(--space-3);
  }
  .ui-tableau-table tbody tr:last-child { border-bottom: 0; }
  .ui-tableau-table td { border: 0; padding: var(--space-1) 0; }
  .ui-tableau-table td[data-label]:not(.ui-tableau-tete)::before {
    content: attr(data-label);
    display: block;
    color: var(--text-muted);
    font-size: var(--fs-caption);
    font-weight: var(--fw-bold);
    letter-spacing: 0.08em;
    text-transform: uppercase;
  }
  .ui-tableau-table td.ui-tableau-tete { padding-right: var(--control-h-tactile); }
  /* La case, en haut à gauche du bloc. */
  .ui-tableau-table tr:has(> .ui-tableau-coche) { padding-left: calc(var(--space-3) + var(--space-5)); }
  .ui-tableau-table td.ui-tableau-coche {
    position: absolute;
    top: calc(var(--space-3) + var(--space-3));
    left: var(--space-3);
    width: auto;
    padding: 0;
  }
  .ui-tableau-groupe > .form-select { flex: 1 1 auto; min-width: 0; }
  .ui-tableau-table .ui-tableau-actions {
    position: absolute;
    top: var(--space-3);
    right: var(--space-3);
    width: auto;
    padding: 0;
  }
}

/* PLUSIEURS PHOTOS CHOISIES (un billet, 2 oct. 2026) : l'aperçu rond devient
   une planche de vignettes carrées, cinq par rangée. */
.ui-champ-photo-apercu.ui-champ-photo-apercu--plusieurs {
  grid-template-columns: repeat(5, 2.75rem);
  gap: var(--space-1);
  width: auto;
  height: auto;
  border-radius: var(--radius-md);
  padding: var(--space-1);
}
.ui-champ-photo-apercu--plusieurs img { width: 2.75rem; height: 2.75rem; border-radius: var(--radius-photo); object-fit: cover; }

/* ---- LE BANDEAU « PUBLICATION EN COURS… » (2 oct. 2026) ----
   Posé par le script pendant l'envoi d'un billet à photos, la fenêtre déjà
   fermée : en bas de l'écran, au-dessus de tout, avec une roue qui tourne. */
.ui-envoi {
  position: fixed;
  left: 50%;
  bottom: var(--space-4);
  z-index: 1090;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  width: max-content;
  max-width: calc(100% - var(--space-4));
  text-align: center;
  transform: translateX(-50%);
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-pill);
  background: var(--color-dark);
  color: var(--white);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  box-shadow: var(--shadow-md);
}

.ui-envoi-roue {
  width: var(--space-3);
  height: var(--space-3);
  flex-shrink: 0;
  border: 2px solid color-mix(in srgb, var(--white) 40%, transparent);
  border-top-color: var(--white);
  border-radius: 50%;
  animation: ui-envoi-tourne 0.8s linear infinite;
}

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

@media (prefers-reduced-motion: reduce) {
  .ui-envoi-roue { animation: none; }
}

/* Au téléphone, au-dessus de la barre de navigation du bas (64px), comme le
   rond « Revenir en haut ». */
@media (width <= 900px) {
  .ui-envoi { bottom: calc(64px + var(--space-3) + env(safe-area-inset-bottom)); }
}

/* ==== ENCART — la carte de colonne (molecules/encart.snip, 3 oct. 2026) ====
   Venue du rail de « Mon fil » ; base de toutes les cartes de colonne. */
.ui-fil-encart {
  background: var(--surface-card);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  margin-bottom: var(--space-4);
  padding: var(--space-4);
}

.ui-fil-encart:last-child { margin-bottom: 0; }

.ui-fil-encart-titre {
  margin: 0 0 var(--space-3);
  color: var(--text-muted);
  font-size: var(--fs-caption);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.ui-fil-encart-item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding-block: var(--space-2);
}

.ui-fil-encart-avatar {
  width: 40px;
  height: 40px;
  border-radius: var(--radius-photo);
  object-fit: cover;
  flex-shrink: 0;
}

/* LE CARRÉ SANS PHOTO : une icône, mêmes dimensions et mêmes coins que la
   vignette, sur le vert pâle des pastilles d'activité (3 oct. 2026). */
.ui-fil-encart-icone {
  flex-shrink: 0;
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: var(--radius-photo);
  background: var(--surface-appuyee);
  color: var(--color-primary);
  font-size: var(--icon-md);
}

.ui-fil-encart-corps { flex: 1; min-width: 0; display: flex; flex-direction: column; }

.ui-fil-encart-nom {
  color: var(--text);
  font-size: var(--fs-sm);
  font-weight: 700;
  line-height: 1.3;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ui-fil-encart-nom:hover { color: var(--color-titre); }

/* La précision passe à la ligne (3 oct. 2026) : « Lieu de rendez-vous » ou
   « Participation » portent des phrases, que l'ellipse coupait. */
.ui-fil-encart-meta {
  color: var(--text-muted);
  font-size: var(--fs-caption);
}

/* La ligne de la page affichée : l'allure du survol, comme l'agenda. */
.ui-fil-encart-item.is-current {
  margin-inline: calc(var(--space-2) * -1);
  padding-inline: var(--space-2);
  border-radius: var(--radius-sm);
  background: var(--surface-appuyee);
}

.ui-fil-encart-vide { margin: 0; color: var(--text-muted); font-size: var(--fs-sm); }

.ui-fil-encart-action {
  flex-shrink: 0;
  display: inline-grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border: 1px solid var(--border);
  border-radius: 50%;
  background: transparent;
  color: var(--color-titre);
  cursor: pointer;
  transition: background 0.2s ease, border-color 0.2s ease, color 0.2s ease;
}

.ui-fil-encart-action .material-symbols-outlined { font-size: var(--icon-md); }

.ui-fil-encart-action:hover,
.ui-fil-encart-action:focus-visible {
  background: var(--color-titre);
  border-color: var(--color-titre);
  color: var(--white);
}

/* LES LIENS DE PIED D'ENCART SONT ROSES, comme les CTA de l'accueil dont ils
   sont les jumeaux littéraux : « Voir les 12 → », « Tous mes évènements → »,
   « Toutes les communautés → » ont la même formulation, la même flèche et la
   même place en pied de section que « Découvrir toutes les communautés → ».
   Ils étaient en pétrole, c'est-à-dire de la même couleur que le texte courant.
   EN ENCRE ET NON EN APLAT. L'accueil les remplit parce qu'il n'en a qu'un par
   section ; ici il y en a trois, empilés dans une colonne de 300px, et trois
   capsules roses les unes sous les autres se disputeraient l'attention avec le
   fil, qui est ce qu'on est venu lire. Le seul aplat rose de la page est le
   bouton « Publier », qui conclut.
   Rose sur le blanc des cartes : 4,89:1, au-dessus du seuil AA de 4,5. */
.ui-fil-encart-lien {
  display: block;
  width: fit-content;
  margin-top: var(--space-3);
  color: var(--color-brand-pink-ink);
  font-size: var(--fs-sm);
  font-weight: 700;
}

.ui-fil-encart-lien:hover { text-decoration: underline; }
.ui-fil-encart-lien + .ui-fil-encart-lien { margin-top: var(--space-2); }

/* Un geste posté au pied de l'encart (« Quitter la communauté »), pleine
   largeur. */
.ui-fil-encart-pied { margin-top: var(--space-3); }
.ui-fil-encart-pied > .ui-btn { width: 100%; }

/* Deux encarts qui se suivent hors d'une liste (rail d'une communauté). */
.ui-fil-encart--essentiel { margin-bottom: var(--space-4); }

/* --repli : sous 900px, l'encart se réduit à ses liens, en pilules côte à
   côte — le repli de l'agenda des pages d'évènements, qu'il remplace. */
@media (width <= 900px) {
  .ui-fil-encart--repli {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin: 0 0 var(--space-3);
    border: 0;
    background: none;
    padding: 0;
    box-shadow: none;
  }
  .ui-fil-encart--repli > :not(.ui-fil-encart-lien) { display: none; }
  .ui-fil-encart--repli > .ui-fil-encart-lien {
    display: inline-flex;
    align-items: center;
    min-height: 40px;
    margin: 0;
    border: 1px solid var(--border);
    border-radius: var(--radius-pill);
    background: var(--surface-card);
    padding: 0 var(--space-3);
    color: var(--color-titre);
    white-space: nowrap;
  }
}
