/* ── Passion Plante — direction « Atelier » (PLANT-130) ──────────────────────
   Clair et net, à la manière d'une app native : des listes groupées sur un fond
   froid, des titres gras et serrés, une seule famille (Figtree). L'eau est la
   seule couleur froide et vive, et elle ne sert qu'à l'arrosage. Le thème suit
   le réglage du téléphone : les jetons ci-dessous sont redéfinis en sombre,
   rien d'autre ; un thème forcé depuis le menu passe par `data-theme`. */

/* Servie depuis `public/fonts/` et non par un CDN : l'app vit sur le réseau
   local et doit s'afficher sans dépendre d'un tiers. Licence OFL à côté. */
@font-face {
  font-family: "Figtree";
  font-style: normal;
  font-weight: 300 900;
  font-display: swap;
  src: url("/fonts/figtree.woff2") format("woff2");
}

:root {
  color-scheme: light dark;

  --fond: #f2f4f1;
  --fond-voile: rgb(242 244 241 / 86%);
  --surface: #ffffff;
  --surface-2: #eef1ec;
  --encre: #13201a;
  --doux: #66706a;
  --filet: #e3e7e1;

  --accent: #17603f;
  --accent-encre: #ffffff;
  --accent-clair: #e2efe7;

  --eau: #1f86ad;
  --eau-clair: #e3f1f7;
  --vague: #cfe7f1;
  --goutte-faite: #1f86ad;
  --goutte-faite-encre: #ffffff;

  --danger: #b73d22;
  --danger-clair: #fbe8e3;
  --danger-encre: #ffffff;
  --taille: #c46b1c;

  --dock: rgb(255 255 255 / 90%);
  --bord-dock: transparent;

  --ombre: 0 1px 2px rgb(19 32 26 / 5%);
  --ombre-flottante: 0 10px 30px rgb(19 32 26 / 14%), 0 2px 6px rgb(19 32 26 / 6%);

  --police: "Figtree", -apple-system, "Helvetica Neue", "Segoe UI", sans-serif;
  --rayon-groupe: 1.3rem;
  --rayon-champ: 0.8rem;

  /* Le dock flotte : sa hauteur (bouton de 2.75rem, 0.4rem de padding de part
     et d'autre, la bordure) plus l'écart qui le sépare du bord. `main` s'en
     sert pour dégager la place d'une barre qui est `fixed`, donc hors du flux. */
  --hauteur-barre-actions: 4.4rem;
  --ecart-dock: 0.75rem;

  /* Les icônes dessinées en CSS sont des masques : elles prennent la couleur de
     qui les porte, dans les deux thèmes. */
  --icone-goutte: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8 1.9c2.4 2.8 4 5 4 7a4 4 0 0 1-8 0c0-2 1.6-4.2 4-7z'/%3E%3C/svg%3E");
  --icone-plus: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round'%3E%3Cpath d='M8 3v10M3 8h10'/%3E%3C/svg%3E");
  --icone-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 3.5L10.5 8 6 12.5'/%3E%3C/svg%3E");
  --icone-loupe: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round'%3E%3Ccircle cx='6.8' cy='6.8' r='4.3'/%3E%3Cpath d='M13 13l-3.1-3.1'/%3E%3C/svg%3E");
}

/* Le sombre, deux fois et à l'identique : par le réglage du téléphone quand le
   thème est « auto », et forcé depuis le menu (`theme.js` pose `data-theme`).
   Une propriété personnalisée ne se partage pas entre deux blocs, d'où la
   recopie ; `tests-front/style.test.js` exige qu'ils restent égaux. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="clair"]) {
    --fond: #0e1411;
    --fond-voile: rgb(14 20 17 / 86%);
    --surface: #161f1a;
    --surface-2: #1f2a24;
    --encre: #e9f0eb;
    --doux: #8d9d93;
    --filet: #243129;

    --accent: #7fd3a0;
    --accent-encre: #0b2616;
    --accent-clair: #1a3325;

    --eau: #6fcff3;
    --eau-clair: #15303a;
    --vague: #1b3d4b;
    --goutte-faite: #173745;
    --goutte-faite-encre: #6fcff3;

    --danger: #ff8d73;
    --danger-clair: #3a1d17;
    --danger-encre: #2a0d06;
    --taille: #f0a45b;

    --dock: rgb(24 34 29 / 92%);
    --bord-dock: #26342d;

    --ombre: none;
    --ombre-flottante: 0 12px 32px rgb(0 0 0 / 45%);
  }
}

:root[data-theme="sombre"] {
  color-scheme: dark;
  --fond: #0e1411;
  --fond-voile: rgb(14 20 17 / 86%);
  --surface: #161f1a;
  --surface-2: #1f2a24;
  --encre: #e9f0eb;
  --doux: #8d9d93;
  --filet: #243129;

  --accent: #7fd3a0;
  --accent-encre: #0b2616;
  --accent-clair: #1a3325;

  --eau: #6fcff3;
  --eau-clair: #15303a;
  --vague: #1b3d4b;
  --goutte-faite: #173745;
  --goutte-faite-encre: #6fcff3;

  --danger: #ff8d73;
  --danger-clair: #3a1d17;
  --danger-encre: #2a0d06;
  --taille: #f0a45b;

  --dock: rgb(24 34 29 / 92%);
  --bord-dock: #26342d;

  --ombre: none;
  --ombre-flottante: 0 12px 32px rgb(0 0 0 / 45%);
}

:root[data-theme="clair"] {
  color-scheme: light;
}

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  padding: 0;
}

body {
  background: var(--fond);
  color: var(--encre);
  font-family: var(--police);
  font-size: 1rem;
  line-height: 1.45;
  min-height: 100vh;
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
}

/* Les contrôles n'héritent pas de la police par défaut. `inherit` leur donne
   la famille ; la **taille** reste posée par chaque règle de champ, parce
   qu'un champ logé dans un libellé plus petit hériterait de moins de 16 px et
   ferait zoomer iOS. */
button,
input,
select,
textarea {
  font-family: inherit;
  color: inherit;
}

a {
  color: var(--accent);
}

h1,
h2 {
  text-wrap: balance;
}

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ── Barre du haut : elle informe ──────────────────────────────────────────── */

.topbar {
  position: sticky;
  top: 0;
  z-index: 5;
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: 3.4rem;
  padding: 0.5rem 1.1rem;
  /* `viewport-fit=cover` laisse la page passer sous l'encoche : sans ce
     rattrapage la barre du haut s'y glisserait (PLANT-75). */
  padding-top: calc(0.5rem + env(safe-area-inset-top));
  background: var(--fond-voile);
  -webkit-backdrop-filter: blur(16px) saturate(1.4);
  backdrop-filter: blur(16px) saturate(1.4);
}

.topbar .marque {
  font-size: 1.05rem;
  font-weight: 800;
  letter-spacing: -0.02em;
  color: var(--accent);
  text-decoration: none;
}

/* Nom du jardin courant, à la place de la marque sur l'accueil (PLANT-73) :
   c'est le titre de la page. Tronqué plutôt que replié — `min-width: 0` est ce
   qui autorise un élément de flex à passer sous sa largeur de contenu, sans
   quoi `text-overflow` ne se déclenche jamais. */
.topbar .jardin-courant {
  font-size: 1.6rem;
  font-weight: 800;
  letter-spacing: -0.035em;
  line-height: 1.15;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.topbar .retour {
  display: inline-flex;
  align-items: center;
  gap: 0.15rem;
  min-height: 2.75rem;
  margin-left: -0.55rem;
  padding: 0 0.8rem 0 0.4rem;
  border-radius: 999px;
  font-size: 1rem;
  font-weight: 600;
  color: var(--accent);
  text-decoration: none;
}

.topbar .retour svg {
  width: 1.15rem;
  height: 1.15rem;
}

.topbar .retour:active {
  background: var(--surface-2);
}

/* `mes-informations.js` le masque sur un code provisoire (PLANT-132), et
   `display: inline-flex` bat le `[hidden]` du navigateur. */
.topbar .retour[hidden] {
  display: none;
}

/* ── Menu, dans le dock ──────────────────────────────────────────────────────
   Le « ☰ » vit dans la zone du pouce (PLANT-75). Son balisage vient de
   `utils.js`, donc il est au même endroit sur les cinq pages. Sa pastille est
   `.bouton-barre` : il la partage avec la loupe, et cette largeur commune est
   ce qui centre le « + » entre les deux. */

.menu-topbar {
  position: relative;
  display: inline-flex;
}

/* Ancré **au-dessus** du bouton et à gauche : le bouton est en bas à gauche,
   un ancrage en haut à droite l'enverrait hors de l'écran (PLANT-75). */
.menu-topbar-panneau {
  position: absolute;
  bottom: calc(100% + 0.9rem);
  left: -0.4rem;
  z-index: 10;
  min-width: 14.5rem;
  max-width: calc(100vw - 2rem);
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  padding: 0.4rem;
  background: var(--surface);
  border: 1px solid var(--filet);
  border-radius: 1.1rem;
  box-shadow: var(--ombre-flottante);
  transform-origin: 1.4rem calc(100% + 1rem);
}

/* Sans ça le panneau est **toujours** ouvert : `display: flex` bat le
   `[hidden] { display: none }` du navigateur en spécificité. */
.menu-topbar-panneau[hidden] {
  display: none;
}

/* Une `animation` et non une `transition` : passer de `display: none` à
   `flex` ne joue aucune transition, alors qu'une animation part au rendu. */
@media (prefers-reduced-motion: no-preference) {
  .menu-topbar-panneau {
    animation: panneau-s-ouvre 0.18s cubic-bezier(0.2, 0.9, 0.3, 1.15);
  }
}

@keyframes panneau-s-ouvre {
  from {
    opacity: 0;
    transform: translateY(0.35rem) scale(0.94);
  }
}

/* Boutons et liens s'y présentent à l'identique : dans une liste, ce qui
   compte est le libellé, pas la nature de l'élément HTML. */
.entree-menu {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  min-height: 2.75rem;
  padding: 0.45rem 0.75rem;
  font-size: 1rem;
  font-weight: 500;
  color: var(--encre);
  text-align: left;
  text-decoration: none;
  background: none;
  border: 0;
  border-radius: 0.7rem;
  cursor: pointer;
}

.entree-menu:active {
  background: var(--surface-2);
}

@media (hover: hover) {
  .entree-menu:hover {
    background: var(--surface-2);
  }
}

.entree-menu:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* Les jardins coiffent le panneau : choisir *où* l'on est vient avant qui l'on
   est et ce qu'on y fait. Masqué à 0 et à 1 jardin (règle 0 / 1 / N). */
.menu-jardins {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  padding-bottom: 0.35rem;
  margin-bottom: 0.3rem;
  border-bottom: 1px solid var(--filet);
}

.menu-jardins[hidden] {
  display: none;
}

/* Sans ce titre, deux noms de jardins posés au milieu des commandes se lisent
   comme deux commandes de plus. */
.menu-groupe-titre {
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--doux);
  padding: 0.35rem 0.75rem 0.15rem;
}

