/* Police manuscrite du logo. Le fichier doit être placé à assets/font/segoe-script.ttf
   (voir CLAUDE.md) — tant qu'il n'y est pas, --font-script retombe sur des polices
   cursives système. */
@font-face {
  font-family: "Segoe Script Idiaa";
  src: url("../font/segoe-script.ttf") format("truetype");
  font-weight: normal;
  font-style: normal;
  font-display: swap;
}

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  padding: 0;
  font-family: var(--font-system);
  /* Couleur seule ici — la photo (--bg-image, "none" en clair/sombre, voir
     variables.css) vit dans body::before ci-dessous, pas directement sur ce
     background-color/-image : une image ne peut pas être transitionnée en douceur via
     `transition: background-image` (aucun navigateur n'interpole un changement d'url()),
     donc le fondu à l'apparition demandé explicitement (voir body::before) a besoin
     d'une couche à part, avec sa propre opacité animable. */
  background-color: var(--bg);
  color: var(--text);
}

/* Photo de fond (mode Îdiää) : couche séparée en position:fixed, fondu d'apparition en
   CSS pur (@keyframes, pas de JS). Vide en clair/sombre (--bg-image: none) : rien à
   afficher ni animer, sans coût perceptible.
   position:fixed remplace ici l'ancien `background-attachment: fixed` posé directement
   sur html/body (ESSAI noté aux Points ouverts, CLAUDE.md, jugé peu fiable sur iOS
   Safari en PWA) : même effet (fond immobile au défilement) obtenu autrement.
   z-index:0, PAS négatif — piège rencontré (signalé par l'utilisateur, "fond blanc
   partout sauf login.php") : position:fixed + z-index négatif se peignait derrière le
   fond de page (canvas) lui-même dans son navigateur, invisible malgré des styles
   calculés entièrement corrects (opacity:1, bonne image, bonne taille — vérifié pas à
   pas via DevTools avant de trouver la cause). z-index négatif suit en théorie l'ordre
   d'empilement CSS standard (peint après le canvas, avant le contenu statique), mais ce
   n'est manifestement pas fiable partout pour cette combinaison précise. Avec
   z-index:0, il faut en contrepartie que TOUT contenu visible passe explicitement
   au-dessus — voir `.main-content { position: relative; z-index: 1 }` plus loin (les
   autres conteneurs de premier niveau — topbar, nav, popups, fab — ont déjà tous un
   z-index positif, aucun changement nécessaire pour eux). */
body::before {
  content: '';
  position: fixed;
  /* top/right/bottom/left plutôt que le raccourci `inset: 0` : aucun avantage ici (même
     résultat) mais un support navigateur plus récent — écarté par précaution. */
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 0;
  background-image: var(--bg-image);
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  opacity: 0;
  animation: idiaa-bg-fade-in 0.8s ease forwards;
}

@keyframes idiaa-bg-fade-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

/* Anti-flash de contenu protégé — voir le script en tête de header.php (pose la classe
   avant le <body>) et auth-guard.js (la retire une fois la connexion confirmée, sinon
   redirige vers login.php sans jamais la retirer). visibility:hidden (pas display:none)
   pour ne rien casser côté layout/JS qui lirait des dimensions avant que auth-guard.js
   ait tranché. Le fond (body::before / background-color, tous les deux sur html/body,
   jamais concernés par ces classes) reste visible : pas de flash blanc, un écran "vide"
   le temps de la vérification (1-2s au pire selon la persistance Firebase locale). */
html.idiaa-auth-pending .topbar,
html.idiaa-auth-pending .nav-desktop,
html.idiaa-auth-pending .nav-mobile,
html.idiaa-auth-pending .main-content,
html.idiaa-auth-pending .fab {
  visibility: hidden;
}

body {
  min-height: 100vh;
  /* env(safe-area-inset-top) = zone occupée par l'heure/l'encoche/le signal en PWA
     plein écran iOS (status-bar-style "black-translucent" laisse le contenu passer
     dessous, viewport-fit=cover l'expose) ; vaut 0 ailleurs (desktop, Android...). */
  padding-top: calc(var(--topbar-h) + env(safe-area-inset-top, 0px));
  padding-bottom: calc(var(--nav-height-mobile) + env(safe-area-inset-bottom, 0px));
}

@media (min-width: 900px) {
  body {
    padding-bottom: 0;
    padding-left: var(--nav-width-desktop);
  }
}

a {
  color: var(--accent);
  text-decoration: none;
}

/* ---------- Tooltips (icônes d'action isolées, sans texte visible à côté) ---------- */
/* Attribut data-tooltip plutôt qu'un composant JS positionné (comme .th-filter__menu) :
   ces icônes n'ont pas besoin de s'échapper d'un conteneur overflow:hidden, un simple
   ::after (bulle) + ::before (flèche) suffit, affiché sur :hover/:focus-visible (clavier).
   Volontairement PAS d'attribut `title` en plus (éviterait un doublon avec la bulle grise
   native du navigateur) — data-tooltip reprend le même texte que l'aria-label existant,
   une seule chaîne à maintenir par icône.
   position:relative placé ICI, tôt dans le fichier : .topbar-account/.avatar-camera-btn
   (plus bas) ont déjà position:absolute — à spécificité égale, la déclaration la plus
   tardive dans le fichier gagne, donc en restant tôt ici, ces position:absolute existants
   continuent de l'emporter sans rien casser (vérifié avant d'écrire cette règle). */
[data-tooltip] {
  position: relative;
}

[data-tooltip]::before,
[data-tooltip]::after {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  opacity: 0;
  pointer-events: none;
  /* transition-delay explicite à 0s ici (pas juste omise) : à la disparition (survol
     terminé), cette règle de base reprend la main SANS délai — seule l'apparition
     (:hover/:focus-visible ci-dessous) en a un. Évite que la bulle traîne à l'écran
     après que le curseur ait quitté l'icône. */
  transition: opacity 0.15s ease;
  transition-delay: 0s;
  z-index: 150;
}

/* Bulle "inversée" (fond var(--text)/texte var(--bg)) : lisible dans les 3 thèmes sans
   nouveau token dédié. */
[data-tooltip]::after {
  content: attr(data-tooltip);
  bottom: calc(100% + 8px);
  background: var(--text);
  color: var(--bg);
  font-size: 12px;
  font-weight: 500;
  line-height: 1.3;
  white-space: nowrap;
  padding: 6px 10px;
  border-radius: 6px;
  box-shadow: var(--shadow-md);
}

/* Petite flèche (triangle CSS classique : border-top coloré, reste transparent —
   pointe vers le bas, vers l'icône). */
[data-tooltip]::before {
  content: '';
  bottom: calc(100% + 3px);
  border: 5px solid transparent;
  border-top-color: var(--text);
}

/* Délai volontaire de 400ms avant apparition (demandé explicitement) : évite que les
   bulles clignotent quand la souris ne fait que traverser l'écran sans s'arrêter sur une
   icône — seul ce sens (apparition) a un délai, la disparition reste immédiate (voir la
   règle de base ci-dessus).
   :hover scopé à `(hover: hover)` (souris/trackpad réel) — sur tactile (tablette
   incluse, pas seulement mobile <900px), :hover se déclenche au tap et peut rester
   "collé" jusqu'au tap suivant ailleurs (bulle qui traîne à l'écran sans curseur à
   déplacer pour la faire disparaître) : autant ne jamais l'activer là où il n'a pas de
   sens. :focus-visible, lui, reste actif dans tous les cas (accessibilité clavier,
   indépendante de la capacité de survol — un clavier externe branché sur tablette doit
   continuer à déclencher la bulle). */
@media (hover: hover) {
  [data-tooltip]:hover::after,
  [data-tooltip]:hover::before {
    opacity: 1;
    transition-delay: 0.4s;
  }
}

[data-tooltip]:focus-visible::after,
[data-tooltip]:focus-visible::before {
  opacity: 1;
  transition-delay: 0.4s;
}

/* Variante "en dessous" — seule icône trop proche du haut de l'écran pour que la bulle
   par défaut (au-dessus) reste visible : #profilePopupBtn (topbar). */
[data-tooltip][data-tooltip-pos="bottom"]::after {
  bottom: auto;
  top: calc(100% + 8px);
}

[data-tooltip][data-tooltip-pos="bottom"]::before {
  bottom: auto;
  top: calc(100% + 3px);
  border-top-color: transparent;
  border-bottom-color: var(--text);
}

/* Variante "à gauche" — icône dans une ligne de glissement tactile (.xxx-row-swipe,
   overflow:hidden pour ses coins arrondis pendant le geste) : ni au-dessus ni en dessous
   n'a la place de s'afficher entièrement (hauteur de ligne trop faible, ~50-70px, coupée
   par ce même overflow:hidden — bulle invisible, signalée par l'utilisateur, capture à
   l'appui), contrairement à la largeur (une ligne est toujours bien plus large que
   haute). La bulle s'ouvre donc horizontalement, à gauche de l'icône plutôt qu'au-dessus/
   en dessous — verticalement centrée sur l'icône (translateY, pas translateX). */
[data-tooltip][data-tooltip-pos="left"]::after {
  bottom: auto;
  top: 50%;
  left: auto;
  right: calc(100% + 8px);
  transform: translateY(-50%);
}

[data-tooltip][data-tooltip-pos="left"]::before {
  bottom: auto;
  top: 50%;
  left: auto;
  right: calc(100% + 3px);
  transform: translateY(-50%);
  border-top-color: transparent;
  border-left-color: var(--text);
}

/* Variante "alignée à droite" — icône trop proche du bord droit d'un panneau étroit
   (overflow:hidden) pour qu'une bulle centrée reste entière : la bulle s'aligne sur le
   bord droit de l'icône au lieu d'être centrée dessus (la flèche, elle, reste centrée
   sur l'icône — pointe toujours au bon endroit même décalée par rapport à la bulle). */
[data-tooltip][data-tooltip-align="end"]::after {
  left: auto;
  right: 0;
  transform: none;
}

/* Miroir de la variante ci-dessus — icône trop proche du bord GAUCHE (ex. premier bouton
   du composeur de chat, collé au bord de l'écran en mobile). */
[data-tooltip][data-tooltip-align="start"]::after {
  left: 0;
  transform: none;
}

/* Wrapper minimal pour poser data-tooltip sur un <svg> brut (icône "+" sans bouton
   autour) : ::after/::before (attr(data-tooltip)) ne se peint pas sur un élément <svg>
   lui-même dans Chromium (testé isolément) — seulement sur un élément HTML classique.
   inline-flex pour ne pas perturber la mise en page (dimensionnement au contenu, comme
   le <svg> qu'il remplace dans le flux). */
.icon-tooltip {
  display: inline-flex;
}

/* ---------- Icône menu (⋮) + menu contextuel (row-menu.js) ---------- */
/* Nouveau concept de liste, demandé explicitement : remplace l'ancienne icône de
   suppression en fin de ligne (hors du cadre) par une icône menu À L'INTÉRIEUR de la
   ligne, ouvrant un petit menu contextuel (Éditer/Partager/Supprimer selon la liste) —
   partagé par toutes les listes de l'app (recettes, amis, listes de courses, articles),
   une seule définition ici plutôt que dupliquée par page. */
/* Masqué entièrement par défaut/sur tactile (display:none, pas juste opacité) : les
   actions y sont révélées par glissement à la place (swipe-row.js), pas par ce menu —
   voir CLAUDE.md / Détection tactile vs pointeur. Sur pointeur fin (media query plus
   bas), redevient un inline-flex normal, masqué seulement par opacity et révélé par
   chaque appelant (règle posée sur SA propre classe de ligne, ex. ".data-table tbody
   tr:hover .row-menu-trigger" — recipes.js) — ou tant que son propre menu reste ouvert
   (.row-menu-open, posée par row-menu.js : sans ça, le déclencheur disparaîtrait dès que
   le curseur quitte la ligne pour aller cliquer une action du menu, lui-même hors de la
   ligne, ajouté à document.body). */
.row-menu-trigger {
  display: none;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  /* Décale l'icône de 10px vers la gauche (demandé explicitement, trop proche du bord) —
     posée sur ce bouton partagé plutôt que sur chaque cellule/conteneur appelant : dans
     un <td> centré (text-align:center, recipes.js/shopping-lists.js/shopping-list-view.js)
     comme dans le dernier élément d'une ligne flex (.people-row__action, friends.js), une
     marge à droite décale le bouton vers la gauche de la même façon dans les deux cas. */
  margin-right: 10px;
  border: none;
  background: none;
  padding: 0;
  color: var(--text-muted);
  cursor: pointer;
  transition: color 0.2s ease;
}

.row-menu-trigger svg {
  width: 100%;
  height: 100%;
}

/* Même couleur/transition de survol que les icônes "+" de l'app (.icon-plus:hover) —
   demandé explicitement. */
.row-menu-trigger:hover {
  color: var(--svg-hover);
}

@media (hover: hover) and (pointer: fine) {
  .row-menu-trigger {
    display: inline-flex;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.15s ease, color 0.2s ease;
  }
}

/* Menu contextuel : ajouté à document.body par row-menu.js (position:fixed, coordonnées
   directement compatibles avec getBoundingClientRect()) — jamais rogné par l'overflow:
   hidden d'un tableau/panneau englobant, contrairement à l'ancienne icône de suppression
   "en dehors" du tableau (.shopping-list-delete-outside, retirée avec ce changement). */
.row-menu {
  position: fixed;
  z-index: 200;
  min-width: 160px;
  background-color: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: var(--shadow-md);
  padding: 6px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

/* Même fond que les popups de l'app (.list-share-panel, .recipe-share-people-panel,
   .list-create-panel...) en mode Îdiää, demandé explicitement : panneau vitré
   (surface-alt translucide + flou), pas le fond plein var(--surface) des autres thèmes. */
:root[data-theme="idiaa"] .row-menu {
  background-color: var(--surface-alt);
  -webkit-backdrop-filter: blur(20px);
  backdrop-filter: blur(20px);
}

/* flex plutôt que block (icône SVG optionnelle, item.icon, à gauche du texte — gap 8px
   demandé explicitement) : "Effacer la discussion" (friends.js, pas d'icône fournie pour
   cette action précise) reste du texte seul, aligné sur les autres via ce même flex (pas
   de décalage entre lignes avec/sans icône, gap ne réserve rien s'il n'y a rien à côté). */
.row-menu__item {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  text-align: left;
  border: none;
  background: none;
  padding: 8px 10px;
  border-radius: 6px;
  font-size: 14px;
  color: var(--text);
  cursor: pointer;
  transition: background-color 0.15s ease;
}

.row-menu__item-icon {
  display: inline-flex;
  flex-shrink: 0;
  width: 16px;
  height: 16px;
  color: inherit;
}

.row-menu__item-icon svg {
  width: 100%;
  height: 100%;
}

/* Même couleur de survol que la sidebar (.nav-desktop__item:hover, nav.css) — demandé
   explicitement. var(--accent-tint) plutôt que var(--surface-alt) (valeur précédente) :
   pas besoin de scoper par thème, la sidebar elle-même n'a pas de règle Îdiää dédiée pour
   son survol, --accent-tint étant déjà défini dans les 3 thèmes (variables.css). */
.row-menu__item:hover {
  background: var(--accent-tint);
}

.row-menu__item--danger {
  color: var(--danger);
}

.topbar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: calc(var(--topbar-h) + env(safe-area-inset-top, 0px));
  display: flex;
  align-items: stretch;
  padding-top: env(safe-area-inset-top, 0px);
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  box-shadow: var(--shadow-sm);
  z-index: 40;
}

/* Colonne alignée sur .nav-desktop (même largeur, même logique de disparition sous
   900px — voir nav.css) : le logo y reste centré horizontalement et verticalement,
   quelle que soit la page. */
.topbar-brand {
  display: none;
  width: var(--nav-width-desktop);
  flex-shrink: 0;
  align-items: center;
  justify-content: center;
}

.topbar-logo {
  font-family: var(--font-script);
  font-size: 32px;
  font-weight: bold;
  color: var(--text);
}

/* Titre de page + bouton profil. position:relative pour ancrer .topbar-account en
   position:absolute (voir plus bas) plutôt qu'en élément flex concurrent du titre :
   sinon les deux se disputeraient l'espace disponible et fausseraient le calcul
   max-width/margin:auto du titre (qui doit correspondre exactement à celui de
   .main-content, sans amputation liée à la largeur du bouton). */
.topbar-main {
  flex: 1;
  min-width: 0;
  position: relative;
  display: flex;
  align-items: center;
  padding: 0 16px 0 20px;
}

.topbar-title-inner {
  width: 100%;
  /* Réserve la place du bouton profil (40px + marge) : un titre qui irait jusqu'au
     bord serait tronqué proprement (ellipsis sur .topbar-title) plutôt que de passer
     dessous. */
  padding-right: 48px;
}

.topbar-title {
  margin: 0;
  font-size: 30px;
  font-weight: 650;
  color: var(--text);
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.topbar-account {
  position: absolute;
  right: 16px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--text);
  transition: color 0.2s ease;
}

.icon-btn.topbar-account:hover {
  color: var(--accent);
  background: transparent;
}

@media (min-width: 900px) {
  .topbar-brand {
    display: flex;
  }

  .topbar-main {
    padding: 0;
  }

  /* Centré sur la largeur de .topbar-main (donc sur la zone de contenu, comme en
     mobile) plutôt qu'aligné à gauche — même boîte que .main-content (max-width 960 +
     margin:auto) pour rester centré sur le contenu réel même sur très grand écran,
     mais padding symétrique (48px des deux côtés, gauche comme droite) puisqu'il n'y a
     plus besoin de caler un bord précis sur quoi que ce soit — juste assez pour que le
     bouton profil (à droite) ne soit jamais recouvert. */
  .topbar-title-inner {
    max-width: 960px;
    margin: 0 auto;
    padding: 0 48px;
  }
}

/* .page-header : conteneur du <h1> encore utilisé directement par recipe-view.php
   (titre = nom de la recette, chargé dynamiquement en JS, distinct du système de
   $pageTitle statique — voir Design system / Titre de page dans le topbar) — pas par
   les pages listées ci-dessus, dont le titre vit maintenant dans le topbar.
   display:flex (demandé explicitement, icônes Partager/Éditer à droite du nom, voir
   .recipe-view-actions plus bas) : justify-content:space-between pousse .h1 à gauche et
   .recipe-view-actions à droite, sans changer le rendu des autres pages qui n'ont pas ce
   second enfant (un seul enfant flex se comporte comme avant, display:block ou non). */