/* Le courant est marqué par un ✓ en pseudo-élément et non par un caractère du
   libellé : le nom du jardin reste le texte de l'entrée. Les autres portent
   l'espace correspondant, pour que la colonne des noms reste alignée. */
.menu-jardins .entree-menu::before {
  content: "✓";
  flex: none;
  width: 1rem;
  font-weight: 700;
  color: transparent;
}

.menu-jardins .entree-menu[aria-current],
.menu-jardins .entree-menu[aria-current]::before {
  color: var(--accent);
}

.menu-jardins .entree-menu[aria-current] {
  font-weight: 700;
}

/* ── Bloc d'utilisateur, dans le panneau ───────────────────────────────────── */

.menu-utilisateur {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}

.menu-utilisateur[hidden] {
  display: none;
}

/* L'identité n'est pas une action : elle coiffe le panneau, séparée des
   commandes par un filet, pour qu'on ne la clique pas en cherchant une
   commande. */
.menu-utilisateur .identite-utilisateur {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.4rem 0.75rem 0.65rem;
  margin-bottom: 0.3rem;
  border-bottom: 1px solid var(--filet);
}

.menu-utilisateur .avatar-utilisateur {
  flex: none;
  display: grid;
  place-items: center;
  width: 2.2rem;
  height: 2.2rem;
  font-size: 1.15rem;
  border-radius: 999px;
  background: var(--surface-2);
}

.menu-utilisateur .nom-utilisateur {
  font-weight: 700;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ── Sélecteur de thème, au pied du panneau ─────────────────────────────────── */

.menu-theme {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin-top: 0.3rem;
  padding: 0.55rem 0.3rem 0.25rem 0.75rem;
  border-top: 1px solid var(--filet);
}

.menu-theme-titre {
  font-weight: 500;
  color: var(--doux);
}

.choix-theme {
  display: flex;
  gap: 0.15rem;
  padding: 0.2rem;
  background: var(--surface-2);
  border-radius: 999px;
}

.choix-theme button {
  min-height: 2.4rem;
  padding: 0.3rem 0.75rem;
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--doux);
  background: none;
  border: 0;
  border-radius: 999px;
  cursor: pointer;
}

.choix-theme button[aria-checked="true"] {
  color: var(--encre);
  background: var(--surface);
  box-shadow: 0 1px 3px rgb(0 0 0 / 14%);
}

/* ── Dock : la barre d'actions, dans la zone du pouce ────────────────────────
   Sur l'accueil : le « ☰ » à gauche, le « + » au centre, la loupe à droite
   (PLANT-90) ; ailleurs le « ☰ » seul (PLANT-75).

   `fixed` et non `sticky` : dernier enfant de `body`, un élément `sticky` ne
   colle au bas de l'écran que si la page dépasse l'écran, et sur une page
   courte il flotterait au milieu du contenu. `.barre-selection` porte la
   **même** géométrie : elle remplace ce dock, elle ne s'empile pas dessus. */
.barre-actions {
  position: fixed;
  /* `viewport-fit=cover` laisse la page passer sous la barre d'accueil de
     l'iPhone : sans le rattrapage, le dock s'y glisserait. */
  inset: auto max(var(--ecart-dock), (100% - 30rem) / 2) calc(var(--ecart-dock) + env(safe-area-inset-bottom));
  z-index: 5;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.7rem;
  padding: 0.4rem;
  background: var(--dock);
  -webkit-backdrop-filter: blur(16px) saturate(1.4);
  backdrop-filter: blur(16px) saturate(1.4);
  border: 1px solid var(--bord-dock);
  border-radius: 999px;
  box-shadow: var(--ombre-flottante);
}

.barre-actions[hidden] {
  display: none;
}

/* Le « ☰ » (rendu par `utils.js`) et la loupe de l'accueil. Leur largeur
   commune n'est pas une coïncidence : `space-between` ne répartit que l'espace
   libre, donc le « + » n'est centré que si ses deux voisins font la même
   largeur. */
.bouton-barre {
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 999px;
  background: none;
  border: 0;
  color: var(--encre);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  cursor: pointer;
}

.bouton-barre svg {
  width: 1.25rem;
  height: 1.25rem;
}

.bouton-barre:active,
.bouton-barre[aria-expanded="true"] {
  background: var(--surface-2);
}

@media (hover: hover) {
  .bouton-barre:hover {
    background: var(--surface-2);
  }
}

.bouton-rond {
  width: 3.4rem;
  height: 2.75rem;
  border-radius: 999px;
  background: var(--accent);
  color: var(--accent-encre);
  display: flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
  line-height: 1;
}

.bouton-rond svg {
  width: 1.4rem;
  height: 1.4rem;
}

/* Sans ce garde-fou le « + » ne se masque **jamais** : `display: flex` bat le
   `[hidden]` du navigateur, et c'est `index.js` qui pose l'attribut quand le
   droit d'édition manque — hors de portée du relevé de `style.test.js`. */
.bouton-rond[hidden] {
  display: none;
}

@media (prefers-reduced-motion: no-preference) {
  .bouton-rond,
  .bouton-barre {
    transition: transform 0.12s ease;
  }

  .bouton-rond:active,
  .bouton-barre:active {
    transform: scale(0.93);
  }
}

/* Le `padding-bottom` dégage la hauteur du dock, qui sort du flux : sans lui la
   dernière plante passe dessous et devient intapable (PLANT-75). */
main {
  max-width: 640px;
  margin: 0 auto;
  padding: 0.5rem 1rem calc(var(--hauteur-barre-actions) + env(safe-area-inset-bottom) + 1rem);
}

/* ── Messages ──────────────────────────────────────────────────────────────── */

/* Lien périmé vers un jardin disparu : on affiche un jardin valide et on le dit,
   plutôt que de laisser une page morte. */
.avertissement {
  margin: 0.25rem 0 0.9rem;
  padding: 0.75rem 0.95rem;
  border-radius: 1rem;
  font-size: 0.95rem;
  color: var(--encre);
  background: var(--eau-clair);
}

.erreur {
  color: var(--danger);
  font-size: 0.92rem;
  font-weight: 500;
}

.aide {
  font-size: 0.92rem;
  color: var(--doux);
  margin: 0 0 0.8rem;
}

/* Masquée par `hidden`, d'où le garde-fou : sans lui la règle de classe
   battrait `[hidden]` et le message resterait affiché en permanence. */
.succes {
  display: block;
  margin: 0 0 1rem;
  padding: 0.7rem 0.95rem;
  font-size: 0.95rem;
  font-weight: 500;
  color: var(--accent);
  background: var(--accent-clair);
  border-radius: 1rem;
}

.succes[hidden] {
  display: none;
}

.vide {
  color: var(--doux);
  font-size: 0.98rem;
  padding: 2rem 0.5rem;
  text-align: center;
}

/* ── Attente : le seul indicateur de chargement de l'app (PLANT-76) ──────────
   Posé en tête de `main` par `attenteChargement`, **inséré puis retiré** :
   jamais masqué par `hidden`, donc pas de garde-fou à poser. Le texte porte
   l'information ; l'anneau n'est que du mouvement et n'existe que sous
   `prefers-reduced-motion: no-preference`. */
.chargement {
  align-items: center;
  color: var(--doux);
  display: flex;
  font-size: 0.95rem;
  gap: 0.6rem;
  justify-content: center;
  padding: 2.5rem 0;
}

@media (prefers-reduced-motion: no-preference) {
  .chargement::before {
    animation: rotation-chargement 0.8s linear infinite;
    border: 2px solid var(--filet);
    border-radius: 50%;
    border-top-color: var(--accent);
    content: "";
    flex: none;
    height: 1.1rem;
    width: 1.1rem;
  }
}

@keyframes rotation-chargement {
  to {
    transform: rotate(1turn);
  }
}

/* ── Recherche ─────────────────────────────────────────────────────────────── */

.barre-recherche {
  position: relative;
  display: flex;
  align-items: center;
  margin: 0.25rem 0 0.75rem;
}

.barre-recherche[hidden] {
  display: none;
}

.barre-recherche::before {
  content: "";
  position: absolute;
  left: 0.95rem;
  width: 1.05rem;
  height: 1.05rem;
  background: var(--doux);
  -webkit-mask: var(--icone-loupe) center / contain no-repeat;
  mask: var(--icone-loupe) center / contain no-repeat;
  pointer-events: none;
}

/* Les panneaux qui se déplient à la demande. Une animation, pour la même
   raison que le panneau du menu : ils sortent de `display: none`. */
@media (prefers-reduced-motion: no-preference) {
  .barre-recherche,
  .barre-renommage,
  .choix-date,
  .form-code {
    animation: se-deplie 0.2s ease-out;
  }
}

@keyframes se-deplie {
  from {
    opacity: 0;
    transform: translateY(-0.35rem);
  }
}

.champ-recherche {
  width: 100%;
  font-size: 0.95rem;
  color: var(--encre);
  background: var(--surface);
  border: 0;
  border-radius: 999px;
  padding: 0.75rem 2.8rem 0.75rem 2.6rem;
  box-shadow: var(--ombre);
}

.champ-recherche::placeholder {
  color: var(--doux);
}

.champ-recherche:focus {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.bouton-vider-recherche {
  position: absolute;
  right: 0.3rem;
  width: 2.3rem;
  height: 2.3rem;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--doux);
  background: none;
  border: none;
  border-radius: 999px;
  cursor: pointer;
}

.bouton-vider-recherche[hidden] {
  display: none;
}

.bouton-vider-recherche:active {
  background: var(--surface-2);
}

/* ── Renommage du jardin ───────────────────────────────────────────────────── */

.barre-renommage {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin: 0.25rem 0 1rem;
  padding: 0.85rem;
  background: var(--surface);
  border-radius: var(--rayon-groupe);
  box-shadow: var(--ombre);
}

.barre-renommage[hidden] {
  display: none;
}

.barre-renommage label {
  flex: 1 0 100%;
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--doux);
  padding-left: 0.15rem;
}

.barre-renommage input {
  flex: 1 1 8rem;
  min-width: 0;
  font-size: 0.95rem;
  background: var(--surface-2);
  border: 0;
  border-radius: var(--rayon-champ);
  padding: 0.65rem 0.8rem;
}

.barre-renommage button[type="submit"] {
  font-size: 0.95rem;
  font-weight: 600;
  background: var(--accent);
  color: var(--accent-encre);
  border: none;
  border-radius: 999px;
  padding: 0.62rem 1.1rem;
  cursor: pointer;
}

.barre-renommage button[type="button"] {
  width: 2.5rem;
  height: 2.5rem;
  display: grid;
  place-items: center;
  background: none;
  border: none;
  border-radius: 999px;
  color: var(--doux);
  padding: 0;
  cursor: pointer;
}

/* Le refus prend toute la largeur : poussé en bout de ligne, un message un peu
   long sortirait de l'écran sur un téléphone. */
.barre-renommage .erreur {
  flex: 1 0 100%;
}

/* Les deux barres sont à 0.95 rem, un choix pour un écran large. Sous 16 px iOS
   zoome à la prise de focus et laisse la page zoomée, donc la taille est reposée
   là où il l'impose. Le sélecteur doit être aussi spécifique que celui qu'il
   reprend, et déclaré après lui : une media query n'ajoute aucune spécificité. */
@media (max-width: 639px) {
  .champ-recherche,
  .barre-renommage input {
    font-size: 1rem;
  }
}

/* ── Liste des plantes : une liste groupée ─────────────────────────────────── */

.liste-plantes {
  list-style: none;
  margin: 0.25rem 0 0;
  padding: 0.2rem 0.6rem;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border-radius: var(--rayon-groupe);
  box-shadow: var(--ombre);
}

.ligne-plante {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.3rem 0.6rem;
  padding: 0.15rem 0;
}

.ligne-plante + .ligne-plante {
  border-top: 1px solid var(--filet);
}

.ligne-plante .case-plante {
  display: none;
  flex: none;
  width: 1.3rem;
  height: 1.3rem;
  margin: 0 0 0 0.15rem;
  accent-color: var(--accent);
  cursor: pointer;
}

.liste-plantes.mode-selection .case-plante {
  display: block;
}

.carte-plante {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 0.85rem;
  margin: 0 -0.4rem;
  padding: 0.55rem 0.4rem;
  border-radius: 0.9rem;
  text-decoration: none;
  color: var(--encre);
  /* Cadre de l'eau à l'arrosage. `z-index: 0` fait de la carte un contexte
     d'empilement, sans quoi le `z-index: -1` de l'eau la ferait passer derrière
     le fond de la carte au lieu de derrière son contenu. `overflow` pour que
     l'eau respecte l'arrondi. */
  position: relative;
  z-index: 0;
  overflow: hidden;
}

.carte-plante:active {
  background: var(--surface-2);
}

@media (hover: hover) {
  .carte-plante:hover {
    background: var(--surface-2);
  }
}

/* L'eau : un aplat surmonté d'une vague qui tuile à l'horizontale, découpés
   par un masque pour prendre la couleur du thème. Présent en permanence mais
   de hauteur nulle : c'est l'animation qui le fait monter. */