.page-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin: 20px 0 16px;
}

/* Remonte "Partagé par" plus près du nom (5px au lieu des 16px habituels) quand il est
   affiché (recipe-view.php uniquement, voir Design system) — laisse le cas normal (pas
   de recette partagée) inchangé, :has() ne cible que ce cas précis. Même technique déjà
   utilisée ailleurs dans ce fichier (.idiaa-field:has(textarea)). */
.page-header:has(+ #recipeViewSharedBy:not([hidden])) {
  margin-bottom: 5px;
}

.page-header h1 {
  font-size: 28px;
  font-weight: 650;
  margin: 0;
}

/* Icônes Partager/Éditer (recipe-view.php), demandé explicitement — flex-shrink:0 pour
   ne jamais se faire comprimer par un nom de recette long (.page-header h1 n'a lui aucune
   largeur maximale propre). */
.recipe-view-actions {
  display: flex;
  flex-shrink: 0;
  gap: 12px;
}

/* color: var(--text) (pas var(--accent), demandé explicitement) + survol orange
   (var(--svg-hover)) — même transition que les autres icônes de l'app (.icon-plus,
   .chat-composer__icon-btn), sans le scale() de ces dernières (pas demandé ici). */
.recipe-view-actions__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border: none;
  background: none;
  padding: 0;
  color: var(--text);
  cursor: pointer;
  transition: color 0.2s ease;
}

.recipe-view-actions__btn:hover {
  color: var(--svg-hover);
}

.recipe-view-actions__btn svg {
  width: 100%;
  height: 100%;
}

/* Bouton icône générique (hamburger, +, ...) */
.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--text);
  cursor: pointer;
}

.icon-btn:hover {
  background: var(--surface-alt);
}

.icon-btn svg {
  width: 22px;
  height: 22px;
}

.main-content {
  padding: 0 20px 24px;
  max-width: 960px;
  margin: 0 auto;
  /* Doit rester au-dessus de body::before (fond photo, voir plus haut) — celui-ci est en
     z-index:0 plutôt que négatif (piège rencontré : position:fixed + z-index négatif se
     peignait derrière le fond de page lui-même dans certains navigateurs, malgré des
     styles calculés corrects — vérifié via DevTools, capture à l'appui), donc n'importe
     quel contenu statique (non positionné) passerait sinon dessous. */
  position: relative;
  z-index: 1;
}

.card {
  border-radius: var(--radius-md);
  padding: 16px;
  margin-bottom: 12px;
}

.card--surface {
  background: var(--surface);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-sm);
}

.empty-state {
  text-align: center;
  color: var(--text-muted);
  padding: 60px 20px;
}

/* Bouton d'action flottant "+" — pas de bouton texte "Ajouter ..." */
.fab {
  position: fixed;
  right: 20px;
  bottom: calc(var(--nav-height-mobile) + env(safe-area-inset-bottom, 0px) + 20px);
  width: 56px;
  height: 56px;
  border-radius: 50%;
  border: none;
  background: var(--accent);
  color: var(--accent-contrast);
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);
  cursor: pointer;
  z-index: 20;
}

.fab svg {
  width: 26px;
  height: 26px;
}

@media (min-width: 900px) {
  .fab {
    bottom: 24px;
  }
}

.auth-card {
  max-width: 380px;
  margin: 12vh auto 0;
  padding: 0 20px;
}

.auth-logo {
  text-align: center;
  font-family: var(--font-script);
  font-size: 44px;
  font-weight: bold;
  color: var(--text);
}

.btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 100%;
  padding: 13px 16px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  font-size: 15px;
  font-weight: 600;
  cursor: pointer;
}

.btn + .btn {
  margin-top: 10px;
}

.btn--primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-contrast);
}

.input {
  width: 100%;
  padding: 13px 14px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--text-muted);
  background: var(--surface);
  color: var(--text);
  font-size: 14px;
  margin-bottom: 10px;
  box-sizing: border-box;
  transition: .3s;
}

.input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 5px 0 var(--accent);
}

/* Champs à ligne unique uniquement (pas les textarea : description recette, étapes) */
.input:not(textarea) {
  height: 30px;
  padding-top: 0;
  padding-bottom: 0;
}

/* Champs multiligne (description recette, étapes) */
textarea.input {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  font-size: 14px;
  min-height: 50px;
}

/* iOS Safari zoome automatiquement la page au focus d'un champ dont la police fait
   moins de 16px — pas de zoom arrière fiable en JS après coup, la seule vraie
   correction est de passer sous ce seuil uniquement sur mobile (desktop garde 14px,
   le zoom auto n'existe pas sur ces navigateurs). */
@media (max-width: 899px) {
  .input {
    font-size: 16px;
  }
}


.divider {
  text-align: center;
  color: var(--text-muted);
  margin: 18px 0;
  font-size: 13px;
}

.form-error {
  color: var(--danger);
  font-size: 13px;
  min-height: 18px;
  margin: 4px 0 0;
}

.form-success {
  color: #34c759;
  font-size: 13px;
  min-height: 18px;
  margin: 4px 0 0;
}

.avatar-wrap {
  text-align: center;
  margin-bottom: 16px;
}

/* Cadre carré autour de la photo (image ou placeholder) : sert d'ancrage pour le badge
   caméra positionné en absolute (voir .avatar-camera-btn) — .avatar/.avatar--placeholder
   remplissent maintenant ce cadre à 100% au lieu de gérer eux-mêmes taille/centrage. */
.avatar-photo {
  position: relative;
  width: 96px;
  height: 96px;
  margin: 0 auto 12px;
}

/* Variante compacte pour une ligne de liste (.people-row, voir plus bas) : même cadre que
   .avatar-photo (photo ou repli silhouette), pas de badge caméra ni de centrage — 48px,
   même taille que la vignette .data-table__thumb (recettes) pour garder le même rythme
   visuel entre les listes de l'app. */
.avatar-photo--sm {
  position: relative;
  width: 48px;
  height: 48px;
  margin: 0;
  flex-shrink: 0;
}

.avatar-photo--sm .avatar--placeholder svg {
  width: 22px;
  height: 22px;
}

.avatar,
.avatar--placeholder {
  width: 100%;
  height: 100%;
  border-radius: 50%;
  object-fit: cover;
}

.avatar--placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--surface-alt);
  color: var(--text-muted);
}

.avatar--placeholder svg {
  width: 44px;
  height: 44px;
}

/* Badge caméra ("changer la photo"), en bas à droite de l'avatar — remplace l'ancien
   bouton texte .btn--neu. */
.avatar-camera-btn {
  position: absolute;
  bottom: 0;
  right: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border-radius: 50%;
  border: 2px solid var(--surface);
  background: var(--surface);
  /* Noir -> bleu au survol, comme l'icône profil de la topbar (.topbar-account). */
  color: var(--text);
  cursor: pointer;
  transition: color 0.2s ease;
}

.avatar-camera-btn:hover {
  color: var(--accent);
}

.avatar-camera-btn svg {
  width: 16px;
  height: 16px;
}

.toggle-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 0;
  border-bottom: 1px solid var(--border);
}

/* Sans accent-color, un input radio/checkbox natif garde le bleu par défaut du
   navigateur quel que soit le thème — invisible en clair (coïncide avec --accent bleu)
   mais détonne en sombre (pas la bonne nuance) et surtout en Îdiää (monochrome
   intégral, voir variables.css) : seule fuite de couleur restante, repérée à l'écran
   (radios de settings.php). */
input[type="radio"],
input[type="checkbox"] {
  accent-color: var(--accent);
}

.toggle-row:last-child {
  border-bottom: none;
}

/* recipe-edit.php : ".toggle-row" y est seul dans son ".form-row" (pas une liste de
   plusieurs toggles comme dans settings.php), donc la bordure séparatrice n'a pas lieu
   d'être — et ":last-child" ne s'applique pas puisque le champ thermostat est le
   dernier enfant du ".form-row", pas lui. Même padding/centrage vertical que le champ
   thermostat pour que les deux lignes s'alignent en desktop (voir ".form-row .form-group"). */
.form-row .toggle-row {
  border-bottom: none;
  justify-content: flex-start;
  gap: 20px;
}

#ovenTempGroup {
  align-items: center;
  padding: 12px 0;
}

/* .form-group définit display:flex (règle auteur), ce qui neutralise le display:none
   par défaut de l'attribut [hidden] (règle du user-agent stylesheet, toujours perdante
   face à une règle auteur) — même bug déjà rencontré/corrigé pour .th-filter__menu et
   .loading-overlay (voir plus bas). Sans ça, le champ thermostat reste visible même
   toggle "Utilise un four" désactivé. */
#ovenTempGroup[hidden] {
  display: none;
}

.toggle-row__label {
  display: flex;
  align-items: center;
  gap: 10px;
}

.switch {
  position: relative;
  display: inline-block;
  width: 36px;
  height: 20px;
}

.switch input {
  opacity: 0;
  width: 0;
  height: 0;
}

.switch__track {
  position: absolute;
  inset: 0;
  /* var(--border) est quasi invisible ici (à peine distinct de --surface une fois
     décoché) : --text-muted donne un vrai contraste tout en restant neutre. */
  background: var(--text-muted);
  border-radius: 999px;
  transition: 0.2s;
}

.switch__track::before {
  content: "";
  position: absolute;
  height: 16px;
  width: 16px;
  left: 2px;
  top: 2px;
  background: white;
  border-radius: 50%;
  transition: 0.2s;
}

/* Bleu FIXE (pas var(--accent)) — demandé explicitement pour les deux seuls toggles de
   ce composant ("Cuisson au four", recipe-edit.php ; "Masquer les articles cochés",
   shopping-list-view.php) : --accent vaut --text en mode Îdiää (monochrome intégral,
   voir variables.css), donc le toggle coché y aurait été noir/blanc plutôt que bleu.
   Même principe que .chat-composer__send-btn (couleur fixe, bypass --accent). */
.switch input:checked + .switch__track {
  background: #007aff;
}

.switch input:checked + .switch__track::before {
  transform: translateX(16px);
}

.modal-overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.4);
  align-items: center;
  justify-content: center;
  z-index: 50;
  padding: 20px;
}

.modal-overlay.is-open {
  display: flex;
}

.modal {
  width: 100%;
  max-width: 360px;
}

.modal h2 {
  margin-top: 0;
  font-size: 18px;
}

.list-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 16px;
  border-radius: var(--radius-md);
  background: var(--surface);
  border: 1px solid var(--border);
  margin-bottom: 8px;
  color: var(--text);
  text-decoration: none;
}

/* Picker d'ingrédients : ligne = vignette + nom, pas de second élément à pousser à
   droite (contrairement à .list-item générique utilisée ailleurs, ex. friends.js). */
#ingredientPickerList .list-item {
  justify-content: flex-start;
  gap: 10px;
}

.list-item__meta {
  color: var(--text-muted);
  font-size: 13px;
}

.list-item__remove {
  border: none;
  background: transparent;
  color: var(--danger);
  font-size: 13px;
  cursor: pointer;
  padding: 4px 8px;
}

/* ---------- Ligne "personne" (avatar + nom + action) ---------- */
/* Même habillage que .list-item (fond, bordure, coins, ombre douce implicite via bordure)
   mais avec un avatar rond en tête et un emplacement optionnel pour une action à droite
   (bouton Accepter/Refuser/Annuler/Inviter, ou juste un aperçu de dernier message) — utilisé
   par la liste d'amis, les deux listes d'invitations (friends.php), les résultats de la
   popup d'ajout d'ami et la liste des conversations (chats.php). Un seul composant plutôt
   qu'une déclinaison par écran : ces quatre usages n'ont structurellement rien d'autre
   qu'un avatar + un nom + une zone de fin, pas de raison de dupliquer. */
.people-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 14px;
  border-radius: var(--radius-md);
  background: var(--surface);
  border: 1px solid var(--border);
  margin-bottom: 8px;
  color: var(--text);
  text-decoration: none;
}

a.people-row:hover {
  border-color: var(--accent);
}

/* Badge "message non lu" (friends.js) : même principe que .nav-badge-wrap (nav.css) —
   wrapper relatif dédié autour du seul avatar (pas toute la ligne), pour superposer le
   badge à son coin sans dépendre du reste de la ligne. .avatar-photo--sm est déjà
   position:relative (voir plus bas) mais ce n'est pas suffisant seul : avatarThumb()
   (avatar-format.js) ne laisse pas d'emplacement pour un enfant supplémentaire à
   l'intérieur de ce <div>, d'où ce wrapper posé autour depuis friends.js plutôt que dans
   avatarThumb() lui-même (qui reste générique, utilisé aussi là où ce badge n'a pas de
   sens — chats.php, popup profil...). */
.people-row__avatar-wrap {
  position: relative;
  display: inline-flex;
  flex-shrink: 0;
}

/* Couleur fixe (--unread-badge, variables.css) : reste bleue même en mode Îdiää
   monochrome, comme --danger ailleurs dans l'app — indicateur de notification, pas un
   élément soumis à --accent. */
.people-row__unread-badge {
  position: absolute;
  top: -2px;
  right: -10px;
  display: flex;
  align-items: center;
  justify-content: center;
  /* +60% par rapport à la taille d'origine (16px), demandé explicitement. */
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--surface);
  color: var(--unread-badge);
}

.people-row__unread-badge svg {
  /* +60% par rapport à la taille d'origine (11px), demandé explicitement. */
  width: 18px;
  height: 18px;
}