.carte-plante::after {
  content: "";
  position: absolute;
  inset: auto 0 0 0;
  height: 0;
  background: var(--vague);
  z-index: -1;
  pointer-events: none;
  -webkit-mask:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 60 12' preserveAspectRatio='none'%3E%3Cpath d='M0 6Q15 0 30 6T60 6V12H0Z'/%3E%3C/svg%3E") repeat-x 0 0 / 60px 12px,
    linear-gradient(#000, #000) no-repeat 0 11px / 100% 100%;
  mask:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 60 12' preserveAspectRatio='none'%3E%3Cpath d='M0 6Q15 0 30 6T60 6V12H0Z'/%3E%3C/svg%3E") repeat-x 0 0 / 60px 12px,
    linear-gradient(#000, #000) no-repeat 0 11px / 100% 100%;
}

.miniature-plante {
  flex: none;
  width: 3rem;
  height: 3rem;
  border-radius: 0.8rem;
  object-fit: cover;
  background: var(--surface-2);
}

/* Sans photo, l'initiale du nom fait l'image (`data-initiale`, posé par
   `index.js`). */
.miniature-vide {
  display: grid;
  place-items: center;
  font-size: 1.1rem;
  font-weight: 700;
  color: var(--doux);
}

.miniature-vide::before {
  content: attr(data-initiale);
}

/* La pastille ne disait rien — la même couleur sur toutes les plantes. Le
   balisage la garde, l'écran ne l'affiche pas. */
.pastille {
  display: none;
}

.carte-plante .infos {
  flex: 1;
  min-width: 0;
}

.carte-plante .nom {
  font-size: 1.02rem;
  font-weight: 650;
  letter-spacing: -0.01em;
  line-height: 1.25;
  overflow-wrap: anywhere;
}

.carte-plante .etat {
  margin-top: 0.05rem;
  font-size: 0.86rem;
  color: var(--doux);
  font-variant-numeric: tabular-nums;
}

@media (min-width: 640px) {
  .miniature-plante {
    width: 3.5rem;
    height: 3.5rem;
    border-radius: 0.9rem;
  }
}

/* ── Arrosage en un clic ───────────────────────────────────────────────────────
   Le bouton est un **frère** de `.carte-plante`, pas un enfant : un <button>
   dans un <a> est invalide, et le clic partirait vers la fiche. */
.bouton-arroser {
  flex: none;
  position: relative;
  width: 2.75rem;
  height: 2.75rem;
  display: grid;
  place-items: center;
  padding: 0;
  background: var(--eau-clair);
  border: 0;
  border-radius: 999px;
  color: var(--eau);
  cursor: pointer;
  /* L'appui long ouvre le choix de date (PLANT-112) : sans ces trois règles,
     iOS ouvre son callout et sélectionne du texte sous le doigt. Pas de
     `touch-action: none`, qui empêcherait de faire défiler la liste depuis le
     bouton. */
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}

.bouton-arroser svg {
  width: 1.3rem;
  height: 1.3rem;
  overflow: visible;
}

/* Transparente et non `none` : la goutte se remplit en fondu, et `none` ne
   s'interpole pas. */
.bouton-arroser svg path {
  fill: transparent;
}

/* Arrosée du jour : la goutte se remplit. Le bouton reste actif — un second
   tap retire l'arrosage du jour (PLANT-111) —, donc le curseur ne change pas. */
.bouton-arroser.fait {
  background: var(--goutte-faite);
  color: var(--goutte-faite-encre);
}

.bouton-arroser.fait svg path {
  fill: currentColor;
}

/* L'anneau du clavier prend la couleur de l'eau : le vert de l'accent jurait
   sur les seuls contrôles bleus de l'app. */
.bouton-arroser:focus-visible,
.choix-date button:focus-visible {
  outline-color: var(--eau);
}

/* ── Antidater d'un appui long ─────────────────────────────────────────────────
   Déplié **sous** la ligne plutôt que posé par-dessus : aucune couche à
   refermer, et le pouce reste sur la ligne qu'il visait. */
.choix-date {
  flex-basis: 100%;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.45rem;
  padding: 0.1rem 0 0.6rem;
}

.choix-date button {
  min-height: 2.5rem;
  padding: 0.4rem 0.95rem;
  font-size: 0.95rem;
  font-weight: 600;
  border: 0;
  border-radius: 999px;
  background: var(--eau-clair);
  color: var(--eau);
  cursor: pointer;
}

.choix-date .choix-date-valider {
  background: var(--eau);
  color: var(--surface);
}

/* Les deux bascules de « autre date… » sont faites en JS : `style.test.js` ne
   les voit pas, les garde-fous sont posés à la main. */
.choix-date-saisie {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.45rem;
}

.choix-date-saisie[hidden] {
  display: none;
}

.choix-date button[hidden] {
  display: none;
}

/* Le geste de l'app, et le seul mis en scène : la goutte rebondit et lâche deux
   ondes, l'eau monte dans la ligne avec sa vague puis se retire. Mouvement
   seul : sous `prefers-reduced-motion: reduce` rien ne part, et le retour reste
   la goutte qui se remplit plus le libellé « arrosée aujourd'hui ».

   650 ms au plus, et c'est un contrat : `DUREE_ANIMATION_ARROSAGE` d'`index.js`
   tient le verrou anti-double-tap sur la plus longue de ces animations. */
@media (prefers-reduced-motion: no-preference) {
  .bouton-arroser svg path {
    transition: fill 0.3s ease 0.12s;
  }

  .ligne-plante.arrosage-en-cours .bouton-arroser svg {
    animation: goutte-rebondit 0.5s cubic-bezier(0.3, 0.7, 0.4, 1);
  }

  .ligne-plante.arrosage-en-cours .bouton-arroser::before,
  .ligne-plante.arrosage-en-cours .bouton-arroser::after {
    content: "";
    position: absolute;
    inset: 0;
    border: 2px solid var(--eau);
    border-radius: 999px;
    opacity: 0;
    pointer-events: none;
    animation: onde 0.55s ease-out;
  }

  .ligne-plante.arrosage-en-cours .bouton-arroser::after {
    animation-delay: 0.1s;
  }

  .ligne-plante.arrosage-en-cours .carte-plante::after {
    animation: carte-se-remplit 0.65s cubic-bezier(0.3, 0.6, 0.4, 1);
  }
}

@keyframes goutte-rebondit {
  25% {
    transform: translateY(2px) scale(1.18, 0.82);
  }
  55% {
    transform: translateY(-4px) scale(0.9, 1.1);
  }
  80% {
    transform: scale(1.04, 0.96);
  }
}

@keyframes onde {
  0% {
    opacity: 0.55;
    transform: scale(1);
  }
  100% {
    opacity: 0;
    transform: scale(1.75);
  }
}

/* L'eau monte avec sa vague qui court, puis s'efface au lieu de disparaître
   d'un coup. La hauteur finale dépasse la carte de la hauteur de la vague : la
   crête sort par le haut, et la carte finit entièrement sous l'eau. */
@keyframes carte-se-remplit {
  0% {
    height: 0;
    -webkit-mask-position: 0 0, 0 11px;
    mask-position: 0 0, 0 11px;
    opacity: 1;
  }
  55% {
    height: calc(100% + 12px);
    opacity: 1;
  }
  100% {
    height: calc(100% + 12px);
    -webkit-mask-position: 90px 0, 0 11px;
    mask-position: 90px 0, 0 11px;
    opacity: 0;
  }
}

/* ── Sélection multiple ────────────────────────────────────────────────────────
   Même géométrie que `.barre-actions`, dont elle prend la place : avec deux
   positionnements différents, « remplace » redeviendrait « s'empile ». Le même
   `z-index` suffit, `index.js` masquant l'une quand il montre l'autre. Le même
   habit aussi : en couleurs inversées, elle faisait une pastille claire au bas
   d'un écran sombre. C'est le bouton rouge qui dit que le mode détruit. */
.barre-selection {
  position: fixed;
  inset: auto max(var(--ecart-dock), (100% - 30rem) / 2) calc(var(--ecart-dock) + env(safe-area-inset-bottom));
  z-index: 5;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  min-height: 3.65rem;
  padding: 0.4rem 0.4rem 0.4rem 1rem;
  background: var(--dock);
  -webkit-backdrop-filter: blur(16px) saturate(1.4);
  backdrop-filter: blur(16px) saturate(1.4);
  color: var(--encre);
  border: 1px solid var(--bord-dock);
  border-radius: 999px;
  box-shadow: var(--ombre-flottante);
}

.barre-selection[hidden] {
  display: none;
}

@media (prefers-reduced-motion: no-preference) {
  .barre-selection {
    animation: dock-remonte 0.22s cubic-bezier(0.2, 0.9, 0.3, 1.1);
  }
}

@keyframes dock-remonte {
  from {
    opacity: 0;
    transform: translateY(0.7rem);
  }
}

.barre-selection #compte-selection {
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.barre-selection-portee {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  min-width: 0;
}

/* Cible tactile de 44 px autour d'un carré de 1.2rem, portée par un `<label>`
   vide — les navigateurs ignorent le padding d'une case à l'apparence native.
   La marge négative annule le padding : la barre est la rangée la plus serrée
   de l'app et ne sait pas refluer. */
.case-tout-cible {
  display: flex;
  flex: none;
  padding: calc((44px - 1.2rem) / 2);
  margin: calc((1.2rem - 44px) / 2);
  cursor: pointer;
}

.case-tout {
  flex: none;
  width: 1.2rem;
  height: 1.2rem;
  margin: 0;
  accent-color: var(--accent);
  cursor: pointer;
}

.case-tout:disabled {
  opacity: 0.45;
  cursor: default;
}

.case-tout-cible:has(:disabled) {
  cursor: default;
}

.barre-selection-actions {
  display: flex;
  gap: 0.4rem;
}

.barre-selection button {
  min-height: 2.75rem;
  font-size: 1rem;
  font-weight: 600;
  background: var(--danger);
  color: var(--danger-encre);
  border: none;
  border-radius: 999px;
  padding: 0.45rem 1.05rem;
  cursor: pointer;
}

.barre-selection .annuler-selection {
  background: var(--surface-2);
  color: var(--encre);
}

.barre-selection button:disabled {
  opacity: 0.45;
  cursor: default;
}

/* ── En-tête de page (nouvelle plante, mes informations, administration) ─── */

.entete-plante {
  margin: 0.4rem 0 1rem;
}

.entete-plante .label {
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--doux);
}

.entete-plante h1 {
  margin: 0.1rem 0 0;
  font-size: 1.9rem;
  font-weight: 800;
  letter-spacing: -0.035em;
  line-height: 1.1;
}

/* ── Fiche plante ──────────────────────────────────────────────────────────── */

.carte-fiche {
  background: var(--surface);
  border-radius: var(--rayon-groupe);
  box-shadow: var(--ombre);
  padding: 1.1rem 1.1rem 1rem;
  margin: 0.4rem 0 0.9rem;
}

.entete-plante-haut {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 0.75rem;
}

/* « fiche » : une étiquette que le titre rend superflue. */
.carte-fiche .label {
  display: none;
}

.carte-fiche h1 {
  margin: 0;
  font-size: 1.75rem;
  font-weight: 800;
  letter-spacing: -0.035em;
  line-height: 1.1;
  overflow-wrap: anywhere;
}

.actions-fiche {
  flex: none;
  display: flex;
  gap: 0.4rem;
}

.bouton-editer-fiche,
.bouton-supprimer-fiche {
  flex: none;
  width: 2.5rem;
  height: 2.5rem;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--encre);
  background: var(--surface-2);
  border: 0;
  border-radius: 999px;
  padding: 0;
  cursor: pointer;
}

.bouton-editer-fiche svg,
.bouton-supprimer-fiche svg {
  width: 1.05rem;
  height: 1.05rem;
}

.bouton-supprimer-fiche {
  color: var(--danger);
}

/* Lecture seule : les contrôles d'écriture sont absents, pas grisés — un
   bouton grisé invite à chercher pourquoi il ne marche pas. */
.mention-lecture-seule {
  flex: none;
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--doux);
  background: var(--surface-2);
  border-radius: 999px;
  padding: 0.3rem 0.7rem;
  white-space: nowrap;
}

/* Le badge d'arrosage : même libellé que la ligne de la liste, qu'il constate
   sans rien rappeler. */
.etat-arrosage {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  margin-top: 0.65rem;
  font-size: 0.88rem;
  font-weight: 600;
  padding: 0.32rem 0.75rem 0.32rem 0.6rem;
  border-radius: 999px;
  background: var(--eau-clair);
  color: var(--eau);
}

.etat-arrosage::before {
  content: "";
  width: 0.8rem;
  height: 0.8rem;
  background: currentColor;
  -webkit-mask: var(--icone-goutte) center / contain no-repeat;
  mask: var(--icone-goutte) center / contain no-repeat;
}

/* Un constat, pas un second badge : ni fond ni couleur d'alerte — il ne
   signale rien (PLANT-72). À côté et non dedans, pour passer à la ligne seul. */
.frequence-arrosage {
  display: inline-block;
  margin: 0.65rem 0 0 0.6rem;
  font-size: 0.88rem;
  color: var(--doux);
}

.carte-fiche .photos,
.entree .photos {
  display: flex;
  flex-wrap: wrap;
  gap: 0.45rem;
}

.carte-fiche .photos {
  margin: 0.9rem 0 0.2rem;
}

.photo-wrap {
  position: relative;
  display: inline-block;
  line-height: 0;
}