.people-row__name {
  flex: 1;
  min-width: 0;
  font-size: 14px;
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Variante "deux lignes" (chats.php : nom + aperçu du dernier message) : colonne au lieu
   du simple span flex:1 de .people-row__name ci-dessus (usage à une seule ligne — liste
   d'amis, résultats de recherche). À l'intérieur, .people-row__name n'a plus besoin
   d'être lui-même flex:1 (déjà porté par ce wrapper). */
.people-row__info {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.people-row__info .people-row__name {
  flex: none;
}

/* Aperçu du dernier message (chats.php), à l'intérieur de .people-row__info — sous le
   nom, même ligne verticale que .list-item__meta ailleurs dans l'app. */
.people-row__meta {
  display: block;
  color: var(--text-muted);
  font-size: 13px;
  font-weight: 400;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Horodatage en fin de ligne (chats.php) — hors de .people-row__info, ne doit jamais être
   compressé (texte toujours court, pas besoin d'ellipsis). */
.people-row__time {
  flex-shrink: 0;
  color: var(--text-muted);
  font-size: 12px;
}

/* Emplacement de fin (boutons Accepter/Refuser/Annuler/Inviter) : ne doit jamais être
   compressé par le nom (flex:1 ci-dessus), ni forcer la ligne à s'agrandir plus qu'il ne
   faut. */
.people-row__action {
  flex-shrink: 0;
  display: flex;
  gap: 8px;
}

/* ---------- Suppression d'un ami (liste d'amis, friends.php uniquement) ---------- */
/* Nouveau concept de liste (demandé explicitement) : l'ancienne icône de suppression
   visible au survol (flex sibling de .people-row-swipe__link, en fin de ligne) est
   remplacée par une icône menu À L'INTÉRIEUR de .people-row (voir .row-menu-trigger,
   plus haut dans ce fichier, et friends.js) — .people-row-swipe ne gère donc plus que le
   glissement tactile ici, le survol pointeur fin est entièrement pris en charge par
   .row-menu-trigger (opacity/pointer-events, règle scopée ci-dessous).
   Choix du mode de révélation tactile basé sur la CAPACITÉ D'ENTRÉE (hover/pointer), PAS
   sur la largeur d'écran (min/max-width:900px, utilisée partout ailleurs pour la mise en
   page sidebar/dock) : une tablette bascule en layout desktop (≥900px, sidebar visible)
   tout en étant pilotée au doigt, sans souris — voir CLAUDE.md / Détection tactile vs
   pointeur. */
.people-row-swipe {
  position: relative;
}

.people-row-swipe__link {
  /* .people-row (fond/bordure/coins/padding) posé sur ce même élément, voir friends.js —
     transform ci-dessous s'ajoute à son rendu habituel. */
  transition: transform 0.2s ease;
}

/* Pendant le glissement actif (voir friends.js) : pas de transition, sinon chaque micro-
   déplacement du doigt serait retardé de 0.2s et le suivi paraîtrait mou. Réactivée
   (classe retirée) au relâchement, pour l'animation d'accroche finale. */
.people-row-swipe.is-dragging .people-row-swipe__link {
  transition: none;
}

/* Révèle .row-menu-trigger (masqué par défaut sur pointeur fin, voir sa règle générique
   plus haut dans ce fichier) au survol de la ligne — ou tant que son propre menu reste
   ouvert (.row-menu-open, posée par row-menu.js). */
@media (hover: hover) and (pointer: fine) {
  .people-row-swipe:hover .row-menu-trigger,
  .people-row-swipe:focus-within .row-menu-trigger,
  .people-row-swipe.row-menu-open .row-menu-trigger {
    opacity: 1;
    pointer-events: auto;
  }

  /* Bug corrigé (signalé par l'utilisateur, capture à l'appui) : sans cette règle,
     .people-row-swipe__actions (glissement tactile) restait un <div> normal, affiché en
     flux sous la ligne sur pointeur fin — visible en texte brut ("Clear conversation",
     repli anglais faute d'i18n chargé) au lieu d'être masqué. Oubliée ici lors de la
     généralisation (présente sur les 3 autres listes : .recipe-row-swipe__actions,
     .shopping-list-row-swipe__actions, .shopping-item-row-swipe__actions) — le menu
     s'en charge à la place sur pointeur fin, pas de colonne/espace réservé pour elle
     non plus ici (contexte flex, pas table). */
  .people-row-swipe__actions {
    display: none;
  }
}

.people-row-swipe__action {
  border: none;
  cursor: pointer;
  padding: 0;
}

@media (hover: none), (pointer: coarse) {
  .people-row-swipe {
    overflow: hidden;
    /* Même arrondi que .people-row (var(--radius-md)) : sans ça, les coins de la ligne
       qui glisse dépasseraient du fond révélé en dessous aux coins de .people-row-swipe. */
    border-radius: var(--radius-md);
    margin-bottom: 8px;
  }

  .people-row-swipe .people-row {
    /* La marge basse passe sur .people-row-swipe ci-dessus : sinon un espace de 8px
       resterait visible (fond de la ligne) entre le bas de .people-row (translaté) et le
       bas de .people-row-swipe, au lieu que le fond révélé occupe toute la hauteur. */
    margin-bottom: 0;
  }

  /* --reveal : largeur réelle de .people-row-swipe__actions (1 seule action ici, mais
     généralisé — voir swipe-row.js), posée en JS à l'ouverture. 64px de repli tant que
     JS n'a rien posé (identique à l'ancienne valeur fixe, avant généralisation). */
  .people-row-swipe.is-swiped .people-row-swipe__link {
    transform: translateX(calc(-1 * var(--reveal, 64px)));
  }

  .people-row-swipe__actions {
    display: flex;
    position: absolute;
    inset: 0 0 0 auto;
    background: var(--surface-alt);
    /* translateX(100%) : décale le bloc de SA PROPRE largeur (pas une valeur fixe codée
       en dur) — fonctionne quel que soit le nombre d'actions qu'il contient, PAS juste
       masqué derrière la ligne : en mode Îdiää, .people-row (posé sur
       .people-row-swipe__link) a un fond translucide (glassmorphism, voir
       variables.css) — le bloc resterait visible en transparence même correctement
       empilée en z-index (piège déjà rencontré ici avant généralisation). En le sortant
       réellement du cadre, c'est .people-row-swipe (overflow:hidden) qui le coupe, plus
       de dépendance à l'opacité d'un calque au-dessus. */
    transform: translateX(100%);
    transition: transform 0.2s ease;
  }

  .people-row-swipe.is-dragging .people-row-swipe__actions {
    transition: none;
  }

  .people-row-swipe.is-swiped .people-row-swipe__actions {
    transform: translateX(0);
  }

  .people-row-swipe__action {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 64px;
  }

  .people-row-swipe__action svg {
    width: 28px;
    height: 28px;
  }
}

/* Section "invitations en attente" (friends.php) : masquée entièrement (hidden) tant que
   les deux sous-listes sont vides — voir friends.js. Chaque sous-liste n'ajoute son titre
   que si elle a du contenu (pas de "Envoyées" vide affiché seul). */
.friend-requests {
  margin-bottom: 20px;
}

.friend-requests h3 {
  margin: 0 0 8px;
  font-size: 14px;
  font-weight: 600;
  color: var(--text-muted);
}

.friend-requests > div:not(:empty) {
  margin-bottom: 16px;
}

.icon-picker {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
  margin: 10px 0;
}

.icon-picker__item {
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 1;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--surface-alt);
  color: var(--text-muted);
  cursor: pointer;
}

.icon-picker__item svg {
  width: 22px;
  height: 22px;
}

.icon-picker__item.is-selected {
  border-color: var(--accent);
  color: var(--accent);
  background: var(--surface);
}

/* ---------- Listes de courses (shopping-lists.php) ---------- */
/* Liste des listes : même .data-table que la liste des recettes (index.php), voir
   shopping-lists.js — .data-table__thumb accepte déjà un <svg> de repli à la place d'un
   <img> (voir sa définition plus bas), pas de classe dédiée nécessaire ici. */

/* Partager/Supprimer une liste : nouveau concept de liste (demandé explicitement, voir
   CLAUDE.md) — icône menu (.row-menu-trigger, voir plus haut) À L'INTÉRIEUR de la ligne
   sur pointeur fin, mêmes actions révélées par glissement sur tactile (swipe-row.js) —
   remplace l'ancienne icône de suppression seule, positionnée EN DEHORS du tableau
   (.shopping-list-delete-outside, JS-positionnée dans une marge réservée — retirée avec
   ce changement, plus nécessaire : le menu vit dans une cellule normale du tableau,
   aucune contrainte d'échapper à son overflow:hidden). Même famille de technique que
   .recipe-row-swipe (index.php) : VRAIE ligne de tableau (<tr>/<td>, pas un <div>) —
   linkSelector correspond ici à PLUSIEURS <td> (vignette/nom/nombre) qui glissent
   ensemble (voir swipe-row.js). */
.shopping-list-row-swipe {
  position: relative;
}

.shopping-list-row-swipe__link {
  transition: transform 0.2s ease;
}

.shopping-list-row-swipe.is-dragging .shopping-list-row-swipe__link {
  transition: none;
}

/* td.xxx (élément + classe), pas juste .xxx : .data-table th/td (élément + classe aussi)
   règle la base en text-align:left avec la même spécificité — sans l'élément ici, cette
   règle perdait silencieusement (repéré en vérifiant le décalage de .row-menu-trigger,
   resté sans effet). text-align:right (pas center) : sans ça, la marge-droite posée sur
   .row-menu-trigger pour le décaler du bord n'aurait aucun effet visible (un élément
   centré n'est pas "collé" à un bord dont on pourrait l'écarter). */
td.shopping-list-row-swipe__menu {
  text-align: right;
}

@media (hover: hover) and (pointer: fine) {
  .shopping-list-row-swipe:hover .row-menu-trigger,
  .shopping-list-row-swipe:focus-within .row-menu-trigger,
  .shopping-list-row-swipe.row-menu-open .row-menu-trigger {
    opacity: 1;
    pointer-events: auto;
  }

  /* Cellule de glissement tactile : ne sert pas ici (voir CLAUDE.md / Détection tactile
     vs pointeur) — le menu s'en charge, pas de colonne réservée pour elle non plus (pas
     de <col> dédié dans le colgroup, voir shopping-lists.php). */
  .shopping-list-row-swipe__actions {
    display: none;
  }
}

.shopping-list-row-swipe__action {
  border: none;
  cursor: pointer;
  padding: 0;
}

/* Mobile/tablette : sortie du flux de la table (position:absolute) et superposée au bord
   droit de la ligne, révélée par glissement — .is-swiped posée en JS (swipe-row.js).
   Clippée par overflow:hidden de .data-table (déjà présent, pour ses coins arrondis)
   tant qu'elle n'est pas révélée. */
@media (hover: none), (pointer: coarse) {
  /* --reveal : largeur réelle de .shopping-list-row-swipe__actions (1 ou 2 actions selon
     que la liste m'appartient ou non, voir shopping-lists.js), posée en JS à
     l'ouverture. 128px (64px × 2) de repli tant que JS n'a rien posé. */
  .shopping-list-row-swipe.is-swiped .shopping-list-row-swipe__link {
    transform: translateX(calc(-1 * var(--reveal, 128px)));
  }

  .shopping-list-row-swipe__actions {
    display: flex;
    position: absolute;
    inset: 0 0 0 auto;
    background: var(--surface-alt);
    /* translateX(100%) : décale le bloc de SA PROPRE largeur, quel que soit le nombre
       d'actions qu'il contient — voir le même choix sur .people-row-swipe__actions
       (friends.js) pour le détail (piège du glassmorphism Îdiää). */
    transform: translateX(100%);
    transition: transform 0.2s ease;
  }

  .shopping-list-row-swipe.is-dragging .shopping-list-row-swipe__actions {
    transition: none;
  }

  .shopping-list-row-swipe.is-swiped .shopping-list-row-swipe__actions {
    transform: translateX(0);
  }

  .shopping-list-row-swipe__action {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 64px;
    font-size: 12px;
    color: var(--text);
  }

  /* Partager : texte temporaire (aucune icône fournie pour l'instant, voir CLAUDE.md /
     Icônes — le projet n'invente pas d'icône approximative), à remplacer dès qu'une
     icône sera fournie — même traitement que sur .recipe-row-swipe__action (index.php).
     Supprimer a déjà une vraie icône (deleteListIconSvg). */
  .shopping-list-row-swipe__action svg {
    width: 24px;
    height: 24px;
  }
}

/* ---------- Vue d'une liste de courses (shopping-list-view.php) ---------- */
.shopping-list-view-header {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 16px;
}

.shopping-list-view-header h1 {
  flex: 1;
  min-width: 0;
  margin: 0;
  font-size: 22px;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.shopping-list-view-header__actions {
  display: flex;
  flex-shrink: 0;
  gap: 8px;
}

/* Bug signalé (capture à l'appui) : une ligne horizontale parasite apparaissait collée
   au-dessus du tableau des articles, juste sous "Masquer les articles cochés" — c'est en
   fait le border-bottom de base de .toggle-row (pensé pour une LISTE de toggles,
   settings.php, séparés les uns des autres, dernier sans bordure via :last-child) : ici
   ".toggle-row" est seule mais n'est PAS le dernier enfant de #shoppingListPage (le
   tableau des articles suit), donc :last-child ne s'applique jamais et la bordure reste
   — visible comme un séparateur collé au tableau. Même correctif que ".form-row
   .toggle-row" (recipe-edit.php, "Cuisson au four") : bordure retirée, toggle ramené
   contre le texte avec un gap de 20px au lieu d'être poussé à l'opposé
   (justify-content:space-between par défaut) — demandé explicitement, même rendu que
   "Cuisson au four" (voir screenshot). */
#shoppingListPage .toggle-row {
  border-bottom: none;
  justify-content: flex-start;
  gap: 20px;
}

.shopping-list-view-header__actions button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border: none;
  background: none;
  padding: 0;
  cursor: pointer;
}

/* :not(.icon-plus) — bug trouvé en vérifiant l'animation du "+" (#articleAddBtn, demandé
   explicitement "même animation que celui d'ajout de recette") : ce bouton partage ce
   conteneur avec #shoppingListShareBtn (partager), qui doit lui virer à var(--accent) au
   survol comme les autres icônes de la topbar/popup profil — mais réutilisée telle
   quelle, cette règle plus spécifique que .icon-plus/.icon-plus:hover (classe + élément +
   pseudo, contre classe + pseudo) l'emportait AUSSI sur #articleAddBtn, écrasant sa
   couleur de base (var(--accent), bleu en clair/sombre) et sa couleur de survol
   (var(--svg-hover), orange) par var(--text)/var(--accent) — invisible en mode Îdiää où
   var(--accent) vaut déjà var(--text) (d'où le bug repéré précisément dans ce thème),
   mais déjà présent dans tous les thèmes (vérifié : #articleAddBtn rendu noir par défaut
   en clair au lieu du bleu attendu). #articleAddBtn n'a donc plus besoin d'aucune couleur
   ici, .icon-plus/.icon-plus:hover (style.css, plus haut) les fournit entièrement. */
.shopping-list-view-header__actions button:not(.icon-plus) {
  color: var(--text);
  transition: color 0.2s ease;
}

.shopping-list-view-header__actions button:not(.icon-plus):hover {
  color: var(--accent);
}

.shopping-list-view-header__actions svg {
  width: 100%;
  height: 100%;
}

/* Liste des articles (shopping-list-view.php) : demandé explicitement "même forme que la
   liste des recettes" (.data-table, index.php) — un seul <table> plutôt qu'une pile de
   cards par catégorie (ancien .shopping-category-section/<h3>, supprimés). La catégorie
   devient une ligne d'en-tête À L'INTÉRIEUR du tableau (.shopping-category-row, voir plus
   bas) — demandé ainsi ("en entête la catégorie d'article") — donc pas de <thead> de
   colonnes séparé, ces en-têtes de catégorie en tiennent lieu. Une seule catégorie
   affichée si elle contient au moins un article (voir shopping-list-view.js /
   resolveCategoryId — Non classé toujours en dernier). */
.shopping-items-table {
  width: 100%;
  table-layout: fixed;
  border-collapse: separate;
  border-spacing: 0;
  background: var(--surface);
  border-radius: 8px;
  border: 1px solid var(--text-muted);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
  margin-bottom: 16px;
}

/* Mêmes valeurs que .data-table (liste des recettes) — classe dédiée plutôt que
   réutilisée telle quelle : nombre/ordre de colonnes différents (case à cocher +
   vignette + nom + quantité + suppression, contre vignette + nom + type + ... pour les
   recettes) — les règles column-spécifiques de .data-table (nth-child centré, padding
   du premier/second enfant) s'appliqueraient au mauvais endroit ici. Même principe que
   .recipe-ingredients, harmonisée avec .data-table mais gardée séparée (voir CLAUDE.md /
   Tableaux). */
.shopping-items-table th,
.shopping-items-table td {
  padding: 12px;
  text-align: left;
  vertical-align: middle;
  border-bottom: 1px solid var(--border);
  font-size: 14px;
}

.shopping-items-table tbody tr:last-child td {
  border-bottom: none;
}

.shopping-items-table__col--check {
  width: 40px;
}

.shopping-items-table__col--thumb {
  width: 60px;
}

.shopping-items-table__col--qty {
  width: 90px;
}

/* Icône menu (nouveau concept de liste, voir CLAUDE.md) — même raisonnement qu'avant
   généralisation (piège rencontré en vérifiant sur mobile) : réservée en dur, cette
   colonne grignotait la place du nom de l'article (déjà comprimé par les 4 autres
   colonnes fixes) au point de tronquer des noms courts — alors que sur tactile le menu
   ne sert pas du tout (les actions y sont révélées par glissement, voir
   .shopping-item-row-swipe__actions plus bas), sa largeur de colonne n'aurait donc aucun
   rôle visuel utile, seulement gaspillée. Réservée seulement là où elle sert vraiment :
   sur pointeur fin. 0 par défaut (tactile). */
.shopping-items-table__col--menu {
  width: 0;
}

/* 58px (pas 40px) : même correctif que .data-table__col--menu (style.css) — sans assez
   de place pour icône + padding + les 10px de marge de .row-menu-trigger, Chromium
   ignore text-align:right (et la marge) en situation de débordement. */
@media (hover: hover) and (pointer: fine) {
  .shopping-items-table__col--menu {
    width: 58px;
  }
}

/* Colonne dédiée pour .shopping-item-row-swipe__actions (6e <td>, jamais dans le flux
   normal — display:none sur pointeur fin, position:absolute sur tactile) : largeur 0
   dans tous les cas, mais DÉCLARÉE (voir shopping-list-view.php pour le piège rencontré
   sans elle : une cellule "en trop" au-delà du nombre de <col>, combinée à une ligne
   d'en-tête de catégorie à colspan juste au-dessus, faussait le calcul de largeur de
   TOUTES les colonnes de la ligne suivante dans Chromium). */
.shopping-items-table__col--actions {
  width: 0;
}

/* Quantité : moins de place nécessaire sur petit écran (colonne nom déjà comprimée par
   case à cocher + vignette + quantité) — même seuil que le reste de l'app (900px,
   desktop/mobile), pas hover/pointer (question de place, pas de capacité d'entrée). */
@media (max-width: 899px) {
  .shopping-items-table__col--qty {
    width: 60px;
  }
}

/* En-tête de catégorie : même apparence que l'en-tête de colonnes de .data-table (fond
   var(--surface-alt), 600/15px) — pas cliquable/survolable comme une ligne d'article,
   volontairement exclue des règles de survol plus bas (scopées à
   .shopping-item-row-swipe, pas à "tbody tr" en général). */
.shopping-category-row th {
  color: var(--text);
  font-weight: 600;
  font-size: 15px;
  background: var(--surface-alt);
}

/* Ligne "article" : VRAIE ligne de tableau (<tr>/<td>, pas un <div>) — même mécanique que
   .shopping-list-row-swipe (shopping-lists.js) et .people-row-swipe (friends.js), voir
   CLAUDE.md / Détection tactile vs pointeur. data-checked porté par le wrapper (pas
   juste la case à cocher) pour permettre le filtre CSS "masquer les cochés" plus bas sans
   avoir à interroger l'état de l'input. */
.shopping-item-row-swipe {
  position: relative;
}

.shopping-item-row-swipe__link {
  transition: transform 0.2s ease;
}

.shopping-item-row-swipe.is-dragging .shopping-item-row-swipe__link {
  transition: none;
}

.shopping-item-row__checkbox {
  width: 20px;
  height: 20px;
  cursor: pointer;
}

.shopping-item-row__thumb {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: var(--radius-sm);
  background: var(--surface-alt);
  color: var(--text-muted);
  overflow: hidden;
}

.shopping-item-row__thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.shopping-item-row__thumb svg {
  width: 20px;
  height: 20px;
}

/* display:block (pas le <span> inline par défaut) : nécessaire pour que
   overflow/text-overflow/white-space (troncature "...") fassent effet de façon fiable —
   avant, .shopping-item-row__name comptait sur son rôle d'ENFANT FLEX (flex:1, min-width:0)
   pour sa contrainte de largeur ; dans un <td>, ce contexte n'existe plus, la largeur
   vient du <col> (celle-ci, sans classe dédiée, absorbe l'espace restant — même principe
   que la colonne "Recette" de .data-table). */
.shopping-item-row__name {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Article coché : nom barré/atténué (repli purement visuel — l'ordre/le groupe ne
   changent pas tant que "masquer les cochés" n'est pas activé, voir plus bas). */
.shopping-item-row-swipe[data-checked="true"] .shopping-item-row__name {
  color: var(--text-muted);
  text-decoration: line-through;
}

.shopping-item-row__qty {
  font-size: 13px;
  color: var(--text-muted);
}

/* td.xxx (élément + classe), pas juste .xxx : .shopping-items-table th/td (élément +
   classe aussi) règle la base en text-align:left avec la même spécificité — voir le même
   correctif sur .shopping-list-row-swipe__menu (style.css) pour le détail du piège.
   text-align:right (pas center) : la marge-droite de .row-menu-trigger a besoin d'un
   bord droit réel à écarter. */
td.shopping-item-row-swipe__menu {
  text-align: right;
}

@media (hover: hover) and (pointer: fine) {
  .shopping-item-row-swipe:hover .row-menu-trigger,
  .shopping-item-row-swipe:focus-within .row-menu-trigger,
  .shopping-item-row-swipe.row-menu-open .row-menu-trigger {
    opacity: 1;
    pointer-events: auto;
  }

  /* Cellule de glissement tactile : ne sert pas ici (voir CLAUDE.md / Détection tactile
     vs pointeur) — le menu s'en charge, pas de colonne réservée pour elle non plus (pas
     de <col> dédié dans le colgroup, voir shopping-list-view.php). */
  .shopping-item-row-swipe__actions {
    display: none;
  }
}

.shopping-item-row-swipe__action {
  border: none;
  cursor: pointer;
  padding: 0;
}

@media (hover: none), (pointer: coarse) {
  /* --reveal : largeur réelle de .shopping-item-row-swipe__actions (une seule action
     ici, "Supprimer"), posée en JS à l'ouverture — voir swipe-row.js. 64px de repli tant
     que JS n'a rien posé (identique à l'ancienne valeur fixe, avant généralisation). */
  .shopping-item-row-swipe.is-swiped .shopping-item-row-swipe__link {
    transform: translateX(calc(-1 * var(--reveal, 64px)));
  }

  .shopping-item-row-swipe__actions {
    display: flex;
    position: absolute;
    inset: 0 0 0 auto;
    background: var(--surface-alt);
    /* translateX(100%) : décale le bloc de SA PROPRE largeur — voir le même choix sur
       .people-row-swipe__actions (friends.js) pour le détail (piège du glassmorphism
       Îdiää). */
    transform: translateX(100%);
    transition: transform 0.2s ease;
  }

  .shopping-item-row-swipe.is-dragging .shopping-item-row-swipe__actions {
    transition: none;
  }

  .shopping-item-row-swipe.is-swiped .shopping-item-row-swipe__actions {
    transform: translateX(0);
  }

  .shopping-item-row-swipe__action {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 64px;
  }

  .shopping-item-row-swipe__action svg {
    width: 28px;
    height: 28px;
  }
}

/* Toggle "masquer les articles cochés" (préférence locale par appareil, localStorage —
   pas dans Firestore, voir CLAUDE.md) : classe posée par shopping-list-view.js sur le
   conteneur englobant (désormais un <tbody>, voir shopping-list-view.php), pur CSS, pas
   de re-render du DOM à chaque bascule. display:none sur un <tr> masque bien la ligne
   entière. */
#shoppingCategories.is-hiding-checked .shopping-item-row-swipe[data-checked="true"] {
  display: none;
}

/* ---------- Popup de sélection d'article (bibliothèque de produits) ---------- */
/* Même famille que .ingredient-selector/.ingredient-list (picker d'ingrédient,
   recipe-edit.php) — classes dédiées plutôt que réutilisées telles quelles, même
   convention que le reste du projet (popups sans rapport ne partagent pas un nom de
   classe, même rendu identique). */
.article-selector {
  display: none;
  position: fixed;
  inset: 0;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.2);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  z-index: 100;
  padding: 20px;
}

.article-selector.is-open {
  display: flex;
}

.article-list {
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 420px;
  max-height: 80vh;
  overflow: hidden;
  background-color: var(--surface);
  border: 1px solid var(--text-muted);
  border-radius: 8px;
  padding: 10px;
  box-shadow: 6px 6px 12px var(--text-muted);
}

.article-list .idiaa-field {
  margin: 0 0 10px;
}

/* Zone qui défile de la popup "Nouveau produit" (#newArticleForm/.article-list__body) —
   demandé explicitement : si le contenu (surtout la zone photo) dépasse la hauteur de
   la popup (max-height:80vh sur .article-list, overflow:hidden), il doit être possible
   de défiler jusqu'en bas plutôt que le voir coupé. Même principe que
   .profile-popup__body (déjà en place) — copié à l'identique, y compris le piège
   overflow-x déjà rencontré et corrigé là-bas (voir son commentaire, style.css) :
   #createArticleBtn a .idiaa-btn, donc le même hover scale(1.02) ferait apparaître une
   scrollbar horizontale transitoire sans ce overflow-x:hidden explicite. Plain block
   flow à l'intérieur (pas display:flex) — .profile-popup__body ne l'est pas non plus,
   chaque enfant garde sa propre marge comme avant l'ajout de ce wrapper. */
.article-list__body {
  overflow-y: auto;
  overflow-x: hidden;
  flex: 1 1 auto;
  min-height: 0;
}

/* Gap de 13px entre la zone photo et "Ajouter", demandé explicitement. Largeur fixe
   358px (même valeur que #createArticleBtn/#newArticleCancel ci-dessous), demandé
   explicitement — même principe que #newIngredientImageZone (popup "Nouvel
   ingrédient", recipe-edit.php) : margin gauche/droite auto pour centrer, cette popup
   étant elle aussi un flex column (.article-list__body ne l'est pas, mais .upload-zone
   reste block-level, donc le centrage par margin:auto fonctionne pareil). */
#newArticleImageZone {
  width: 358px;
  margin-left: auto;
  margin-right: auto;
  margin-bottom: 13px;
}

/* .form-error (#newArticleError) réserve 22px (min-height:18px + margin-top:4px, voir
   sa règle de base) même vide — fausserait le gap de 13px demandé entre "Ajouter" et
   "Fermer" si elle restait dans le flux. Même technique que #profileForm/#emailForm/
   #passwordForm .form-error:empty (voir Popup profil, CLAUDE.md) : masquée tant que
   vide, réapparaît normalement dès qu'un message d'erreur y est posé. */
#newArticleForm .form-error:empty {
  display: none;
}

/* Quantité/Unité sur la même ligne (popup "Nouveau produit", shopping-list-view.php),
   demandé explicitement — toujours en ligne, pas seulement à partir de 900px comme
   .form-row (recipe-edit.php, pensé pour une page large qui peut aussi s'afficher en
   colonne sur petit écran) : cette popup reste une colonne étroite (max-width:420px)
   quel que soit le viewport, donc pas besoin/pas de sens à empiler ces deux champs même
   sur mobile. */
.new-article-qty-row {
  display: flex;
  gap: 16px;
}

.new-article-qty-row .idiaa-field {
  flex: 1;
  min-width: 0;
}

.article-list .zone-search-articles {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items: center;
  gap: 10px;
  padding-bottom: 10px;
}

.article-list .zone-search-articles .input {
  flex: 1;
  margin-bottom: 0;
}

.article-list .zone-search-articles svg {
  height: 26px;
  width: 26px;
  flex-shrink: 0;
  fill: var(--accent);
  cursor: pointer;
  transition: 0.2s;
}

.article-list .zone-search-articles svg:hover {
  fill: var(--svg-hover);
  transform: scale(1.15);
}

.article-list .zone-liste-articles {
  overflow-y: auto;
  max-height: 420px;
}

.zone-liste-articles .shopping-article-item {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 8px;
  padding: 10px;
  border-radius: 8px;
  transition: background-color 0.2s;
  border-bottom: 1px solid var(--border);
}

.zone-liste-articles .shopping-article-item:hover {
  background-color: var(--accent-tint);
}

.zone-liste-articles .shopping-article-item img,
.zone-liste-articles .shopping-article-item svg:not(.shopping-article-item__add) {
  width: 40px;
  height: 40px;
  flex-shrink: 0;
  object-fit: contain;
  color: var(--text-muted);
}

.zone-liste-articles .shopping-article-item .nom_article {
  flex: 1;
  font-size: 14px;
  color: var(--text);
}

.zone-liste-articles .shopping-article-item .input-qte {
  flex-grow: 0;
  width: 60px;
  height: 30px;
  border: 1px solid var(--text-muted);
  border-radius: var(--radius-sm);
  outline: none;
  padding: 5px;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  font-size: 14px;
  color: var(--text);
  background-color: var(--surface);
  box-sizing: border-box;
  transition: .3s;
}

.zone-liste-articles .shopping-article-item .input-qte:focus {
  border-color: var(--accent);
  box-shadow: 0 0 5px 0 var(--accent);
}

@media (max-width: 899px) {
  .zone-liste-articles .shopping-article-item .input-qte {
    font-size: 16px;
  }
}

.zone-liste-articles .shopping-article-item .qte_article {
  font-size: 13px;
  color: var(--text-muted);
  width: 48px;
  flex-shrink: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.zone-liste-articles .shopping-article-item .shopping-article-item__action {
  position: relative;
  height: 26px;
  width: 26px;
  flex-shrink: 0;
}

.zone-liste-articles .shopping-article-item .shopping-article-item__action svg {
  position: absolute;
  inset: 0;
  height: 100%;
  width: 100%;
  transition: opacity 0.3s ease;
}

.zone-liste-articles .shopping-article-item .shopping-article-item__add {
  color: var(--accent);
  cursor: pointer;
  opacity: 1;
  transition: color 0.2s ease, transform 0.2s ease;
}

.zone-liste-articles .shopping-article-item .shopping-article-item__add:hover {
  color: var(--svg-hover);
  transform: scale(1.15);
}

.zone-liste-articles .shopping-article-item .shopping-article-item__check {
  opacity: 0;
  pointer-events: none;
}

.zone-liste-articles .shopping-article-item .shopping-article-item__action.is-added {
  pointer-events: none;
}

.zone-liste-articles .shopping-article-item .shopping-article-item__action.is-added .shopping-article-item__add {
  opacity: 0;
}

.zone-liste-articles .shopping-article-item .shopping-article-item__action.is-added .shopping-article-item__check {
  opacity: 1;
}

.article-list #articlePickerCancel {
  width: 100%;
  margin-top: 10px;
  height: 30px;
  flex-shrink: 0;
}

/* Gap de 13px avec "Ajouter" (demandé explicitement, voir aussi
   #newArticleImageZone/.form-error:empty ci-dessus) — séparée de #articlePickerCancel
   (qui garde ses 10px, pas concerné par cette demande) : les deux boutons "Fermer"
   n'ont plus la même valeur, ne peuvent plus partager une seule règle. flex-shrink:0 :
   #newArticleCancel est, comme #profilePopupClose, un enfant flex direct du panneau
   (.article-list) hors de la zone qui défile (.article-list__body, flex:1 1 auto) — sans
   ça il pourrait se faire compresser sous sa hauteur déclarée quand le contenu dépasse
   max-height:80vh (même piège que #profilePopupClose/#pickerCancel, voir Popup profil). */
/* Même largeur que "Ajouter" (calc(100% - 40px), voir #createArticleBtn plus bas),
   demandé explicitement — les deux boutons de cette popup ont donc de nouveau la même
   largeur, après le tour précédent qui les avait désalignés. */
.article-list #newArticleCancel {
  width: calc(100% - 40px);
  margin: 0 auto;
  height: 30px;
  flex-shrink: 0;
}

/* Même hauteur que "Fermer" (30px), demandé explicitement — même technique déjà
   utilisée pour #createIngredientBtn (recipe-edit.php, voir Popup de sélection
   d'ingrédient / Nouvel ingrédient) : .btn (classe de base) a son propre padding
   vertical (13px), remplacé par un padding horizontal seul pour laisser height faire
   effet. Gap avec "Fermer" (10px, demandé explicitement) : porté ici, sur "Ajouter"
   lui-même (margin-bottom) plutôt que sur #newArticleCancel (margin-top, retiré
   ci-dessus) — un seul endroit pour ce gap, pas les deux cumulés.
   Largeur : 40px de moins que la pleine largeur — d'abord 20px (même réduction que
   "Fermer"), puis 20px de plus rien que sur "Ajouter" (deux demandes explicites
   successives) ; #newArticleCancel a ensuite été réaligné sur cette même largeur
   (calc(100% - 40px) lui aussi, voir sa règle ci-dessus) — les deux boutons ont donc de
   nouveau la même largeur. Recentré (margin auto) comme "Fermer". */
#createArticleBtn {
  height: 30px;
  padding: 0 16px;
  width: calc(100% - 40px);
  margin: 0 auto 10px;
}

/* ---------- En-tête de liste (ex. "Liste des recettes" + limite + icône +) ---------- */
.list-header {
  display: flex;
  align-items: flex-end;
  gap: 16px;
  margin-bottom: 16px;
}

.list-header h2 {
  margin: 0;
  font-size: 20px;
  font-weight: 600;
}

/* Variante icône + titre (ex. "Illustration") : centrée verticalement, contrairement
   au flex-end par défaut (pensé pour titre + badge texte + icône "+" de index.php). */
.list-header--icon {
  align-items: center;
}

.list-header__limit {
  font-size: 13px;
  color: var(--text-muted);
}

.list-header__add {
  margin-left: auto;
}

/* Icône "+" simple (pas de bouton circulaire) : cf. charte fournie. */
.icon-plus {
  display: inline-flex;
  width: 26px;
  height: 26px;
  color: var(--accent);
  cursor: pointer;
  transition: transform 0.15s ease, color 0.2s ease;
  border: none;
  background: none;
  padding: 0;
}

.icon-plus:hover {
  transform: scale(1.15);
  color: var(--svg-hover);
}

/* ---------- Tableaux (recettes, ingrédients d'une recette) ---------- */
.data-table {
  width: 100%;
  /* table-layout:auto (par défaut) recalcule la largeur des colonnes selon le contenu
     des lignes actuellement affichées : filtrer par type (recipes.js) changeait donc la
     largeur des colonnes selon les libellés visibles. fixed + <colgroup> (voir
     index.php) fige des largeurs indépendantes du contenu affiché. */
  table-layout: fixed;
  /* border-collapse:collapse faisait entrer la bordure du <table> en conflit avec les
     border-bottom des cellules, qui l'effaçaient sur les bords — separate (comme
     .recipe-ingredients) laisse la bordure du tableau s'afficher normalement. */
  border-collapse: separate;
  border-spacing: 0;
  background: var(--surface);
  /* Même rayon et bordure que .recipe-ingredients (tableau d'ingrédients,
     recipe-edit.php / recipe-view.php), pas --radius-md. */
  border-radius: 8px;
  border: 1px solid var(--text-muted);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
  margin-bottom: 16px;
}

.data-table th,
.data-table td {
  padding: 12px;
  text-align: left;
  border-bottom: 1px solid var(--border);
  font-size: 14px;
}

.data-table__col--thumb {
  width: 70px;
}

.data-table__col--type {
  width: 140px;
}

.data-table__col--prep-time {
  width: 110px;
}

/* Les cellules n'affichent que l'icône + le nombre (voir recipes.js), mais l'en-tête a
   besoin de place pour "Personnes" + le chevron du filtre + le padding — 70px les
   coupait (rognés par overflow:hidden de .data-table). */
.data-table__col--servings {
  width: 130px;
}

/* Colonne "Articles" (nombre non cochés) — shopping-lists.php. */
.data-table__col--shopping-count {
  width: 100px;
}

/* Icône menu (⋮, voir .row-menu-trigger plus haut) — partagée par .data-table
   (recettes) ET les listes de courses (même classe de base). 0 par défaut/tactile (le
   menu n'y sert pas, voir sa règle générique) : ne réserve de place que sur pointeur
   fin, où .row-menu-trigger redevient un inline-flex normal.
   58px (pas 40px) : piège rencontré en vérifiant le décalage de 10px demandé sur
   .row-menu-trigger (margin-right) — avec seulement 40px, le contenu (icône 24px +
   padding 12px de chaque côté de la cellule = 48px) dépassait déjà la largeur de la
   colonne ; Chromium ignore alors le text-align:right (et donc la marge) dans cette
   situation de débordement, l'icône restait plaquée au même endroit quelle que soit la
   marge. 58px laisse assez de place pour icône + padding + les 10px de marge sans
   débordement, condition nécessaire pour que le décalage prenne effet. */
.data-table__col--menu {
  width: 0;
}

/* Colonne dédiée pour .recipe-row-swipe__actions / .shopping-list-row-swipe__actions
   (dernier <td>, jamais dans le flux normal — display:none sur pointeur fin,
   position:absolute sur tactile) : largeur 0 dans tous les cas, mais DÉCLARÉE (voir
   .shopping-items-table__col--actions, style.css, pour le piège rencontré sans elle). */
.data-table__col--actions {
  width: 0;
}

@media (hover: hover) and (pointer: fine) {
  .data-table__col--menu {
    width: 58px;
  }
}

/* Type/Préparation/Personnes centrés sous leur en-tête (le bouton/texte de l'en-tête est
   inline-flex, donc centré comme le contenu par text-align). */
.data-table th:nth-child(3),
.data-table td:nth-child(3),
.data-table th:nth-child(4),
.data-table td:nth-child(4),
.data-table th:nth-child(5),
.data-table td:nth-child(5) {
  text-align: center;
}

/* Colonnes "Préparation" / "Personnes" : desktop uniquement (même seuil que le reste de
   l'app). Le <col> ET les cellules doivent être masqués ensemble — un <col> caché
   n'entraîne pas forcément l'effacement des cellules dans tous les moteurs, et une
   cellule cachée seule laisserait la largeur du <col> réservée (colonne "fantôme")
   avec table-layout:fixed. */
.data-table__col--optional,
.data-table__cell--optional {
  display: none;
}

@media (min-width: 900px) {
  .data-table__col--optional {
    display: table-column;
  }

  .data-table__cell--optional {
    display: table-cell;
  }
}

/* Cellule image : padding réduit pour ne pas écraser la vignette (50px) dans les 70px
   de .data-table__col--thumb. */
.data-table td:first-child {
  padding: 10px;
}

.data-table td:nth-child(2) {
  padding-left: 0;
}

.data-table th {
  color: var(--text);
  font-weight: 600;
  font-size: 15px;
  background: var(--surface-alt);
}

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

/* Ligne entière cliquable (recipes.js gère la navigation en JS, pas juste le lien sur le
   nom) — survol léger du fond en desktop uniquement, demandé explicitement ("pas
   nécessaire en mode mobile" : au toucher, :hover resterait "collé" après un tap tant
   qu'on n'a pas touché ailleurs, plutôt gênant). Même teinte que les autres lignes de
   liste survolables de l'app (.zone-liste-ingr .ingredient-item, .recipe-share-item). */
@media (min-width: 900px) {
  .data-table tbody tr {
    cursor: pointer;
  }

  .data-table tbody tr:hover {
    background-color: var(--accent-tint);
  }
}

/* ---------- Éditer/Partager/Supprimer une recette (index.php) ---------- */
/* Nouveau concept de liste, demandé explicitement : la liste des recettes gagne une
   icône menu (voir .row-menu-trigger plus haut) + un glissement tactile révélant
   Éditer/Partager/Supprimer — n'existait pas avant (la suppression se faisait depuis le
   formulaire d'édition, "Supprimer" y a été retiré ; l'édition depuis la page de
   visualisation, "Éditer" y a été retiré aussi — voir recipe-edit.php/recipe-view.php).
   Même famille de technique que .shopping-list-row-swipe (shopping-lists.js) : VRAIE
   ligne de tableau (<tr>/<td>), pas un <div>. */
.recipe-row-swipe {
  position: relative;
}

.recipe-row-swipe__link {
  transition: transform 0.2s ease;
}