.carte-fiche .photos img,
.entree .photos img {
  width: 4.75rem;
  height: 4.75rem;
  object-fit: cover;
  border-radius: 0.8rem;
  background: var(--surface-2);
  cursor: zoom-in;
}

/* La première photo de la fiche en grand : c'est le portrait de la plante. Le
   `>` la réserve à la vue — le formulaire d'édition porte aussi des `.photos`. */
.carte-fiche > .photos > .photo-wrap:first-child {
  flex-basis: 100%;
}

.carte-fiche > .photos > .photo-wrap:first-child img {
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 3;
  max-height: 24rem;
  border-radius: 1rem;
}

.photo-wrap .suppr-photo {
  position: absolute;
  top: -0.4rem;
  right: -0.4rem;
  width: 1.7rem;
  height: 1.7rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 999px;
  border: 2px solid var(--surface);
  background: var(--danger);
  color: var(--danger-encre);
  line-height: 1;
  padding: 0;
  cursor: pointer;
}

.photo-wrap .suppr-photo svg {
  width: 0.75rem;
  height: 0.75rem;
}

.fiche-contenu {
  margin-top: 1rem;
}

.fiche-contenu[hidden] {
  display: none;
}

/* Les trois sections d'une fiche (PLANT-109). Une section vide n'est pas
   masquée, elle n'est **pas rendue** : rien à protéger par un `[hidden]`. */
.section-fiche + .section-fiche {
  margin-top: 0.95rem;
  padding-top: 0.95rem;
  border-top: 1px solid var(--filet);
}

.section-fiche h2 {
  margin: 0 0 0.3rem;
  font-size: 0.85rem;
  font-weight: 700;
  color: var(--doux);
}

.section-fiche p {
  margin: 0.3rem 0 0;
}

.section-fiche > :last-child {
  margin-bottom: 0;
}

/* Une consigne par ligne se relit d'un coup d'œil, là où une prose demande de
   chercher. */
.consignes {
  margin: 0;
  padding-left: 1.15rem;
}

.consignes li {
  margin: 0.2rem 0;
}

.consignes li::marker {
  color: var(--accent);
}

/* Mention de saisie (PLANT-46) : qui a écrit, et quand. Une note de bas de
   bloc, la même sur la fiche et sur chaque entrée de journal. */
.signature {
  margin: 1rem 0 0;
  font-size: 0.8rem;
  line-height: 1.55;
  color: var(--doux);
}

.entree .signature {
  margin-top: 0.5rem;
}

.signature-sep {
  margin: 0 0.4rem;
  opacity: 0.55;
}

.signature-auteur {
  color: var(--encre);
  font-weight: 600;
}

.signature-avatar {
  margin-right: 0.2rem;
}

/* ── Journal : ajout et arrosages repliés ──────────────────────────────────── */

details.ajout,
details.arrosages {
  margin: 0 0 0.9rem;
  background: var(--surface);
  border-radius: var(--rayon-groupe);
  box-shadow: var(--ombre);
}

details.ajout > summary,
details.arrosages > summary {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  min-height: 3.4rem;
  padding: 0.6rem 1rem;
  font-size: 1rem;
  font-weight: 600;
  list-style: none;
  cursor: pointer;
  border-radius: var(--rayon-groupe);
}

details.ajout > summary::-webkit-details-marker,
details.arrosages > summary::-webkit-details-marker {
  display: none;
}

/* La pastille de chaque bloc, l'icône évidée dans le disque : un « + » pour ce
   qui crée, une goutte pour ce qui révèle des arrosages déjà là. */