.recipe-row-swipe.is-dragging .recipe-row-swipe__link {
  transition: none;
}

/* td.xxx (élément + classe), pas juste .xxx : .data-table th/td (élément + classe aussi)
   règle la base en text-align:left avec la même spécificité — voir le même correctif sur
   .shopping-list-row-swipe__menu (style.css) pour le détail du piège. text-align:right
   (pas center) : la marge-droite de .row-menu-trigger a besoin d'un bord droit réel à
   écarter. */
td.recipe-row-swipe__menu {
  text-align: right;
}

@media (hover: hover) and (pointer: fine) {
  .recipe-row-swipe:hover .row-menu-trigger,
  .recipe-row-swipe:focus-within .row-menu-trigger,
  .recipe-row-swipe.row-menu-open .row-menu-trigger {
    opacity: 1;
    pointer-events: auto;
  }

  /* Cellule de glissement tactile : ne sert pas ici (voir CLAUDE.md / Détection tactile
     vs pointeur) — le menu s'en charge, pas de colonne réservée pour elle non plus (pas
     de <col> dédié dans le colgroup, voir index.php). */
  .recipe-row-swipe__actions {
    display: none;
  }
}

.recipe-row-swipe__action {
  border: none;
  cursor: pointer;
  padding: 0;
}

@media (hover: none), (pointer: coarse) {
  /* --reveal : largeur réelle de .recipe-row-swipe__actions (3 actions ici), posée en JS
     à l'ouverture — voir swipe-row.js. 168px (56px × 3) de repli tant que JS n'a rien
     posé. */
  .recipe-row-swipe.is-swiped .recipe-row-swipe__link {
    transform: translateX(calc(-1 * var(--reveal, 168px)));
  }

  .recipe-row-swipe__actions {
    display: flex;
    position: absolute;
    inset: 0 0 0 auto;
    background: var(--surface-alt);
    /* translateX(100%) : décale le bloc de SA PROPRE largeur, quel que soit le nombre
       d'actions qu'il contient — voir le même choix sur .people-row-swipe__actions
       (friends.js) pour le détail (piège du glassmorphism Îdiää). */
    transform: translateX(100%);
    transition: transform 0.2s ease;
  }

  .recipe-row-swipe.is-dragging .recipe-row-swipe__actions {
    transition: none;
  }

  .recipe-row-swipe.is-swiped .recipe-row-swipe__actions {
    transform: translateX(0);
  }

  .recipe-row-swipe__action {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 56px;
    font-size: 12px;
    color: var(--text);
    text-align: center;
  }

  /* Éditer/Partager : texte temporaire (aucune icône fournie pour l'instant, voir
     CLAUDE.md / Icônes — le projet n'invente pas d'icône approximative), à remplacer dès
     qu'une icône sera fournie — même traitement que Supprimer ci-dessous, qui a déjà une
     vraie icône (deleteFriendIconSvg, réutilisée telle quelle). */
  .recipe-row-swipe__action svg {
    width: 28px;
    height: 28px;
  }
}

/* Colonne "Type" filtrable (recipes.js) : bouton dans l'en-tête + menu contextuel,
   positionné en JS par rapport à .data-table-wrap (voir plus bas). */
.data-table-wrap {
  position: relative;
}

.th-filter,
.th-sort {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  font: inherit;
  font-weight: inherit;
  color: inherit;
  cursor: pointer;
}

.th-filter svg,
.th-sort svg {
  width: 12px;
  height: 12px;
  flex-shrink: 0;
}

/* Filtre actif : bouton (texte + symbole) en accent, comme les autres états actifs de
   l'app (nav, icônes "+"). */
.th-filter.is-active {
  color: var(--accent);
}

/* Tri "Recette" (recipes.js) : croissant par défaut, la flèche pivote à 180° au clic
   (ordre décroissant) avec une petite animation — même transition douce que le reste
   de l'app (0.2s ease). */
.th-sort svg {
  /* +80% par rapport aux 12px partagés avec .th-filter (chevrons Type/Personnes) :
     propre à l'icône de tri, ne touche pas les autres. */
  width: 22px;
  height: 22px;
  transition: transform 0.2s ease;
}

.th-sort.is-desc svg {
  transform: rotate(180deg);
}

.th-sort.is-desc {
  color: var(--accent);
}

.th-filter__menu {
  position: absolute;
  min-width: 160px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-md);
  overflow: hidden;
  z-index: 20;
  font-weight: 400;
  font-size: 14px;
}

.th-filter__menu[hidden] {
  display: none;
}

.th-filter__option {
  display: block;
  width: 100%;
  padding: 10px 14px;
  background: none;
  border: none;
  border-bottom: 1px solid var(--border);
  text-align: left;
  cursor: pointer;
  color: var(--text);
  font: inherit;
}

.th-filter__option:last-child {
  border-bottom: none;
}

.th-filter__option:hover {
  background: var(--surface-alt);
}

.th-filter__option.is-selected {
  color: var(--accent);
  font-weight: 600;
}

.data-table tbody a {
  color: var(--text);
  display: flex;
  align-items: center;
  gap: 4px;
}

.data-table__thumb {
  width: 50px;
  height: 50px;
  border-radius: var(--radius-sm);
  object-fit: cover;
  background: var(--surface-alt);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--text-muted);
}

.data-table__thumb svg {
  width: 18px;
  height: 18px;
}

/* Colonne "Personnes" : icône + nombre seulement (recipes.js), pas de mot "personnes". */
.data-table__servings {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

/* Même taille que .form-group__icon (champs des formulaires recette/profil). */
.data-table__servings-icon {
  width: 20px;
  height: 30px;
  flex-shrink: 0;
  color: var(--text-muted);
}

/* ---------- Ingrédients de la recette (recipe-edit.php) ---------- */
/* Décalées de 30px (largeur .form-group__icon + gap) pour s'aligner avec le texte des
   champs de saisie au-dessus, qui n'ont pas d'icône ici pour faire ce décalage naturellement. */
.titre-add-ingredient {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin: 30px 0 15px 30px;
  width: calc(100% - 30px);
}

/* Même format que "Illustration" (.list-header h2) : 20px / 600. */
.titre-add-ingredient h2 {
  margin: 0;
  font-size: 20px;
  font-weight: 600;
}

.titre-add-ingredient svg {
  height: 26px;
  width: 26px;
  fill: var(--accent);
  cursor: pointer;
  transition: 0.2s;
}

.titre-add-ingredient svg:hover {
  fill: var(--svg-hover);
  transform: scale(1.15);
}

/* Même décalage de 30px que .titre-add-ingredient, pour aligner "Étapes de la
   recette" avec "Ingrédients de la recette" (et avec le texte des champs à icône
   au-dessus). */
.titre-section {
  margin: 30px 0 15px 30px;
  font-size: 20px;
  font-weight: 600;
}

/* Décalage de 30px pour s'aligner avec les champs à icône : seulement sur la page
   d'édition (#recipeIngredientsTable) — sur recipe-view.php (pas d'icônes au-dessus),
   .recipe-ingredients garde sa pleine largeur, alignée sous la photo. */
#recipeIngredientsTable {
  width: calc(100% - 30px);
  margin-left: 30px;
}

.recipe-ingredients {
  width: 100%;
  background-color: var(--surface);
  border-radius: 8px;
  overflow: hidden;
  border: 1px solid var(--text-muted);
}

/* Espace avant "Étapes de la recette" (recipe-view.php) : scopé à cette page (pas
   .recipe-ingredients en général) pour ne pas toucher recipe-edit.php, où le titre
   "Étapes" a déjà sa propre marge (.titre-section). */
#recipeViewIngredientsTable {
  margin-bottom: 24px;
}

.recipe-ingredients td {
  padding: 12px;
  text-align: left;
}

.ingredient-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  border-bottom: 1px solid var(--border);
}

.ingredient-row img {
  width: 50px;
  height: 50px;
  object-fit: contain;
}

.ingredient-row .ligne-ingedient {
  width: 100%;
}

.ingredient-row svg {
  height: 30px;
  width: 30px;
  cursor: pointer;
}

/* ---------- Popup de sélection d'ingrédient (fond flouté, comme .loading-overlay) ---------- */
.ingredient-selector {
  display: none;
  position: fixed;
  inset: 0;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.2);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  z-index: 100;
  padding: 20px;
}

.ingredient-selector.is-open {
  display: flex;
}

.ingredient-list {
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 420px;
  max-height: 80vh;
  overflow: hidden;
  background-color: var(--surface);
  border: 1px solid var(--text-muted);
  border-radius: 8px;
  padding: 10px;
  box-shadow: 6px 6px 12px var(--text-muted);
}

.ingredient-list .zone-search-ingr {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items: center;
  gap: 10px;
  padding-bottom: 10px;
}

.ingredient-list .zone-search-ingr .input {
  flex: 1;
  margin-bottom: 0;
}

.ingredient-list .zone-search-ingr svg {
  /* Même taille que le "+" d'ajout d'ingrédient à la recette (.titre-add-ingredient
     svg). */
  height: 26px;
  width: 26px;
  flex-shrink: 0;
  fill: var(--accent);
  cursor: pointer;
  transition: 0.2s;
}

.ingredient-list .zone-search-ingr svg:hover {
  fill: var(--svg-hover);
  transform: scale(1.15);
}

.ingredient-list .zone-liste-ingr {
  overflow-y: auto;
  max-height: 420px;
}

.zone-liste-ingr .ingredient-item {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 8px;
  padding: 10px;
  border-radius: 8px;
  transition: background-color 0.2s;
  border-bottom: 1px solid var(--border);
}

.zone-liste-ingr .ingredient-item:hover {
  background-color: var(--accent-tint);
}

.zone-liste-ingr .ingredient-item img,
.zone-liste-ingr .ingredient-item svg:not(.ingredient-item__add) {
  width: 40px;
  height: 40px;
  flex-shrink: 0;
  object-fit: contain;
  color: var(--text-muted);
}

.zone-liste-ingr .ingredient-item .nom_ingr {
  flex: 1;
  font-size: 14px;
  color: var(--text);
}

.zone-liste-ingr .ingredient-item .input-qte {
  flex-grow: 0;
  width: 60px;
  height: 30px;
  border: 1px solid var(--text-muted);
  border-radius: var(--radius-sm);
  outline: none;
  padding: 5px;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  font-size: 14px;
  color: var(--text);
  background-color: var(--surface);
  box-sizing: border-box;
  transition: .3s;
}

.zone-liste-ingr .ingredient-item .input-qte:focus {
  border-color: var(--accent);
  box-shadow: 0 0 5px 0 var(--accent);
}

/* Doit venir après la règle de base ci-dessus (même spécificité) pour gagner sur mobile
   — voir la note sur le zoom iOS Safari plus haut dans ce fichier. */
@media (max-width: 899px) {
  .zone-liste-ingr .ingredient-item .input-qte {
    font-size: 16px;
  }
}

/* Largeur fixe (pas min-width) : sinon une unité plus longue ("gousse" pour l'aïl)
   grandit et pousse .input-qte plus à gauche, désalignant les lignes entre elles. */
.zone-liste-ingr .ingredient-item .qte_ingr {
  font-size: 13px;
  color: var(--text-muted);
  width: 48px;
  flex-shrink: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Superpose l'icône "ajouter" et le check vert : fondu enchaîné de l'une vers l'autre
   au clic (voir .is-added, ajoutée en JS), plutôt que de remplacer le SVG dans le DOM. */
.zone-liste-ingr .ingredient-item .ingredient-item__action {
  position: relative;
  height: 26px;
  width: 26px;
  flex-shrink: 0;
}

.zone-liste-ingr .ingredient-item .ingredient-item__action svg {
  position: absolute;
  inset: 0;
  height: 100%;
  width: 100%;
  transition: opacity 0.3s ease;
}

/* color (pas fill) : le <path> du SVG a fill="currentColor"/stroke="currentColor" en
   attribut de présentation, qui ne suit que la propriété CSS "color", pas "fill". */
.zone-liste-ingr .ingredient-item .ingredient-item__add {
  color: var(--accent);
  cursor: pointer;
  opacity: 1;
  transition: color 0.2s ease, transform 0.2s ease;
}

.zone-liste-ingr .ingredient-item .ingredient-item__add:hover {
  color: var(--svg-hover);
  transform: scale(1.15);
}

.zone-liste-ingr .ingredient-item .ingredient-item__check {
  opacity: 0;
  pointer-events: none;
}

.zone-liste-ingr .ingredient-item .ingredient-item__action.is-added {
  pointer-events: none;
}

.zone-liste-ingr .ingredient-item .ingredient-item__action.is-added .ingredient-item__add {
  opacity: 0;
}

.zone-liste-ingr .ingredient-item .ingredient-item__action.is-added .ingredient-item__check {
  opacity: 1;
}

/* Hauteur alignée sur #profilePopupClose (popup profil, 30px) au lieu des 40px hérités
   de .btn--neu — demandé pour que les deux boutons "Fermer" fassent la même hauteur.
   Largeur fixe 358px + 20px de marge en dessous, demandé explicitement (même valeur que
   #newIngredientCancel/#createIngredientBtn ci-dessous — popup "Nouvel ingrédient" —
   sans que les deux popups ne partagent la même règle, elles ont divergé depuis).
   margin gauche/droite auto : nécessaire pour centrer, .ingredient-list étant un flex
   column — un enfant flex à largeur explicite ne "stretch" plus (voir commentaire de
   #newIngredientCancel). */
.ingredient-list #pickerCancel {
  width: 358px;
  margin: 10px auto 20px;
  height: 30px;
  /* Même piège que #profilePopupClose (popup profil, voir plus bas) : .ingredient-list
     est aussi un flex column (display:flex; flex-direction:column) et ces boutons en
     sont des enfants directs au même titre que .zone-search-ingr/.zone-liste-ingr —
     sans flex-shrink:0, ils se feraient comprimer sous leurs 30px déclarés dès que le
     contenu dépasse l'espace disponible sous max-height:80vh (repéré sur capture par
     l'utilisateur avec une vraie liste d'ingrédients — pas reproduit lors de la
     vérification initiale, faite avec un contenu factice trop court pour déclencher
     la compression). */
  flex-shrink: 0;
}

/* Largeur fixe 358px (demandé explicitement, même valeur que "Ajouter"/"Fermer" de la
   popup "Nouveau produit", shopping-list-view.php) — séparée de #pickerCancel
   ci-dessus (qui garde 100%, pas concerné par cette demande) : les deux boutons
   "Fermer" n'ont plus la même largeur, ne peuvent plus partager une seule règle.
   margin:auto (axe horizontal) : nécessaire pour centrer, .ingredient-list étant un
   flex column — un enfant flex avec une largeur explicite ne "stretch" plus (voir
   .ingredient-list #pickerCancel ci-dessus) et se retrouverait plaqué à gauche sans ça. */
.ingredient-list #newIngredientCancel {
  width: 358px;
  /* 20px sous le bouton, demandé explicitement — .ingredient-list a déjà 10px de
     padding (style.css), donc 30px d'espace au total jusqu'au bord du panneau. */
  margin: 10px auto 20px;
  height: 30px;
  flex-shrink: 0;
}

/* ---------- Popup "Mon profil" (topbar), includes/profile-popup.php ---------- */
/* Même traitement que .ingredient-selector/.ingredient-list (picker d'ingrédient) :
   overlay plein écran flouté, panneau centré — classes dédiées plutôt que réutilisées
   telles quelles pour ne pas mélanger deux popups sans rapport sous un même nom, mais
   valeurs identiques. */
.profile-popup {
  display: none;
  position: fixed;
  inset: 0;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.2);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  z-index: 100;
  padding: 20px;
}

.profile-popup.is-open {
  display: flex;
}

.profile-popup__panel {
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 420px;
  max-height: 80vh;
  /* Le panneau lui-même ne défile plus (voir .profile-popup__body ci-dessous) : sans
     ça, #profilePopupClose (dernier enfant) défilait avec le reste du contenu et
     disparaissait hors de vue quand le contenu dépassait 80vh — pas "visible en
     permanence" contrairement à #pickerCancel dans le picker d'ingrédient
     (.ingredient-list), qui reste hors de la zone de défilement (.zone-liste-ingr). */
  overflow: hidden;
  background-color: var(--surface);
  border: 1px solid var(--text-muted);
  border-radius: 8px;
  padding: 20px;
  box-shadow: 6px 6px 12px var(--text-muted);
  /* Effet "point de départ = icône profil" (voir profile.js) : agrandissement et
     déplacement animés ensemble via un seul transform (translate + scale), 1s. */
  transition: transform 1s ease, opacity 1s ease;
}

/* Enveloppe tout le contenu du panneau SAUF #profilePopupClose (voir profile-popup.php)
   — c'est cette zone qui défile désormais, pas le panneau entier (overflow:hidden
   ci-dessus). flex:1 1 auto + min-height:0 : occupe l'espace restant sous le padding
   du panneau jusqu'à max-height (80vh) ; min-height:0 nécessaire (défaut "auto" sur un
   enfant flex empêcherait le rétrécissement/scroll interne — piège classique
   flexbox). */
.profile-popup__body {
  overflow-y: auto;
  /* overflow-x explicite, pas laissé à "visible" par défaut : quand overflow-y n'est
     pas "visible" et qu'overflow-x l'est, la spec CSS calcule quand même overflow-x en
     "auto" (pas "visible") — piège trouvé en vérifiant le signalement de l'utilisateur
     ("barre claire sous le bouton, au hover") : .idiaa-btn:hover fait
     transform:scale(1.02) sur ces boutons en pleine largeur (100%), qui dépassent alors
     de quelques px leur conteneur le temps du survol — confirmé (scrollWidth >
     clientWidth exactement le temps du hover, Playwright). overflow-x:auto calculé
     malgré moi faisait apparaître une scrollbar horizontale transitoire à chaque survol
     — invisible en overlay (Linux/macOS) mais rendue en barre claire réservant de
     l'espace sur un navigateur à scrollbars classiques (Windows), d'où le symptôme.
     Pas de défilement horizontal voulu ici de toute façon (page à une colonne) :
     hidden, pas juste laissé au hasard du calcul implicite. */
  overflow-x: hidden;
  flex: 1 1 auto;
  min-height: 0;
}

.profile-popup__panel h2 {
  margin: 0 0 16px;
  font-size: 20px;
  font-weight: 600;
}

/* "Mon profil" + "Se déconnecter" sur la même ligne, bouton collé à droite (la marge
   vient du padding du panneau, pas d'espace supplémentaire). */
.profile-popup__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 16px;
}

.profile-popup__header h2 {
  margin: 0;
}

/* Même famille visuelle que .btn--neu (neumorphique, hover accent -> svg-hover), en
   plus compact ("petit bouton") pour tenir sur la ligne du titre. */
.profile-popup__logout {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* .profile-popup__header est en justify-content:space-between, ce qui colle ce
     bouton (dernier enfant) au bord droit — demandé explicitement, toutes apparences,
     décalé de 15px vers la gauche via cette marge plutôt que de toucher au conteneur. */
  margin-right: 15px;
  padding: 6px 12px;
  color: var(--accent);
  font-size: 12px;
  font-weight: normal;
  border-radius: 0.8em;
  background: var(--bg);
  cursor: pointer;
  border: 1px solid var(--ombre);
  box-shadow: 3px 3px 6px var(--ombre), -3px -3px 6px var(--surface);
  transition: all 0.2s ease-in-out;
}

.profile-popup__logout:hover {
  color: var(--svg-hover);
  box-shadow: 3px 3px 6px var(--surface), -3px -3px 6px var(--surface);
}

/* Bouton compact générique — même habillage neumorphique que .profile-popup__logout
   (sans sa marge/positionnement, spécifiques à la topbar) : Accepter/Refuser/Annuler/
   Inviter (.people-row__action, friends.php), "Envoyer" du composeur de chat. Compose
   avec .idiaa-btn (même mécanisme que .btn--neu/.btn) pour le rendu Îdiää transparent/
   contour, sans toucher aux dimensions ici. */
.btn--pill {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 6px 14px;
  color: var(--accent);
  font-size: 13px;
  font-weight: 500;
  border-radius: 0.8em;
  background: var(--bg);
  cursor: pointer;
  border: 1px solid var(--ombre);
  box-shadow: 3px 3px 6px var(--ombre), -3px -3px 6px var(--surface);
  transition: all 0.2s ease-in-out;
  white-space: nowrap;
}

.btn--pill:hover {
  color: var(--svg-hover);
  box-shadow: 3px 3px 6px var(--surface), -3px -3px 6px var(--surface);
}

.btn--pill:disabled {
  opacity: 0.5;
  cursor: default;
  box-shadow: none;
}

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

/* Variante "danger" (Refuser) : rouge au lieu du bleu/accent par défaut. */
.btn--pill--danger {
  color: var(--danger);
}

.btn--pill--danger:hover {
  color: var(--danger);
  opacity: 0.75;
}

.profile-popup__panel #credentialsSection {
  margin-top: 20px;
  padding-top: 20px;
  border-top: 1px solid var(--border);
}

/* Champs et boutons du panneau profil réduits de 30px et recentrés (20px puis 10px
   lors des demandes précédentes, ajusté à 30px ici) — demandé explicitement, **toutes
   apparences**, seulement ces éléments (le panneau lui-même, son padding, sa largeur
   max. etc. ne changent pas). margin-left/right:auto plutôt qu'un simple centrage par
   le parent : fonctionne aussi bien sur un enfant flex (#profilePopupClose, .btn--neu
   dans .form-actions — un flex-item avec des marges auto se centre sur l'axe
   principal indépendamment de justify-content) que sur un div de flux normal
   (.idiaa-field, display:block, clair/sombre/système). Scopé à .profile-popup__panel
   (via .idiaa-field notamment, classe réutilisée ailleurs dans l'app — recherche
   recettes, recipe-edit.php, popup nouvel ingrédient — donc pas touchée globalement,
   seulement dans ce panneau précis). */
.profile-popup__panel .idiaa-field {
  width: calc(100% - 30px);
  margin-left: auto;
  margin-right: auto;
}

/* Pleine largeur moins 30px (voir ci-dessus), contrairement aux autres .btn--neu du
   panneau (Enregistrer/Changer email/Changer mot de passe) qui gardent 120px via
   .form-actions. */
.profile-popup__panel #profilePopupClose {
  width: calc(100% - 30px);
  margin-left: auto;
  margin-right: auto;
  /* Même écart que .form-actions -> #passwordForm (40px) : voir plus bas. Mesuré
     depuis le bord bas de .profile-popup__body (aucun padding propre), donc même
     rendu qu'avant son introduction. */
  margin-top: 40px;
  /* Même hauteur que les boutons "Changer l'email"/"Changer le mot de passe" —
     et désormais aussi que #pickerCancel du picker d'ingrédient (voir plus haut). */
  height: 30px;
  /* .profile-popup__panel est flex column : sans ça, ce bouton (seul enfant direct du
     panneau parmi les boutons — les autres sont imbriqués dans .profile-popup__body)
     se ferait compresser sous ses 30px par .profile-popup__body (flex: 1 1 auto), qui
     réclame tout l'espace disponible. */
  flex-shrink: 0;
}

/* "Changer le pseudo" / "Changer l'email" / "Changer le mot de passe" (popup profil) :
   pleine largeur moins 30px (voir plus haut), même hauteur/police que les champs de
   saisie (.input:not(textarea), 30px/14px) — au lieu du format 120px aligné à droite
   utilisé pour les paires Enregistrer/Annuler ailleurs dans l'app. */
#profileForm .btn--neu,
#emailForm .btn--neu,
#passwordForm .btn--neu {
  width: calc(100% - 30px);
  margin-left: auto;
  margin-right: auto;
  height: 30px;
  font-size: 14px;
}

/* "Changer le mot de passe" seul, demandé explicitement : espace supplémentaire sous
   ce bouton précis (dernier champ visible de #passwordForm), en plus des 40px déjà
   posés sur #profilePopupClose ci-dessus (les deux s'additionnent : ~60px au total
   avant "Fermer"). */
#passwordForm .btn--neu {
  margin-bottom: 20px;
}

/* Vides par défaut (pas encore d'erreur/succès), ces <p> ne doivent pas réserver
   d'espace : sinon impossible d'aligner le bouton juste sous le dernier champ avec le
   même espacement qu'entre deux champs. :empty les efface tant qu'ils n'ont pas de
   texte (JS les remplit via .textContent, ce qui les fait réapparaître automatiquement
   — aucun changement JS nécessaire). Scopé à ces formulaires : .form-error/.form-success
   gardent leur hauteur réservée habituelle partout ailleurs dans l'app. */
#profileForm .form-error:empty,
#emailForm .form-error:empty,
#emailForm .form-success:empty,
#passwordForm .form-error:empty,
#passwordForm .form-success:empty {
  display: none;
}

/* Le bouton colle au dernier champ avec le même gap qu'entre deux champs (10px) : marge
   nulle ici, la marge basse du champ (10px) suffit — les deux fusionnent (collapse)
   puisque .form-error/.form-success n'occupent plus d'espace entre les deux quand vides. */
#profileForm .form-actions,
#emailForm .form-actions,
#passwordForm .form-actions {
  margin-top: 0;
}

/* Écart entre les deux formulaires (bouton "Changer l'email" -> premier champ de
   #passwordForm) : doublé (10px x2 x2 = 40px) — même valeur reprise pour l'écart
   "Changer le mot de passe" -> "Fermer" (#profilePopupClose ci-dessus). */
#passwordForm {
  margin-top: 40px;
}

/* ---------- Popup d'ajout d'ami (friends.php) ---------- */
/* Mêmes valeurs que .ingredient-selector/.ingredient-list (picker d'ingrédient) — classes
   dédiées plutôt que réutilisées telles quelles, même convention que .profile-popup__panel
   face à ce même picker (deux popups sans rapport ne partagent pas un nom de classe, même
   quand leur rendu est identique). */
.friend-add-overlay {
  display: none;
  position: fixed;
  inset: 0;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.2);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  z-index: 100;
  padding: 20px;
}

.friend-add-overlay.is-open {
  display: flex;
}

.friend-add-panel {
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 420px;
  max-height: 80vh;
  overflow: hidden;
  background-color: var(--surface);
  border: 1px solid var(--text-muted);
  border-radius: 8px;
  padding: 10px;
  box-shadow: 6px 6px 12px var(--text-muted);
}

.friend-add-panel .idiaa-field {
  margin: 0 0 10px;
}

.friend-add-panel__results {
  overflow-y: auto;
  max-height: 420px;
}

.friend-add-panel__empty {
  color: var(--text-muted);
  font-size: 13px;
  text-align: center;
  padding: 20px 10px;
}

/* Même bouton "Fermer" que le picker d'ingrédient/la popup profil (voir .ingredient-list
   #pickerCancel) : pleine largeur, 30px, flex-shrink:0 pour ne pas se faire comprimer si
   .friend-add-panel__results dépasse l'espace disponible sous max-height:80vh. */
.friend-add-panel #friendAddClose {
  width: 100%;
  margin-top: 10px;
  height: 30px;
  flex-shrink: 0;
}

/* ---------- Popup de création de liste (shopping-lists.php) ---------- */
/* Même famille que .friend-add-overlay/.ingredient-selector — classe dédiée plutôt que
   réutilisée, même convention (popups sans rapport ne partagent pas de nom de classe). */
.list-create-overlay {
  display: none;
  position: fixed;
  inset: 0;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.2);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  z-index: 100;
  padding: 20px;
}

.list-create-overlay.is-open {
  display: flex;
}

.list-create-panel {
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 420px;
  max-height: 80vh;
  overflow: hidden;
  background-color: var(--surface);
  border: 1px solid var(--text-muted);
  border-radius: 8px;
  padding: 10px;
  box-shadow: 6px 6px 12px var(--text-muted);
}

.list-create-panel .idiaa-field {
  margin: 0 0 10px;
}

/* Même espacement que .idiaa-field ci-dessus (10px) : cohérent avec le reste du panneau,
   .upload-zone n'a par défaut aucune marge propre (voir sa définition générale).
   Largeur fixe 358px, demandé explicitement (même valeur que "Ajouter"/"Fermer"
   ci-dessous) — margin gauche/droite auto pour centrer, .list-create-panel étant un
   flex column (un enfant à largeur explicite ne "stretch" plus, même piège que
   #newIngredientCancel/#pickerCancel, popup "Nouvel ingrédient"). */
.list-create-panel #listCreateImageZone {
  width: 358px;
  margin-left: auto;
  margin-right: auto;
  margin-bottom: 10px;
}

/* Largeur fixe 358px (demandé explicitement, même valeur que la zone photo ci-dessus),
   centrée (margin gauche/droite auto, même raison). */
.list-create-panel #listCreateSubmit,
.list-create-panel #listCreateCancel {
  width: 358px;
  margin-left: auto;
  margin-right: auto;
  height: 30px;
  flex-shrink: 0;
}

.list-create-panel #listCreateSubmit {
  margin-top: 10px;
}

/* Gap de 13px avec "Ajouter" (demandé explicitement, remplace les 10px partagés
   ci-dessus) + 20px de marge sous le bouton (demandé explicitement). */
.list-create-panel #listCreateCancel {
  margin-top: 13px;
  margin-bottom: 20px;
}

/* ---------- Popup "Passe à Îdiää Pro" (assets/js/subscription-upsell.js) ---------- */
/* Même famille que .list-create-overlay/.ingredient-selector — classe dédiée plutôt que
   réutilisée telle quelle, même convention. Construite en JS et ajoutée à document.body
   (pas de markup PHP dupliqué par page), donc naturellement hors de .idiaa-page-wrap. */
.upsell-overlay {
  display: none;
  position: fixed;
  inset: 0;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.2);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  z-index: 100;
  padding: 20px;
}

.upsell-overlay.is-open {
  display: flex;
}

.upsell-panel {
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 420px;
  max-height: 80vh;
  overflow: hidden;
  background-color: var(--surface);
  border: 1px solid var(--text-muted);
  border-radius: 8px;
  padding: 10px;
  box-shadow: 6px 6px 12px var(--text-muted);
}

.upsell-panel h2 {
  margin: 0 0 8px;
}

.upsell-panel__reason {
  color: var(--text-muted);
  font-size: 14px;
  margin: 0 0 12px;
}

.upsell-benefits {
  list-style: none;
  margin: 0 0 16px;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.upsell-benefits li {
  font-size: 14px;
  padding-left: 24px;
  position: relative;
}

/* Coche devant chaque avantage — même vert que le check du picker d'ingrédient
   (#1ad569), couleur sémantique hors du monochrome Îdiää comme --danger ailleurs. */
.upsell-benefits li::before {
  content: '✓';
  position: absolute;
  left: 0;
  color: #1ad569;
  font-weight: 700;
}

.upsell-panel #upsellSubscribe,
.upsell-panel #upsellClose {
  width: 100%;
  height: 30px;
  flex-shrink: 0;
  margin-top: 10px;
}

:root[data-theme="idiaa"] .upsell-panel {
  background-color: var(--surface-alt);
  -webkit-backdrop-filter: blur(20px);
  backdrop-filter: blur(20px);
}

/* ---------- Popup de partage de liste de courses (shopping-list-view.php) ---------- */
/* Même famille que .friend-add-overlay/.friend-add-panel (friends.php) — classe dédiée
   plutôt que réutilisée telle quelle, même convention (popups sans rapport ne partagent
   pas de nom de classe, même rendu identique). */
.list-share-overlay {
  display: none;
  position: fixed;
  inset: 0;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.2);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  z-index: 100;
  padding: 20px;
}

.list-share-overlay.is-open {
  display: flex;
}

.list-share-panel {
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 420px;
  max-height: 80vh;
  overflow: hidden;
  background-color: var(--surface);
  border: 1px solid var(--text-muted);
  border-radius: 8px;
  padding: 10px;
  box-shadow: 6px 6px 12px var(--text-muted);
}

.list-share-panel__results {
  overflow-y: auto;
  max-height: 420px;
}

.list-share-panel__empty {
  color: var(--text-muted);
  font-size: 13px;
  text-align: center;
  padding: 20px 10px;
}

.list-share-panel #listShareClose {
  width: 100%;
  margin-top: 10px;
  height: 30px;
  flex-shrink: 0;
}

:root[data-theme="idiaa"] .list-share-panel {
  background-color: var(--surface-alt);
  -webkit-backdrop-filter: blur(20px);
  backdrop-filter: blur(20px);
}

/* Survol de ligne (demandé explicitement, "comme la popup de partage d'une recette") :
   même teinte que .recipe-share-people-panel .people-row ci-dessous — scopée au
   sélecteur [data-friend-id] (posé en JS uniquement sur un ami PAS ENCORE partagé,
   shopping-lists.js) : cliquer n'importe où sur cette ligne partage la liste avec lui,
   plus de bouton "Partager" dédié. La ligne d'un ami DÉJÀ partagé n'a pas cet attribut
   (retirer l'accès reste un bouton explicite, "Partagé ✕") — pas de curseur/survol
   trompeur dessus. */
.list-share-panel .people-row[data-friend-id] {
  cursor: pointer;
  transition: background-color 0.2s ease;
}

.list-share-panel .people-row[data-friend-id]:hover {
  background-color: var(--accent-tint);
}

/* ---------- Popup "Partager" une recette (menu contextuel, index.php) ---------- */
/* Même famille visuelle que .list-share-overlay/.friend-add-overlay ci-dessus — classe
   dédiée plutôt que réutilisée (même convention que le reste du projet, voir CLAUDE.md) :
   #recipeShareOverlay existe déjà sur chat.php pour l'inverse (choisir une RECETTE à
   partager dans la conversation ouverte), contenu différent malgré le nom proche. */
.recipe-share-people-overlay {
  display: none;
  position: fixed;
  inset: 0;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.2);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  z-index: 100;
  padding: 20px;
}

.recipe-share-people-overlay.is-open {
  display: flex;
}

.recipe-share-people-panel {
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 420px;
  max-height: 80vh;
  overflow: hidden;
  background-color: var(--surface);
  border: 1px solid var(--text-muted);
  border-radius: 8px;
  padding: 10px;
  box-shadow: 6px 6px 12px var(--text-muted);
}

.recipe-share-people-panel__results {
  overflow-y: auto;
  max-height: 420px;
}

.recipe-share-people-panel__empty {
  color: var(--text-muted);
  font-size: 13px;
  text-align: center;
  padding: 20px 10px;
}

/* Survol de ligne (demandé explicitement, "comme la liste des recettes") : même teinte
   que .data-table tbody tr:hover (index.php), scopé à cette popup plutôt qu'à .people-row
   en général (les autres usages de cette classe — invitations, résultats de recherche
   d'ami, liste des conversations — ne sont pas cliquables de la même façon). */
.recipe-share-people-panel .people-row {
  cursor: pointer;
  transition: background-color 0.2s ease;
}

.recipe-share-people-panel .people-row:hover {
  background-color: var(--accent-tint);
}

.recipe-share-people-panel #recipeSharePeopleClose {
  width: 100%;
  margin-top: 10px;
  height: 30px;
  flex-shrink: 0;
}

:root[data-theme="idiaa"] .recipe-share-people-panel {
  background-color: var(--surface-alt);
  -webkit-backdrop-filter: blur(20px);
  backdrop-filter: blur(20px);
}

/* ---------- Popup de partage de recette (chat.php) ---------- */
/* Même famille que .friend-add-overlay ci-dessus — dédiée plutôt que réutilisée, même
   convention. */
.recipe-share-overlay {
  display: none;
  position: fixed;
  inset: 0;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.2);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  z-index: 100;
  padding: 20px;
}

.recipe-share-overlay.is-open {
  display: flex;
}

.recipe-share-panel {
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 420px;
  max-height: 80vh;
  overflow: hidden;
  background-color: var(--surface);
  border: 1px solid var(--text-muted);
  border-radius: 8px;
  padding: 10px;
  box-shadow: 6px 6px 12px var(--text-muted);
}

.recipe-share-panel__list {
  overflow-y: auto;
  max-height: 420px;
}

/* Ligne recette cliquable : vignette carrée (même taille/traitement que .data-table__thumb,
   recettes) + nom — pas de bouton "ajouter" séparé, un clic sur la ligne envoie directement
   (voir chat.js), contrairement au picker d'ingrédient qui a une étape de quantité en plus. */
.recipe-share-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px;
  border-radius: 8px;
  border-bottom: 1px solid var(--border);
  cursor: pointer;
  transition: background-color 0.2s;
  color: var(--text);
}