details.ajout > summary::before,
details.arrosages > summary::before {
  content: "";
  flex: none;
  width: 2rem;
  height: 2rem;
  border-radius: 999px;
  background: var(--accent);
  -webkit-mask: var(--icone-plus) center / 1rem no-repeat, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: var(--icone-plus) center / 1rem no-repeat, linear-gradient(#000 0 0);
  mask-composite: exclude;
}

details.arrosages > summary::before {
  background: var(--eau);
  -webkit-mask-image: var(--icone-goutte), linear-gradient(#000 0 0);
  mask-image: var(--icone-goutte), linear-gradient(#000 0 0);
  -webkit-mask-size: 0.95rem, auto;
  mask-size: 0.95rem, auto;
}

/* Le chevron de droite dit que le bloc se déplie. */
details.ajout > summary::after,
details.arrosages > summary::after {
  content: "";
  flex: none;
  width: 1rem;
  height: 1rem;
  margin-left: auto;
  background: var(--doux);
  -webkit-mask: var(--icone-chevron) center / contain no-repeat;
  mask: var(--icone-chevron) center / contain no-repeat;
}

details[open].ajout > summary::after,
details[open].arrosages > summary::after {
  transform: rotate(90deg);
}

@media (prefers-reduced-motion: no-preference) {
  details.ajout > summary::after,
  details.arrosages > summary::after {
    transition: transform 0.2s ease;
  }
}

/* L'insertion horizontale passe par la **marge** : le padding de `.timeline`
   place la pastille de chaque entrée, il ne se retouche pas ici. */
.timeline-arrosages {
  margin: 0 1rem 0.4rem;
}

/* ── Formulaires de la fiche ───────────────────────────────────────────────── */

.form-journal {
  padding: 0 1rem 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.65rem;
}

.form-journal[hidden] {
  display: none;
}

.form-editer-fiche {
  padding: 1rem 0 0;
}

.form-journal select,
.form-journal textarea,
.form-journal input[type="text"],
.form-journal input[type="date"] {
  width: 100%;
  font-size: 1rem;
  padding: 0.7rem 0.85rem;
  border: 0;
  border-radius: var(--rayon-champ);
  background: var(--surface-2);
  color: var(--encre);
}

.form-journal textarea {
  resize: vertical;
  min-height: 5rem;
}

/* Aucune n'est scopée sous `.form-journal` : les trois champs existent aussi
   dans le formulaire d'édition d'une entrée, qui ne porte pas cette classe, et
   le champ photo dans celui de création. Une classe de champ partagée porte son
   allure toute seule ; `tests-front/style.test.js` le tient. */
.champ-texte,
.champ-photo,
.champ-date {
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--doux);
}

/* Le champ enveloppé passe sous son libellé. */
.champ-texte input,
.champ-texte textarea,
.champ-photo input {
  display: block;
  margin-top: 0.35rem;
}

/* `plante.js` masque le champ photo et le texte quand « arrosage » est
   sélectionné (PLANT-113). Garde-fous posés à la main : `style.test.js` ne voit
   pas ce que masque le seul JS. */
.champ-photo[hidden] {
  display: none;
}

.form-journal textarea[hidden],
.form-edit-entree textarea[hidden] {
  display: none;
}

/* Une date n'a pas besoin de toute la largeur : le champ natif est dimensionné
   par son format. */
.champ-date input[type="date"] {
  width: auto;
  max-width: 12rem;
  margin-left: 0.5rem;
  padding: 0.5rem 0.7rem;
  font-size: 0.95rem;
  vertical-align: middle;
  border: 0;
  border-radius: var(--rayon-champ);
  background: var(--surface-2);
  color: var(--encre);
}

input[type="file"] {
  max-width: 100%;
  color: var(--doux);
}

input[type="file"]::file-selector-button {
  margin-right: 0.7rem;
  padding: 0.5rem 0.9rem;
  font: inherit;
  font-weight: 600;
  color: var(--encre);
  background: var(--surface-2);
  border: 0;
  border-radius: 999px;
  cursor: pointer;
}

.form-journal button {
  align-self: flex-start;
  font-size: 1rem;
  font-weight: 600;
  background: var(--accent);
  color: var(--accent-encre);
  border: none;
  border-radius: 999px;
  padding: 0.7rem 1.3rem;
  cursor: pointer;
}

.form-journal button:disabled {
  opacity: 0.6;
}

/* ── Timeline du journal ───────────────────────────────────────────────────── */

.timeline {
  background: var(--surface);
  border-radius: var(--rayon-groupe);
  box-shadow: var(--ombre);
  padding: 0.2rem 1rem;
}

/* Dans le bloc des arrosages, la timeline est déjà dans une carte. */
.timeline.timeline-arrosages {
  background: none;
  box-shadow: none;
  padding: 0;
  border-radius: 0;
  border-top: 1px solid var(--filet);
}

.entree {
  position: relative;
  padding: 0.85rem 0 0.9rem 1.1rem;
}

.entree + .entree {
  border-top: 1px solid var(--filet);
}

/* La pastille de type : une couleur par geste. */
.entree::before {
  content: "";
  position: absolute;
  left: 0;
  top: 1.7rem;
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 999px;
  background: var(--doux);
}

.entree.arrosage::before {
  background: var(--eau);
}

.entree.observation::before {
  background: var(--doux);
}

.entree.rempotage::before {
  background: var(--accent);
}

.entree.taille::before {
  background: var(--taille);
}

.entree .date-type {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  min-height: 2.25rem;
  font-size: 0.88rem;
  color: var(--doux);
}

.entree .date-type strong {
  color: var(--encre);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.lien-modifier,
.lien-supprimer {
  width: 2.25rem;
  height: 2.25rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
  background: none;
  border: none;
  border-radius: 999px;
  padding: 0;
  cursor: pointer;
}

.lien-modifier:active,
.lien-supprimer:active {
  background: var(--surface-2);
}

.lien-modifier {
  margin-left: auto;
  color: var(--doux);
}

.lien-supprimer {
  color: var(--danger);
}

.entree p {
  margin: 0.15rem 0 0;
}

.entree .photos {
  margin-top: 0.55rem;
}

.timeline > .vide {
  margin: 0;
  padding: 1.5rem 0.5rem;
}

.form-edit-entree {
  margin-top: 0.5rem;
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
}

.form-edit-entree[hidden] {
  display: none;
}

.form-edit-entree select,
.form-edit-entree textarea {
  width: 100%;
  font-size: 0.95rem;
  padding: 0.6rem 0.75rem;
  border: 0;
  border-radius: var(--rayon-champ);
  background: var(--surface-2);
  color: var(--encre);
}

.form-edit-entree textarea {
  min-height: 3.5rem;
  resize: vertical;
}

/* Le champ date et le formulaire d'édition d'une entrée sont plus compacts sur
   un écran large, un choix. Sous 16 px iOS zoome à la prise de focus, donc la
   taille est reposée là où il l'impose — au même sélecteur, et **après** les
   règles reprises : une media query n'ajoute aucune spécificité, et remonter
   ce bloc en tête du fichier le désactiverait. `style.test.js` tient les deux. */
@media (max-width: 639px) {
  .champ-date input[type="date"],
  .form-edit-entree select,
  .form-edit-entree textarea {
    font-size: 1rem;
  }
}

.actions-edit {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

/* `display: flex` bat le `[hidden]` du navigateur : sans ce garde-fou, « retirer
   mon code » restait visible sur un utilisateur sans code. */
.actions-edit[hidden] {
  display: none;
}

.actions-edit button {
  font-size: 0.98rem;
  font-weight: 600;
  border: 0;
  border-radius: 999px;
  padding: 0.62rem 1.15rem;
  cursor: pointer;
}

.actions-edit button[type="submit"] {
  background: var(--accent);
  color: var(--accent-encre);
}

.actions-edit .annuler-edit {
  background: var(--surface-2);
  color: var(--encre);
}

/* ── Visionneuse de photo ──────────────────────────────────────────────────────
   Au-dessus des deux barres, qui portent `z-index: 5`. */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2rem 1rem;
  background: rgb(6 10 8 / 92%);
}

.lightbox[hidden] {
  display: none;
}

.lightbox img {
  max-width: 100%;
  max-height: 100%;
  border-radius: 1rem;
}

@media (prefers-reduced-motion: no-preference) {
  .lightbox {
    animation: fondu 0.2s ease-out;
  }

  .lightbox img {
    animation: agrandit 0.25s cubic-bezier(0.2, 0.9, 0.3, 1.1);
  }
}

@keyframes fondu {
  from {
    opacity: 0;
  }
}

@keyframes agrandit {
  from {
    transform: scale(0.94);
  }
}

.lightbox-fermer {
  position: fixed;
  top: calc(0.9rem + env(safe-area-inset-top));
  right: 0.9rem;
  width: 2.75rem;
  height: 2.75rem;
  display: grid;
  place-items: center;
  padding: 0;
  background: rgb(255 255 255 / 14%);
  border: none;
  border-radius: 999px;
  color: #fff;
  cursor: pointer;
}

/* ── Formulaires de page : nouvelle plante, mes informations ───────────────── */

.form-plante {
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
  margin: 0 0 1rem;
  padding: 1.1rem;
  background: var(--surface);
  border-radius: var(--rayon-groupe);
  box-shadow: var(--ombre);
}

/* `.label-champ` est un `<span>` et non un `<label>` : le bloc d'entretien est
   une liste de champs, il n'y a pas un contrôle unique à désigner. Même
   allure, donc même déclaration. */
.form-plante label,
.form-plante .label-champ {
  margin-top: 0.45rem;
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--doux);
}

.form-plante > label:first-child {
  margin-top: 0;
}

.form-plante input,
.form-plante textarea {
  width: 100%;
  font-size: 1rem;
  padding: 0.72rem 0.85rem;
  border: 0;
  border-radius: var(--rayon-champ);
  background: var(--surface-2);
  color: var(--encre);
}

.form-plante textarea {
  min-height: 6rem;
  resize: vertical;
}

/* Le cadre des champs de saisie encadrerait le bouton natif « Choisir un
   fichier » et une grande zone vide : on le défait pour ce champ-là. */
.form-plante input[type="file"] {
  padding: 0;
  background: none;
  border-radius: 0;
}

.form-plante button {
  align-self: stretch;
  margin-top: 0.8rem;
  font-size: 1rem;
  font-weight: 600;
  background: var(--accent);
  color: var(--accent-encre);
  border: none;
  border-radius: 999px;
  padding: 0.8rem 1.2rem;
  cursor: pointer;
}

.form-plante button:disabled {
  opacity: 0.6;
}

/* Le sélecteur doit être **descendant** : `.form-plante input` (0,1,1) bat une
   classe nue et imposerait son `font-size: 1rem`, l'emoji resterait petit. */
.form-plante .champ-avatar-grand {
  width: 4.5rem;
  font-size: 1.6rem;
  text-align: center;
  padding: 0.4rem;
}

/* ── Saisie des consignes d'entretien (PLANT-109) ──────────────────────────────
   Portée par les **deux** formulaires de fiche, donc à classe nue. Les deux
   boutons s'écrivent `button.…` et viennent **après** `.form-plante button` /
   `.form-journal button` : à spécificité égale (0,1,1), c'est l'ordre qui les
   garde d'être habillés en bouton d'envoi. */
.liste-consignes {
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
}

.consigne {
  display: flex;
  align-items: center;
  gap: 0.45rem;
}

/* Sans `min-width: 0`, un `input` en flex refuse de descendre sous sa largeur
   intrinsèque et pousse la croix hors de l'écran. */
.consigne input[type="text"] {
  flex: 1;
  min-width: 0;
}

/* `padding`, `margin` et `align-self` sont défaits explicitement : la cascade
   s'applique propriété par propriété, et le padding du bouton d'envoi ne
   laisserait aucune place à l'icône dans 2.6 rem. */
button.retirer-consigne {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.6rem;
  height: 2.6rem;
  padding: 0;
  margin: 0;
  align-self: center;
  line-height: 1;
  border: 0;
  border-radius: var(--rayon-champ);
  background: var(--surface-2);
  color: var(--doux);
  cursor: pointer;
}

/* Une commande secondaire : elle ajoute une ligne, elle n'enregistre rien. */
button.ajouter-consigne {
  align-self: flex-start;
  margin-top: 0.2rem;
  font-size: 0.95rem;
  font-weight: 600;
  background: none;
  color: var(--accent);
  border: 0;
  border-radius: 999px;
  padding: 0.45rem 0.2rem;
  cursor: pointer;
}

/* ── Écran de connexion ────────────────────────────────────────────────────── */

.ecran-utilisateurs {
  padding-top: 1rem;
}

.ecran-utilisateurs h1 {
  margin: 0.5rem 0 1.1rem;
  font-size: 2.1rem;
  font-weight: 800;
  letter-spacing: -0.04em;
  line-height: 1.05;
}

/* Avatars en emoji plutôt qu'en images téléversées : pas de stockage, pas de
   redimensionnement, pas de surface de risque. */
.avatar-utilisateur {
  font-size: 1.1rem;
  line-height: 1;
}

.form-code {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  margin: 0;
  padding: 1.1rem;
  background: var(--surface);
  border-radius: var(--rayon-groupe);
  box-shadow: var(--ombre);
}

/* Ce n'était pas cosmétique : le formulaire est basculé par `utilisateurs.js`,
   et `display: flex` le rendait visible avant que l'instance soit connue. */
.form-code[hidden] {
  display: none;
}

.form-code label {
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--doux);
}

.form-code input + label {
  margin-top: 0.35rem;
}

/* Au moins 1 rem, et posé : sans taille choisie ces champs prenaient les ~13 px
   du navigateur, et ce sont les premiers du parcours sur téléphone. */
.form-code input {
  font-size: 1.1rem;
  font-weight: 600;
  padding: 0.6rem 0.8rem;
  color: var(--encre);
  background: var(--surface-2);
  border: 0;
  border-radius: var(--rayon-champ);
}

/* Le code : six chiffres espacés et centrés, à chasse fixe. */
.form-code input[type="password"] {
  font-size: 1.5rem;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.5em;
  text-align: center;
  padding: 0.55rem 0 0.55rem 0.5em;
}

.ecran-utilisateurs > .erreur {
  margin-top: 1rem;
}

/* ── Mes informations : le code d'accès ────────────────────────────────────── */

.bloc-code {
  margin-top: 1.75rem;
}

.bloc-admin h2,
.bloc-code h2 {
  margin: 0 0 0.4rem;
  font-size: 1.2rem;
  font-weight: 800;
  letter-spacing: -0.02em;
}

/* ── Administration ────────────────────────────────────────────────────────── */

/* Plus large que les autres pages sur un grand écran : c'est là qu'on lit la
   matrice en tableau, et 640 px la faisaient défiler dès deux jardins. */
main.page-admin {
  max-width: 60rem;
}

.bloc-admin {
  margin: 1.5rem 0;
}

/* L'échelle des droits, en marches : l'indicateur de chaque ligne remplit
   autant de marches que le niveau en contient, et dit sans phrase que chaque
   niveau contient le précédent. */
.echelle p {
  margin: 0 0 0.75rem;
}

.echelle-droits {
  list-style: none;
  margin: 0 0 1rem;
  padding: 0.35rem 1rem;
  background: var(--surface);
  border-radius: var(--rayon-groupe);
  box-shadow: var(--ombre);
}

.echelle-droits li {
  display: grid;
  grid-template-columns: 1.5rem minmax(0, 9rem) 1fr;
  align-items: center;
  gap: 0.2rem 0.75rem;
  padding: 0.6rem 0;
  color: var(--doux);
}

.echelle-droits li + li {
  border-top: 1px solid var(--filet);
}

.echelle-droits li::before {
  content: "";
  width: 1.5rem;
  height: 1.1rem;
  background: linear-gradient(to right, var(--accent) calc(var(--marches) * 20%), var(--filet) 0);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 16'%3E%3Crect y='12' width='3' height='4' rx='1'/%3E%3Crect x='4.25' y='9' width='3' height='7' rx='1'/%3E%3Crect x='8.5' y='6' width='3' height='10' rx='1'/%3E%3Crect x='12.75' y='3' width='3' height='13' rx='1'/%3E%3Crect x='17' width='3' height='16' rx='1'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 16'%3E%3Crect y='12' width='3' height='4' rx='1'/%3E%3Crect x='4.25' y='9' width='3' height='7' rx='1'/%3E%3Crect x='8.5' y='6' width='3' height='10' rx='1'/%3E%3Crect x='12.75' y='3' width='3' height='13' rx='1'/%3E%3Crect x='17' width='3' height='16' rx='1'/%3E%3C/svg%3E") center / contain no-repeat;
}

.echelle-droits li:nth-child(1) {
  --marches: 1;
}

.echelle-droits li:nth-child(2) {
  --marches: 2;
}

.echelle-droits li:nth-child(3) {
  --marches: 3;
}

.echelle-droits li:nth-child(4) {
  --marches: 4;
}

.echelle-droits li:nth-child(5) {
  --marches: 5;
}

.echelle-droits .niveau {
  font-weight: 700;
  color: var(--encre);
}

@media (max-width: 639px) {
  .echelle-droits li {
    grid-template-columns: 1.5rem 1fr;
  }

  .echelle-droits .effet {
    grid-column: 2;
  }
}

.note-delegation {
  margin-top: 0.9rem;
}

/* Ajouter un utilisateur et les jardins, côte à côte là où la place le
   permet : ce sont deux petits blocs, les empiler sur un grand écran laissait
   une colonne de vide à droite. */
.admin-colonnes {
  display: grid;
  gap: 0 1.5rem;
  align-items: start;
}

@media (min-width: 860px) {
  .admin-colonnes {
    grid-template-columns: 1fr 1fr;
  }
}

/* La matrice peut être large (un jardin par colonne) : elle défile
   horizontalement dans son conteneur, la page jamais. */
.matrice-enveloppe {
  overflow-x: auto;
  background: var(--surface);
  border-radius: var(--rayon-groupe);
  box-shadow: var(--ombre);
}

.matrice {
  border-collapse: collapse;
  width: 100%;
  font-size: 0.92rem;
}

.matrice th,
.matrice td {
  padding: 0.65rem 0.9rem;
  text-align: left;
  border-bottom: 1px solid var(--filet);
  white-space: nowrap;
}

.matrice tbody tr:last-child th,
.matrice tbody tr:last-child td {
  border-bottom: none;
}

/* Le nom du jardin, et rien d'autre : son compte de plantes et son `✕` sont
   dans `.liste-jardins` (PLANT-79). */
.matrice thead th {
  font-weight: 600;
  font-size: 0.82rem;
  color: var(--doux);
  vertical-align: bottom;
}

.matrice tbody th {
  font-weight: 400;
}

.matrice caption.vide {
  padding: 1.5rem 1rem;
}

/* Une ligne d'utilisateur : qui, puis ce qu'on peut lui faire, rangé à droite. */
.ligne-utilisateur {
  display: flex;
  align-items: center;
  gap: 0.4rem 0.75rem;
}

.identite-ligne {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-width: 0;
}

.matrice .avatar-utilisateur {
  flex: none;
  display: grid;
  place-items: center;
  width: 2.1rem;
  height: 2.1rem;
  font-size: 1.1rem;
  border-radius: 999px;
  background: var(--surface-2);
}

.nom-ligne {
  font-weight: 650;
  color: var(--encre);
}

/* L'identifiant de connexion, à côté du nom et plus discret que lui : c'est
   une donnée à transmettre, pas ce que l'app affiche. */
.identifiant-ligne {
  font-size: 0.85rem;
  color: var(--doux);
  overflow-wrap: anywhere;
}

.commandes-ligne {
  display: flex;
  align-items: center;
  gap: 0.1rem;
  margin-left: auto;
}

/* Le seul endroit où un jardin porte son compte et son geste de suppression, à
   toutes les largeurs (PLANT-79). */
.liste-jardins {
  list-style: none;
  margin: 0 0 0.8rem;
  padding: 0.2rem 1rem;
  background: var(--surface);
  border-radius: var(--rayon-groupe);
  box-shadow: var(--ombre);
}

.liste-jardins:empty {
  display: none;
}

.liste-jardins li {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  min-height: 3.1rem;
  padding: 0.35rem 0;
}

.liste-jardins li + li {
  border-top: 1px solid var(--filet);
}

/* Un nom de jardin monte à 80 caractères : il prend la place qui reste et se
   tronque, plutôt que de pousser le compte et le `✕` hors de la ligne. */
.liste-jardins .nom-jardin {
  flex: 1;
  min-width: 0;
  font-weight: 650;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.liste-jardins .compte-jardin {
  font-size: 0.88rem;
  color: var(--doux);
  font-variant-numeric: tabular-nums;
}

.liste-jardins .suppr-jardin {
  margin-left: 0;
}

.etiquette-admin {
  font-size: 0.72rem;
  font-weight: 700;
  color: var(--accent);
  background: var(--accent-clair);
  border-radius: 999px;
  padding: 0.15rem 0.55rem;
  margin-left: 0.2rem;
  white-space: nowrap;
}

/* Promouvoir et rétrograder, en mots : « + admin » désigne déjà un jardin, et
   deux boutons de même étiquette pour deux périmètres se confondraient. */
.promouvoir,
.retrograder,
.nommer-admin,
.revoquer-admin {
  font-size: 0.75rem;
  font-weight: 600;
  background: none;
  border: 1px solid var(--filet);
  border-radius: 999px;
  padding: 0.2rem 0.6rem;
  margin-left: 0.35rem;
  cursor: pointer;
  color: var(--doux);
  white-space: nowrap;
}

.promouvoir:hover,
.nommer-admin:hover {
  color: var(--accent);
  border-color: var(--accent);
}

.revoquer-admin {
  color: var(--accent);
  background: var(--accent-clair);
  border-color: transparent;
}

.retrograder:hover,
.revoquer-admin:hover {
  color: var(--danger);
  border-color: var(--danger);
}

/* Sans bordure, contrairement à `.etiquette-admin` qu'elle suit : une précision
   sur l'étiquette, pas une seconde étiquette. */
.note-perimetre {
  font-size: 0.75rem;
  color: var(--doux);
  margin-left: 0.2rem;
  white-space: nowrap;
}

/* Même allure que `.note-perimetre`, en couleur de refus : cet utilisateur ne
   peut pas entrer tant qu'on ne lui a pas donné de code (PLANT-131). C'est ce
   que l'administrateur vient chercher. Une classe à elle, parce qu'elle ne dit
   rien du périmètre. */
.note-sans-code {
  font-size: 0.75rem;
  color: var(--danger);
  margin-left: 0.2rem;
  white-space: nowrap;
}

/* Le droit d'une cellule. Accordé, il se voit en couleur ; « aucun accès »
   s'efface : la matrice se lit d'un coup d'œil, sans lire chaque libellé. */
.choix-droit {
  appearance: none;
  -webkit-appearance: none;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--accent);
  background: var(--accent-clair)
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%238a948d' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E")
    no-repeat right 0.55rem center / 0.8rem;
  border: 0;
  border-radius: 999px;
  padding: 0.35rem 1.8rem 0.35rem 0.8rem;
  cursor: pointer;
}

.choix-droit:has(option[value=""]:checked) {
  color: var(--doux);
  background-color: transparent;
  box-shadow: inset 0 0 0 1px var(--filet);
}

.suppr-utilisateur,
.suppr-jardin,
.editer-utilisateur,
.definir-code {
  background: none;
  border: none;
  border-radius: 999px;
  color: var(--doux);
  cursor: pointer;
  font-size: 0.8rem;
  padding: 0.1rem 0.3rem;
  margin-left: 0.2rem;
}

.commande-icone {
  display: inline-grid;
  place-items: center;
  min-width: 2.1rem;
  min-height: 2.1rem;
  vertical-align: middle;
}

.commande-icone svg {
  width: 1.1em;
  height: 1.1em;
}

.commande-icone:active {
  background: var(--surface-2);
}

@media (hover: hover) {
  .commande-icone:hover {
    background: var(--surface-2);
  }
}

.suppr-utilisateur:hover,
.suppr-jardin:hover {
  color: var(--danger);
}

/* Vert et non rouge : modifier n'est pas détruire, et les deux boutons sont
   voisins dans la même cellule. Poser un code non plus. */
.editer-utilisateur:hover,
.definir-code:hover {
  color: var(--accent);
}

/* Le formulaire remplace le contenu de la cellule pendant l'édition. */
.edition-utilisateur {
  display: flex;
  align-items: center;
  gap: 0.3rem;
}

.edition-utilisateur input {
  font: inherit;
  color: var(--encre);
  background: var(--surface-2);
  border: 0;
  border-radius: 0.6rem;
  padding: 0.35rem 0.5rem;
  min-width: 0;
}

.edition-utilisateur input[name="nom"],
.edition-utilisateur .champ-identifiant {
  flex: 1 1 7rem;
}

.edition-utilisateur .champ-avatar {
  flex: 0 0 2.8rem;
  text-align: center;
}

.edition-utilisateur .champ-code-admin {
  flex: 1 1 5.5rem;
  letter-spacing: 0.15em;
}

/* Ce que le ✓ va faire, sous le champ : on ne confirme pas, on prévient. */
.aide-code-admin {
  display: block;
  margin-top: 0.25rem;
  white-space: normal;
  font-size: 0.75rem;
  color: var(--doux);
}

.edition-utilisateur button {
  background: none;
  border: none;
  border-radius: 999px;
  color: var(--doux);
  cursor: pointer;
  font-size: 0.85rem;
  padding: 0 0.2rem;
}

.edition-utilisateur button[type="submit"] {
  color: var(--accent);
}

.edition-utilisateur .annuler-edition:hover {
  color: var(--danger);
}

/* Le refus s'affiche sous le formulaire, dans la cellule : il concerne cette
   ligne-là. `white-space: normal` annule le `nowrap` de la matrice, sinon un
   message long étirerait la colonne. */
.refus-edition {
  display: block;
  margin-top: 0.25rem;
  max-width: 16rem;
  white-space: normal;
  font-size: 0.8rem;
  line-height: 1.3;
}

/* ── La matrice se déplie en cartes sur téléphone (PLANT-74) ─────────────────
   Sous 640 px, un jardin par colonne est intapable. La même matrice se relit en
   **une carte par utilisateur** ; à partir de 640 px le tableau revient tel
   quel. Tout est en CSS, à un attribut près : `thead` disparaît, donc le
   libellé de colonne est reporté en `::before` depuis le `data-nom-jardin` du
   `<td>`. */
@media (max-width: 639px) {
  .matrice-enveloppe {
    overflow: visible;
    background: none;
    box-shadow: none;
    border-radius: 0;
  }

  /* 1 rem : sous 16 px iOS zoome à la prise de focus. Les champs d'édition sont
     en `font: inherit` et suivent ; le `<select>` des droits porte sa propre
     taille, il est repris plus bas. */
  .matrice {
    display: block;
    font-size: 1rem;
  }

  .matrice thead {
    display: none;
  }

  .matrice caption {
    display: block;
  }

  .matrice tbody {
    display: block;
  }

  .matrice tbody tr {
    display: block;
    border-radius: var(--rayon-groupe);
    background: var(--surface);
    box-shadow: var(--ombre);
    margin-bottom: 0.6rem;
  }

  .matrice tbody tr:last-child {
    margin-bottom: 0;
  }

  .matrice th,
  .matrice td {
    white-space: normal;
  }

  /* L'en-tête de carte : la ligne d'utilisateur, ou son formulaire d'édition
     quand on le modifie. */
  .matrice tbody th {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem;
    padding: 0.75rem 0.75rem 0.75rem 1rem;
  }

  /* Identité et commandes sur une ligne tant qu'elles tiennent ; les commandes
     passent dessous, à droite, sinon. */
  .matrice .ligne-utilisateur {
    flex: 1 1 100%;
    flex-wrap: wrap;
  }

  /* Une ligne de jardin : son nom, son `<select>`, son « + admin ». */
  .matrice tbody td {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    padding: 0.55rem 0.75rem 0.55rem 1rem;
  }

  .matrice tbody td::before {
    content: attr(data-nom-jardin);
    flex: 1 1 auto;
    min-width: 0;
    color: var(--encre);
  }

  /* Les séparateurs sont ceux de la **carte**, alors que le tableau les retire
     sur sa dernière ligne : sans ces deux règles, la dernière carte perdrait
     ses lignes internes et chacune porterait un trait sous la dernière. */
  .matrice tbody tr:last-child th,
  .matrice tbody tr:last-child td:not(:last-child) {
    border-bottom: 1px solid var(--filet);
  }

  .matrice tbody tr td:last-child {
    border-bottom: none;
  }

  /* Le `gap` du conteneur remplace les marges de l'affichage en ligne. */
  .matrice .etiquette-admin,
  .matrice .note-perimetre,
  .matrice .note-sans-code,
  .matrice .promouvoir,
  .matrice .retrograder,
  .matrice .nommer-admin,
  .matrice .revoquer-admin,
  .matrice .suppr-utilisateur,
  .matrice .editer-utilisateur,
  .matrice .definir-code {
    margin-left: 0;
  }

  /* Cibles tactiles de 44 px. Dans la requête et pas au-dessus : sur écran
     large, elles déformeraient le tableau, qui est la bonne forme là-bas. */
  .matrice .choix-droit,
  .matrice .promouvoir,
  .matrice .retrograder,
  .matrice .nommer-admin,
  .matrice .revoquer-admin,
  .matrice .edition-utilisateur input {
    min-height: 44px;
  }

  .matrice .etiquette-admin,
  .matrice .note-perimetre,
  .matrice .note-sans-code,
  .matrice .promouvoir,
  .matrice .retrograder,
  .matrice .nommer-admin,
  .matrice .revoquer-admin {
    font-size: 0.82rem;
  }

  .matrice .promouvoir,
  .matrice .retrograder,
  .matrice .nommer-admin,
  .matrice .revoquer-admin {
    padding: 0.05rem 0.85rem;
  }

  /* Les commandes en icône, où qu'elles vivent : 44 px dans les deux sens.
     `button.commande-icone` et non une classe nue : `.edition-utilisateur
     button` (0,1,1) battrait (0,1,0) et laisserait les ✓/✕ du formulaire
     d'édition à leur taille d'écran large. */
  button.commande-icone {
    min-height: 44px;
    min-width: 44px;
    font-size: 1.1rem;
  }

  /* Le `<select>` porte sa taille au niveau de base (0.85rem), qui bat
     l'héritage : il est donc le seul à devoir être repris ici. */
  .matrice .choix-droit {
    flex: 0 1 12rem;
    font-size: 1rem;
  }

  .matrice .edition-utilisateur,
  .matrice .aide-code-admin,
  .matrice .refus-edition {
    flex: 1 1 100%;
  }

  .matrice .edition-utilisateur {
    flex-wrap: wrap;
  }

  .refus-edition {
    max-width: none;
  }
}

/* Les deux formulaires d'ajout, chacun dans sa carte. */
.form-inline {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
}

.carte-admin {
  padding: 0.55rem;
  background: var(--surface);
  border-radius: var(--rayon-groupe);
  box-shadow: var(--ombre);
}

/* 1 rem à toute largeur : sans taille posée ces champs prenaient les ~13 px du
   navigateur, sous les 16 px à partir desquels iOS zoome. */
.form-inline input {
  flex: 1 1 8rem;
  min-width: 0;
  font-size: 1rem;
  padding: 0.7rem 0.85rem;
  color: var(--encre);
  background: var(--surface-2);
  border: 0;
  border-radius: var(--rayon-champ);
}

.form-inline .champ-avatar {
  flex: 0 0 3.2rem;
  text-align: center;
}

.form-inline .champ-code-admin {
  flex: 0 1 7rem;
  letter-spacing: 0.15em;
}

.form-inline button {
  font-size: 1rem;
  font-weight: 600;
  background: var(--accent);
  color: var(--accent-encre);
  border: 0;
  border-radius: 999px;
  padding: 0.7rem 1.2rem;
  cursor: pointer;
}

/* « générer » propose un code, il n'envoie rien : bouton secondaire, pour qu'on
   ne le confonde pas avec « Ajouter » à côté. À (0,2,0), il bat
   `.form-inline button` (0,1,1). */
.form-inline .generer-code {
  background: var(--surface-2);
  color: var(--encre);
  font-weight: 500;
}

/* Le refus d'une opération, en bandeau au-dessus du dock dès qu'il porte un
   message : la matrice est longue, et un refus écrit en bas de page tombait
   hors de l'écran de qui venait de toucher un `<select>` en haut. Vide, le
   bandeau reste dans le flux et n'y occupe rien : masquer le conteneur sortirait
   l'alerte de l'arbre d'accessibilité, et son premier message ne serait pas lu. */
.bandeau-erreur:has(#erreur-admin:not(:empty)) {
  position: fixed;
  left: 50%;
  bottom: calc(var(--hauteur-barre-actions) + env(safe-area-inset-bottom) + 0.4rem);
  z-index: 6;
  display: flex;
  align-items: center;
  gap: 0.4rem;
  width: max-content;
  max-width: min(26rem, calc(100% - 2rem));
  padding: 0.3rem 0.3rem 0.3rem 1rem;
  border-radius: 1rem;
  background: var(--danger-clair);
  color: var(--danger);
  box-shadow: var(--ombre-flottante);
  transform: translateX(-50%);
}

.fermer-bandeau {
  display: none;
  flex: none;
  place-items: center;
  width: 2.75rem;
  height: 2.75rem;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: none;
  color: inherit;
  cursor: pointer;
}

.bandeau-erreur:has(#erreur-admin:not(:empty)) .fermer-bandeau {
  display: grid;
}

@media (prefers-reduced-motion: no-preference) {
  .bandeau-erreur:has(#erreur-admin:not(:empty)) {
    animation: bandeau-arrive 0.22s ease-out;
  }
}

@keyframes bandeau-arrive {
  from {
    opacity: 0;
    transform: translate(-50%, 0.5rem);
  }
}