.recipe-share-item:hover {
  background-color: var(--accent-tint);
}

/* Même taille/traitement que .data-table__thumb (liste des recettes) — voir commentaire
   ci-dessus. */
.recipe-share-item img {
  width: 50px;
  height: 50px;
  border-radius: var(--radius-sm);
  object-fit: cover;
  flex-shrink: 0;
}

.recipe-share-item__name {
  flex: 1;
  font-size: 14px;
}

.recipe-share-panel__empty {
  color: var(--text-muted);
  font-size: 13px;
  text-align: center;
  padding: 20px 10px;
}

.recipe-share-panel #recipeShareClose {
  width: 100%;
  margin-top: 10px;
  height: 30px;
  flex-shrink: 0;
}

/* ---------- Conversation (chat.php) ---------- */
/* Avatar + nom de l'ami en tête de page — le nom est déjà dans la topbar ($pageTitle),
   ce bandeau sert surtout à afficher l'avatar (voir chat.php pour le repli silhouette /
   chat.js pour son remplacement par la vraie photo). */
.chat-header {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 16px;
}

.chat-header__name {
  font-size: 18px;
  font-weight: 600;
}

/* Fil de discussion : flux normal de la page (pas de zone à défilement interne) — toute
   .main-content défile, comme le reste de l'app. Plus simple/robuste qu'une hauteur
   calculée sur 100vh (fragile sur mobile, barre d'adresse iOS qui va et vient — même
   classe de piège déjà rencontrée avec background-attachment:fixed, voir CLAUDE.md). */
.chat-thread {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 4px 4px 12px;
}

.chat-bubble {
  max-width: 75%;
  padding: 10px 14px;
  border-radius: var(--radius-md);
  font-size: 14px;
  line-height: 1.4;
  word-break: break-word;
}

.chat-bubble--theirs {
  align-self: flex-start;
  background: var(--surface-alt);
  color: var(--text);
  border-bottom-left-radius: 4px;
}

.chat-bubble--mine {
  align-self: flex-end;
  background: var(--accent);
  color: var(--accent-contrast);
  border-bottom-right-radius: 4px;
}

.chat-bubble--image {
  padding: 4px;
}

.chat-bubble--image img {
  display: block;
  max-width: 100%;
  border-radius: calc(var(--radius-md) - 4px);
  cursor: pointer;
}

/* Carte recette dans une bulle : vignette + nom, cliquable vers recipe-view.php — mêmes
   couleurs que la bulle qui la contient (héritées), juste un fond légèrement creusé pour se
   distinguer d'un message texte. */
.chat-bubble--recipe {
  padding: 8px;
}

.chat-bubble__recipe-link {
  display: flex;
  align-items: center;
  gap: 10px;
  color: inherit;
  text-decoration: none;
}

.chat-bubble__recipe-link img {
  width: 44px;
  height: 44px;
  border-radius: var(--radius-sm);
  object-fit: cover;
  flex-shrink: 0;
}

.chat-bubble__time {
  display: block;
  margin-top: 4px;
  font-size: 11px;
  opacity: 0.7;
}

/* Barre de composition, collée en bas de .chat-page (voir plus bas) — décalée du dock
   mobile comme .fab (même variable --nav-height-mobile + zone de sécurité iOS). */
.chat-composer {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px;
  border-radius: var(--radius-md);
  background: var(--surface);
  border: 1px solid var(--border);
  position: sticky;
  bottom: calc(var(--nav-height-mobile) + env(safe-area-inset-bottom, 0px) + 10px);
}

@media (min-width: 900px) {
  .chat-composer {
    bottom: 10px;
  }
}

.chat-composer__input {
  flex: 1;
  margin-bottom: 0;
}

.chat-composer__icon-btn {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border: none;
  background: none;
  padding: 0;
  color: var(--accent);
  cursor: pointer;
  transition: color 0.2s ease, transform 0.15s ease;
}

.chat-composer__icon-btn:hover {
  color: var(--svg-hover);
  transform: scale(1.15);
}

.chat-composer__icon-btn svg {
  width: 100%;
  height: 100%;
}

/* "Envoyer" (#chatSendBtn) : couleur bleue fixe demandée explicitement, PAS var(--accent)
   comme les deux autres icônes du composeur — var(--accent) devient noir en mode Îdiää
   (monochrome intégral, voir CLAUDE.md / Design system), ce qui aurait rendu cette icône
   invisible/incohérente avec la demande. Même bleu que --accent en thème clair (#007AFF,
   iOS system blue), fixé pour rester bleu dans les 3 thèmes — même famille que les autres
   couleurs sémantiques fixes de l'app (--danger, --svg-hover, --unread-badge). Hérite du
   reste de .chat-composer__icon-btn (taille, survol → orange, transition). */
.chat-composer__send-btn {
  color: #007aff;
}

/* ---------- Formulaire recette : champ précédé d'une icône ---------- */
.form-group {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin-bottom: 16px;
}

.form-group__icon {
  width: 20px;
  height: 30px;
  flex-shrink: 0;
  color: var(--text-muted);
  display: flex;
  align-items: center;
}

.form-group__icon svg {
  width: 100%;
  height: 100%;
}

.form-group .input {
  margin-bottom: 0;
}


/* Paires de champs (type/personnes, préparation/cuisson, four/thermostat) sur une
   seule ligne en desktop uniquement — en mobile chaque .form-group/.toggle-row reste
   empilé normalement (comportement par défaut, pas de règle nécessaire). */
@media (min-width: 900px) {
  .form-row {
    display: flex;
    gap: 16px;
  }

  .form-row .form-group,
  .form-row .toggle-row {
    flex: 1;
    min-width: 0;
  }

  /* .form-group a margin-bottom:16px (espacement vertical par défaut entre champs
     empilés en mobile), mais .toggle-row n'en a pas — en desktop, ce delta de marge
     fausse le stretch/centrage du .form-row (le contenu du toggle "Utilise un four"
     se retrouve ~8px plus bas que le champ thermostat). Neutralisé seulement ici
     (le mobile empilé garde son espacement normal). */
  #ovenTempGroup {
    margin-bottom: 0;
  }
}

.form-group select.input {
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23007aff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-size: 16px;
  padding-right: 36px;
}

/* Option "placeholder" (ex. "Type de recette", "Thermostat du four") : même gris que
   les placeholders des champs texte dans la liste déroulante ouverte. */
.option--placeholder {
  color: var(--text-muted);
}

/* Texte du <select> fermé, piloté par JS (recipe-edit.js, syncPlaceholderStyle) plutôt
   que par la couleur de l'<option> sélectionnée (peu fiable selon les navigateurs) :
   gris tant que le placeholder est la valeur courante, noir (var(--text), défaut du
   select) dès qu'un vrai choix est sélectionné. */
.input.is-placeholder-value {
  color: var(--text-muted);
}

/* ---------- Zone d'upload (illustration recette / avatar) ---------- */
.upload-zone {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100px;
  border-radius: var(--radius-md);
  border: 1px dashed var(--border);
  background: var(--surface-alt);
  color: var(--accent);
  cursor: pointer;
  overflow: hidden;
}

/* Même largeur/décalage que le textarea des étapes (icône 20px + gap 10px au-dessus) :
   .upload-zone est block-level et prend toute la largeur du formulaire par défaut,
   30px plus large que le textarea côté gauche. Scopé à #recipeImageZone (recipe-edit.php)
   uniquement — .upload-zone seule n'a pas ce contexte d'icônes. */
#recipeImageZone {
  width: calc(100% - 30px);
  margin-left: 30px;
}

.upload-zone svg {
  width: 32px;
  height: 32px;
}

.upload-zone img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Même hauteur que "Fermer" (#pickerCancel/#newIngredientCancel, 30px, voir plus haut)
   — demandé explicitement, **toutes apparences** (comme la hauteur de "Fermer"
   elle-même, jamais scopée Îdiää). .btn (classe de base de ce bouton) a son propre
   padding vertical (13px) : remplacé par un padding horizontal seul, sinon
   height:30px n'aurait aucun effet visible (box-sizing: content-box par défaut,
   padding vertical qui s'ajouterait par-dessus plutôt que d'être inclus dedans).
   Largeur fixe 358px, demandé explicitement — même valeur que #newIngredientCancel
   ci-dessus (voir sa règle pour le détail), les deux boutons de cette popup doivent
   avoir la même largeur. margin:auto pour centrer (même raison que
   #newIngredientCancel — .ingredient-list est un flex column, un enfant à largeur
   explicite ne "stretch" plus). */
#createIngredientBtn {
  height: 30px;
  padding: 0 16px;
  width: 358px;
  margin-left: auto;
  margin-right: auto;
}

/* Même largeur que les boutons "Ajouter"/"Fermer" ci-dessus (358px), demandé
   explicitement — même raison de centrage (.ingredient-list est un flex column, un
   enfant à largeur explicite ne "stretch" plus, voir #newIngredientCancel). */
#newIngredientImageZone {
  width: 358px;
  margin-left: auto;
  margin-right: auto;
}

/* Écarte "Ajouter" de la zone d'upload juste au-dessus (popup "Nouvel ingrédient",
   recipe-edit.php) — demandé explicitement, **Îdiää uniquement** (pas précisé "toutes
   apparences" comme d'autres décollages similaires, voir Design system / Page
   paramètres pour un cas où ça avait justement été élargi sur demande). */
:root[data-theme="idiaa"] #createIngredientBtn {
  margin-top: 20px;
}

/* ---------- Page de visualisation d'une recette (lecture seule) ---------- */
.recipe-view-photo {
  display: block;
  width: 100%;
  height: auto;
  object-fit: contain;
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
  /* Petit espace pour décoller l'image de la topbar en mobile (écrasé par les 10px de
     la règle desktop ci-dessous). */
  margin-top: 12px;
  margin-bottom: 16px;
}

/* Mobile : empilée normalement (image, puis titre/type en dessous) — pas de règle
   spécifique nécessaire, .recipe-view-header reste en flux bloc normal. */

/* Desktop uniquement : image à gauche (50% de la largeur), titre + type à droite,
   au même niveau vertical. */
@media (min-width: 900px) {
  .recipe-view-header {
    display: flex;
    align-items: flex-start;
    gap: 24px;
  }

  .recipe-view-photo {
    width: 50%;
    flex-shrink: 0;
    margin-top: 10px;
    margin-bottom: 0;
  }

  .recipe-view-header__info {
    flex: 1;
  }

  /* TEST : ingrédients de la recette sur 2 colonnes, desktop uniquement (voir
     renderIngredients() dans recipe-view.js pour --ingredient-rows). Le tbody devient
     un conteneur grid avec un nombre de lignes/colonne fixé par ingrédient,
     grid-auto-flow:column remplit d'abord la colonne de gauche : si le total est
     impair, c'est elle qui récupère la ligne en plus. Pour revenir en arrière :
     supprimer ce bloc + la ligne équivalente dans recipe-view.js. */
  #recipeViewIngredients {
    display: grid;
    grid-template-columns: 1fr 1fr;
    grid-template-rows: repeat(var(--ingredient-rows, 1), auto);
    grid-auto-flow: column;
    column-gap: 24px;
  }
}

/* "Partagé par {nom}" : au-dessus des autres faits (type, personnes...), aligné à gauche
   avec le nom de la recette — pas de colonne d'icône commune ici (voir
   .recipe-view-facts__icon plus bas), contrairement aux autres faits. Demandé
   explicitement. Sa présence (affichée seulement quand la recette a une provenance,
   recipe-view.js) décale naturellement .recipe-view-facts vers le bas via son propre
   flux normal — aucun décalage à calculer à la main. */
.recipe-view-shared-by {
  display: flex;
  align-items: center;
  gap: 8px;
  /* 15px avant le premier fait (type de plat, etc.) — demandé explicitement. Marges
     verticales adjacentes entre éléments de bloc qui se suivent (celle-ci et
     .recipe-view-facts { margin-top: 8px }) fusionnent au plus grand des deux : 15px
     l'emporte sans avoir à toucher à .recipe-view-facts, qui garde son 8px pour les
     autres cas où rien ne le précède directement (aucun, dans les faits, mais pas de
     raison de le changer pour autant). */
  margin: 0 0 15px;
  font-size: 13px;
  color: var(--text-muted);
}

/* Même taille que la plupart des icônes de .recipe-view-facts (16px) — pas de colonne
   fixe de 60px ici (voir commentaire ci-dessus), juste un petit espace après l'icône
   (gap sur .recipe-view-shared-by). */
.recipe-view-shared-by__icon {
  display: flex;
  flex-shrink: 0;
  width: 16px;
  height: 16px;
}

.recipe-view-shared-by__icon svg {
  width: 100%;
  height: 100%;
}

.recipe-view-facts {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 8px;
}

.recipe-view-facts__item {
  display: flex;
  align-items: center;
  color: var(--text-muted);
  font-size: 13px;
}

/* Colonne de largeur fixe (60px) autour de chaque icône, centrée dedans : les icônes
   ont des tailles différentes (16px, 21px pour "personnes"...) mais leur centre tombe
   toujours à 30px du bord gauche — un axe vertical commun d'une ligne à l'autre. Le
   texte démarre juste après cette colonne, donc à 30px de cet axe, aligné à gauche sur
   toutes les lignes quelle que soit la largeur de l'icône. */
.recipe-view-facts__icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 60px;
  flex-shrink: 0;
}

.recipe-view-facts__icon svg {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
}

/* +30% par rapport aux 16px partagés avec les autres icônes (préparation/cuisson/four) :
   propre à l'icône "personnes", ne touche pas les autres. */
.recipe-view-facts__item--servings .recipe-view-facts__icon svg {
  width: 21px;
  height: 21px;
}

.recipe-view-steps {
  margin: 0;
  /* Puces décalées de 30px par rapport au bord gauche du texte "Étapes de la recette"
     (remplace le padding par défaut du navigateur sur <ul>, ~40px). */
  padding-left: 30px;
  list-style: disc;
}

.recipe-view-steps li {
  margin-bottom: 8px;
}

.recipe-view-steps li:last-child {
  margin-bottom: 0;
}

.view-actions {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 24px;
}

.view-actions .btn--neu {
  width: 100%;
}

/* Desktop : mêmes dimensions/position que Enregistrer/Annuler sur la page d'édition
   (120px, côte à côte, alignés à droite) — Éditer à gauche, Retour à droite (même
   ordre visuel qu'Enregistrer/Annuler). */
@media (min-width: 900px) {
  .view-actions {
    flex-direction: row;
    justify-content: flex-end;
  }

  .view-actions .btn--neu {
    width: 120px;
  }
}

/* ---------- Boutons neumorphiques (actions de formulaire) ---------- */
.form-actions {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  margin-top: 20px;
}

.btn--neu {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 120px;
  height: 40px;
  color: var(--accent);
  padding: 0;
  font-size: 13px;
  border-radius: 0.8em;
  background: var(--bg);
  cursor: pointer;
  border: 1px solid var(--ombre);
  box-shadow: 6px 6px 12px var(--ombre), -6px -6px 12px var(--surface);
  transition: all 0.2s ease-in-out;
}

.btn--neu:hover {
  cursor: pointer;
  color: var(--svg-hover);
  box-shadow: 6px 6px 12px var(--surface), -6px -6px 12px var(--surface);
}

/* Aucun bouton de l'app ne neutralisait le contour de focus par défaut du navigateur
   (seuls les champs .input le font, voir .input:focus plus haut) — remplacé par un
   anneau thématisé plutôt que supprimé complètement (accessibilité clavier), au cas où
   le rendu par défaut du navigateur serait la cause d'un artefact visuel repéré sur
   "Changer le pseudo" (popup profil) : un bandeau clair sous le bouton après clic, pas
   reproduit dans les vérifications Playwright de cette session (headless) — possible
   différence de rendu navigateur réel/`outline:auto`, pas confirmée avec certitude. */
.btn:focus-visible,
.btn--neu:focus-visible,
.profile-popup__logout:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

@media (max-width: 899px) {
  .form-actions {
    flex-direction: column;
  }

  .btn--neu {
    width: 100%;
    font-size: 15px;
  }
}

/* ---------- Overlay de chargement (enregistrement recette / profil) ---------- */
.loading-overlay {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.2);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  z-index: 100;
}

.loading-overlay[hidden] {
  display: none;
}

.loading-spinner rect {
  animation: idiaa-spinner-fade 1.2s linear infinite;
}

/* Les <rect> sont positionnées une fois pour toutes via rotate() (voir
   loading-overlay.php) et ne bougent jamais : seule leur couleur est animée,
   ce qui donne l'illusion que le spinner tourne. */
@keyframes idiaa-spinner-fade {
  0% {
    fill: var(--spinner-strong);
  }
  16% {
    fill: var(--spinner-soft);
  }
  100% {
    fill: var(--spinner-soft);
  }
}

/* Barres de défilement verticales personnalisées — Îdiää uniquement, demandé
   explicitement, toutes zones qui défilent (pas de sélecteur par zone à tenir à jour).
   Deux jeux de propriétés nécessaires, aucun des deux seul ne couvrant tous les
   navigateurs :
   - scrollbar-color/scrollbar-width : standard (Firefox, et Chromium récent qui les
     supporte aussi en plus de son propre mécanisme ci-dessous). scrollbar-color est
     une propriété héritée (voir MDN) : posée une fois sur :root[data-theme="idiaa"],
     elle s'applique à toutes les zones qui défilent en dessous sans avoir à les
     cibler une par une.
   - ::-webkit-scrollbar* : Chrome/Safari/Edge (WebKit/Blink), qui l'utilisent pour le
     détail du rendu (thumb/track/hover) même quand scrollbar-color est supporté aussi.
     Pas hérité (pseudo-élément propre à chaque boîte qui défile, pas une propriété
     CSS) : posé via le sélecteur universel (*) pour couvrir toutes les zones (page
     entière, .profile-popup__body, .zone-liste-ingr...) sans les lister. */
:root[data-theme="idiaa"] {
  scrollbar-color: var(--text-muted) transparent;
  scrollbar-width: thin;
}

:root[data-theme="idiaa"] *::-webkit-scrollbar {
  width: 10px;
}

:root[data-theme="idiaa"] *::-webkit-scrollbar-track {
  background: transparent;
}

:root[data-theme="idiaa"] *::-webkit-scrollbar-thumb {
  background-color: var(--text-muted);
  border: none;
}

:root[data-theme="idiaa"] *::-webkit-scrollbar-thumb:hover {
  background-color: var(--text);
}

/* ---------------------------------------------------------------------------
   Mode "Îdiää" (data-theme="idiaa") : --surface/--surface-alt deviennent
   translucides via variables.css (identité login.php), ce qui suffit pour la
   couleur. Ce bloc ajoute juste le flou (backdrop-filter) sur les panneaux de
   premier niveau posés directement sur la photo de fond — pas sur les éléments
   imbriqués à l'intérieur (inputs, badges, lignes de tableau...) : empiler des
   backdrop-filter n'apporte rien visuellement et coûte cher au rendu.
   --------------------------------------------------------------------------- */
:root[data-theme="idiaa"] .topbar,
:root[data-theme="idiaa"] .nav-desktop,
:root[data-theme="idiaa"] .nav-mobile,
:root[data-theme="idiaa"] .card--surface,
:root[data-theme="idiaa"] .data-table,
:root[data-theme="idiaa"] .recipe-ingredients,
:root[data-theme="idiaa"] .th-filter__menu,
:root[data-theme="idiaa"] .ingredient-list,
:root[data-theme="idiaa"] .profile-popup__panel,
:root[data-theme="idiaa"] .friend-add-panel,
:root[data-theme="idiaa"] .recipe-share-panel,
:root[data-theme="idiaa"] .list-create-panel,
:root[data-theme="idiaa"] .article-list {
  -webkit-backdrop-filter: blur(20px);
  backdrop-filter: blur(20px);
}

/* Demandé : fond de la popup profil légèrement plus opaque que les autres panneaux
   vitrés (--surface, rgba(255,255,255,0.4)) — --surface-alt (rgba(255,255,255,0.55)
   en Îdiää, voir variables.css) au lieu de --surface (base, style.css, toutes
   apparences). */
:root[data-theme="idiaa"] .profile-popup__panel {
  background-color: var(--surface-alt);
}

/* Même fond que la popup profil ci-dessus (var(--surface-alt)), demandé explicitement
   — popup d'ajout/sélection d'ingrédient (.ingredient-list, recipe-edit.php). */
:root[data-theme="idiaa"] .ingredient-list {
  background-color: var(--surface-alt);
}

/* Même traitement pour les nouvelles popups de la même famille (ajout d'ami, partage de
   recette, création de liste de courses, sélection/création de produit) — cohérence avec
   les popups existantes, demandé explicitement pour les listes de courses ("même fond que
   la popup profil"). */
:root[data-theme="idiaa"] .friend-add-panel,
:root[data-theme="idiaa"] .recipe-share-panel,
:root[data-theme="idiaa"] .list-create-panel,
:root[data-theme="idiaa"] .article-list {
  background-color: var(--surface-alt);
}

/* Composeur du chat : --surface-alt plutôt que --surface par défaut, pour se distinguer
   du fil de messages en dessous — pas de flou propre ici (retiré, voir plus haut) : c'est
   maintenant un élément IMBRIQUÉ dans .idiaa-page-wrap (.chat-page, voir chat.php), qui
   porte déjà le flou du panneau — même logique que .recipe-instructions-field/
   .upload-zone sur recipe-edit.php, qui utilisent aussi --surface-alt sans flou propre
   pour se démarquer du reste du formulaire. */
:root[data-theme="idiaa"] .chat-composer {
  background-color: var(--surface-alt);
}

/* .idiaa-page-wrap (page liste des recettes, index.php) : conteneur englobant tous les
   éléments de la page. Décollé du topbar de 20px dans TOUS les thèmes (pas seulement
   Îdiää) : depuis que le titre de page a été sorti de .main-content vers la topbar
   (voir Design system / Titre de page dans le topbar), plus rien ne crée d'espace
   naturel au-dessus du premier champ en clair/sombre/système — sans ce margin-top, le
   champ de recherche colle directement au topbar. En Îdiää, seul thème où ce div est
   visuellement un panneau (voir plus bas), ce même margin-top le décolle lui aussi. */
.idiaa-page-wrap {
  margin-top: 20px;
}

/* Carte "Apparence" de settings.php (#appearanceSection) : même cause que
   .idiaa-page-wrap ci-dessus (rien ne décolle plus .main-content du topbar depuis que
   le titre de page en est sorti) — décollée dans TOUS les thèmes désormais, comme
   .idiaa-page-wrap (élargi depuis un premier essai Îdiää-only, voir CLAUDE.md : le
   collage direct au topbar en clair/sombre/système, repéré à cette occasion mais pas
   corrigé dans un premier temps, s'est avéré concerné par la demande finalement).
   settings.php n'a pas de .idiaa-page-wrap (pas de panneau englobant sur cette page). */
#appearanceSection {
  margin-top: 20px;
}

/* Apparence "vitrée" du panneau — visible SEULEMENT en mode Îdiää, pas de règle hors
   du sélecteur scopé ci-dessous, donc `div` neutre en clair/sombre/système à part le
   margin-top ci-dessus. Reprend les caractéristiques du `.wrapper` de login.php
   (login.css) — fond vitré, coins 20px, flou 20px, ombre — via les tokens de l'app
   plutôt que les `--login-*` locaux, à deux différences volontaires près :
   - bordure en `var(--border)` (couleur de la bordure droite de la sidebar, nav.css)
     et non `var(--text)` (valeur d'origine du `.wrapper`, pleine opacité) — demandé
     pour rester cohérent avec le reste du chrome de l'app plutôt qu'avec login.php ;
   - pas d'`overflow: hidden` : sur `.wrapper` il sert à couper les panneaux
     connexion/inscription qui glissent horizontalement (spécifique à login.php) — omis
     ici, sinon les menus de filtre du tableau (`.th-filter__menu`, positionnés hors du
     `<table>` pour ne pas être rognés par son propre `overflow:hidden`, voir plus haut)
     seraient rognés à leur tour par ce nouveau conteneur englobant. */
:root[data-theme="idiaa"] .idiaa-page-wrap {
  background: var(--surface);
  /* Même couleur que la bordure droite de la sidebar (nav.css, .nav-desktop) plutôt
     que var(--text) (valeur d'origine du .wrapper de login.php, pleine opacité) —
     demandé pour cohérence avec le reste du chrome de l'app. */
  border: 1px solid var(--border);
  border-radius: 20px;
  -webkit-backdrop-filter: blur(20px);
  backdrop-filter: blur(20px);
  box-shadow: 0 0 30px var(--shadow-md);
  padding: 24px;
}

/* .idiaa-field : mise en forme réutilisable "à la login.php" (.input-box de
   login.css — soulignement, libellé flottant) pour n'importe quel champ ailleurs dans
   l'app — EN MODE ÎDIÄÄ UNIQUEMENT, partout où ces classes sont utilisées (recherche
   recettes, popup profil...). .idiaa-field s'ajoute aux classes existantes (ex.
   .form-group), ne les remplace pas : sans le sélecteur scopé ci-dessous,
   .idiaa-field__label reste display:none et le champ garde son rendu .input habituel
   (bordé, placeholder visible) — c'est ce qui se passe en clair/sombre/système,
   intouché.
   Icône : PAS gérée ici, volontairement — deux classes distinctes selon que l'icône
   existait déjà avant (.idiaa-field__icon--shown) ou non (.idiaa-field__icon), voir
   leurs définitions plus bas — un champ qui utilise la mauvaise classe perd son icône
   dans tous les autres thèmes, piège déjà rencontré (recipe-edit.php). */
.idiaa-field__label {
  display: none;
}

:root[data-theme="idiaa"] .idiaa-field {
  display: block;
  position: relative;
  border-bottom: 2px solid var(--text);
  padding: 5px 0;
  /* Longhand plutôt que le raccourci "margin: 8px 0 24px" d'origine : ce dernier
     fixe aussi margin-left/right à 0, qui gagnerait sur les margin-left/right:auto
     de largeur/centrage ajoutés spécifiquement pour la popup profil (voir plus bas,
     .profile-popup__panel .idiaa-field) à spécificité égale et déclarés avant dans ce
     fichier. Aucun effet ailleurs (recherche recettes, recipe-edit.php, popup nouvel
     ingrédient) : rien d'autre ne définissait de margin-left/right sur .idiaa-field,
     la valeur initiale (0) reste donc identique pour ces pages. */
  margin-top: 8px;
  margin-bottom: 24px;
}

:root[data-theme="idiaa"] .idiaa-field .input {
  width: 100%;
  height: auto;
  border: none;
  border-radius: 0;
  /* background-color (pas le raccourci `background`) : `background: transparent`
     réinitialise aussi background-repeat/position/size à leurs valeurs par défaut
     (dont `repeat`) — piège rencontré sur les <select> (chevron, voir plus bas),
     dont le fond carrelait au lieu de s'afficher une seule fois. Repéré à l'écran
     (Playwright), pas évident sur le papier. */
  background-color: transparent;
  /* Pas de font-size ici : celui de .input (14px, 16px en mobile pour éviter le zoom
     auto iOS, voir plus haut) reste en place — login.css n'a pas cette contrainte
     (stylesheet indépendant, pas de champ testé en usage réel sur mobile). */
  color: var(--text);
  padding: 0 32px 0 5px;
  margin-bottom: 0;
  box-sizing: border-box;
}

:root[data-theme="idiaa"] .idiaa-field .input::placeholder {
  /* Le texte reste dans l'attribut (nécessaire pour :placeholder-shown ci-dessous),
     juste invisible — le libellé flottant affiche le même texte par-dessus. */
  color: transparent;
}

:root[data-theme="idiaa"] .idiaa-field .input:focus {
  box-shadow: none;
  border-color: transparent;
}

:root[data-theme="idiaa"] .idiaa-field__label {
  display: block;
  position: absolute;
  top: 50%;
  left: 5px;
  transform: translateY(-50%);
  font-size: 14px;
  color: var(--text);
  font-weight: 500;
  pointer-events: none;
  transition: 0.3s;
}

/* Flotte au focus OU dès que le champ est rempli — équivalent de ":valid" côté
   login.php (utilisable là-bas car le champ email est "required", invalide tant qu'il
   est vide) : la plupart des champs ici n'ont pas cette contrainte de validité (ou
   type=search n'en a pas du tout), ":not(:placeholder-shown)" est l'équivalent
   robuste ("le champ n'affiche plus son placeholder" = "il a une valeur"), même effet
   visuel, sans dépendre de required/type. */
:root[data-theme="idiaa"] .idiaa-field .input:focus ~ .idiaa-field__label,
:root[data-theme="idiaa"] .idiaa-field .input:not(:placeholder-shown) ~ .idiaa-field__label {
  top: -6px;
}

/* Icône à droite, DEUX classes selon le contexte — ne pas les confondre (piège déjà
   rencontré, voir recipe-edit.php) :
   - .idiaa-field__icon--shown : repositionne une icône DÉJÀ affichée par défaut
     (.form-group__icon, visible hors Îdiää) — pas de display:none ici, sinon
     l'icône disparaîtrait dans TOUS les autres thèmes (spécificité égale à
     .form-group__icon, gagnerait car déclarée après dans ce fichier). Utilisée par
     le champ de recherche (index.php) et les champs texte/nombre/textarea de
     recipe-edit.php, qui avaient tous déjà une icône avant Îdiää.
   - .idiaa-field__icon (plus bas) : CRÉE l'icône elle-même, masquée par défaut —
     pour des champs qui n'avaient AUCUNE icône avant (popup profil uniquement). */
:root[data-theme="idiaa"] .idiaa-field__icon--shown {
  position: absolute;
  right: 5px;
  top: 50%;
  transform: translateY(-50%);
  width: 20px;
  height: 20px;
  color: var(--text);
}

.idiaa-field__icon {
  display: none;
}

:root[data-theme="idiaa"] .idiaa-field__icon {
  display: block;
  position: absolute;
  right: 5px;
  top: 50%;
  transform: translateY(-50%);
  width: 20px;
  height: 20px;
  color: var(--text);
}

/* .idiaa-field contenant un textarea (recipe-edit.php : description, étapes) :
   libellé/icône calés en haut (comme le texte d'un textarea démarre en haut) plutôt
   que centrés verticalement (top:50%/translateY(-50%) mettrait le libellé au milieu
   d'un bloc de plusieurs lignes, pas au niveau de la première ligne de texte). Un peu
   de padding-top sur le textarea lui-même pour ne pas chevaucher le libellé/l'icône
   avant qu'ils ne s'écartent (focus/rempli). */
:root[data-theme="idiaa"] .idiaa-field:has(textarea) .idiaa-field__label,
:root[data-theme="idiaa"] .idiaa-field:has(textarea) .idiaa-field__icon,
:root[data-theme="idiaa"] .idiaa-field:has(textarea) .idiaa-field__icon--shown {
  top: 14px;
  transform: none;
}

:root[data-theme="idiaa"] .idiaa-field textarea.input {
  padding-top: 22px;
}

/* Textarea "Étapes de la recette" (recipe-edit.php) : PAS le traitement .idiaa-field
   des autres champs (icône restée à gauche, comme dans les autres thèmes, demandé
   explicitement) — juste un fond aligné sur celui de la zone d'illustration juste en
   dessous (var(--surface-alt), .upload-zone) au lieu du var(--surface) par défaut de
   .input, pour que les deux se distinguent visuellement du reste du formulaire de la
   même façon. */
:root[data-theme="idiaa"] .recipe-instructions-field {
  background: var(--surface-alt);
}

/* Chevron des <select> (.form-group select.input, style.css) : couleur bleue codée en
   dur (#007aff, fixe dans les autres thèmes) — remonte au monochrome en Îdiää, même
   tracé (juste la couleur du "stroke" changée, %231d1d1f = #1d1d1f encodé pour un data
   URI). Pas de var(--text) possible dans un data URI (pas d'évaluation CSS dedans),
   valeur medium recopiée telle quelle : à mettre à jour si --text change un jour.
   Décalé à gauche (34px au lieu de 12px) pour laisser la place à l'icône de champ
   (.idiaa-field__icon--shown, 20px de large, ancrée à droite) : les deux se lisent
   "chevron puis icône" en partant de la droite, comme demandé. padding-right élargi
   d'autant (54px au lieu de 36px) pour que le texte du select ne passe pas dessous. */
:root[data-theme="idiaa"] .form-group select.input {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%231d1d1f' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
  background-position: right 34px center;
  padding-right: 54px;
}

/* .idiaa-btn : même design que .btn de login.php (login.css) — fond transparent,
   contour, coins 6px, ombre portée, survol = fond teinté + léger agrandissement — EN
   MODE ÎDIÄÄ UNIQUEMENT. S'ajoute à .btn--neu (pas de remplacement) sur les boutons
   pleine largeur de la popup profil ("Changer le pseudo/l'email/le mot de passe",
   "Fermer") et les boutons Enregistrer/Annuler de recipe-edit.php. Dimensions
   volontairement PAS touchées (chaque contexte garde les siennes — 30px/14px pour la
   popup, 120px/40px pour recipe-edit) : re-dessine seulement la couleur/le contour, ne
   touche pas à la mise en page déjà réglée ailleurs. --accent-tint vaut déjà
   rgba(29,29,31,0.1) en Îdiää, exactement --login-text-tint de login.css : réutilisé
   tel quel. Pas de token existant pour l'ombre (rgba(29,29,31,0.4), --login-shadow-btn
   côté login.css, stylesheet indépendant) : valeur reprise telle quelle. */
:root[data-theme="idiaa"] .idiaa-btn {
  background: transparent;
  border: 1px solid var(--text);
  border-radius: 6px;
  box-shadow: 6px 6px 12px rgba(29, 29, 31, 0.4);
  color: var(--text);
  font-weight: 500;
  transition: transform 0.2s ease, background-color 0.2s ease;
}

:root[data-theme="idiaa"] .idiaa-btn:hover {
  background: var(--accent-tint);
  color: var(--text);
  box-shadow: 6px 6px 12px rgba(29, 29, 31, 0.4);
  transform: scale(1.02);
}

/* "Se déconnecter" (popup profil, .profile-popup__logout) : même design Îdiää que
   .idiaa-btn ci-dessus (fond transparent, contour var(--text), ombre, survol = fond
   teinté + agrandissement), demandé explicitement — mais **pas** la classe .idiaa-btn
   elle-même (coins 6px / ombre 6px 6px 12px, dimensionnés pour les gros boutons pleine
   largeur) : ce bouton reste volontairement compact ("petit bouton... sur la ligne du
   titre", voir sa règle de base plus haut), donc override dédié à ses proportions —
   coins/ombre à moitié plus petits (0.8em déjà en place, ombre 3px 3px 6px comme sa
   version neumorphique de base, qui était déjà à échelle réduite par rapport à
   .btn--neu : 3px/6px contre 6px/12px). */
:root[data-theme="idiaa"] .profile-popup__logout {
  background: transparent;
  border: 1px solid var(--text);
  box-shadow: 3px 3px 6px rgba(29, 29, 31, 0.4);
  color: var(--text);
}

:root[data-theme="idiaa"] .profile-popup__logout:hover {
  background: var(--accent-tint);
  box-shadow: 3px 3px 6px rgba(29, 29, 31, 0.4);
  color: var(--text);
  transform: scale(1.02);
}

/* .btn--pill (Accepter/Refuser/Annuler/Inviter, composeur de chat) : même échelle
   compacte que .profile-popup__logout ci-dessus (pas .idiaa-btn — coins/ombre trop
   grands pour ces petits boutons de ligne), donc override dédié plutôt qu'une classe
   partagée. .btn--pill--danger garde volontairement --danger (pas var(--text)) : les
   couleurs sémantiques restent hors du monochrome Îdiää, comme --danger/.form-success
   ailleurs dans l'app (voir CLAUDE.md). */
:root[data-theme="idiaa"] .btn--pill {
  background: transparent;
  border: 1px solid var(--text);
  box-shadow: 3px 3px 6px rgba(29, 29, 31, 0.4);
  color: var(--text);
}

:root[data-theme="idiaa"] .btn--pill:hover {
  background: var(--accent-tint);
  box-shadow: 3px 3px 6px rgba(29, 29, 31, 0.4);
  color: var(--text);
  transform: scale(1.02);
}

:root[data-theme="idiaa"] .btn--pill--danger,
:root[data-theme="idiaa"] .btn--pill--danger:hover {
  color: var(--danger);
  border-color: var(--danger);
}
