/* =========================================================================
   POLICES AUTO-HÉBERGÉES
   =========================================================================

   Fraunces variable — police d'affichage (titres). Consommée via --font-serif,
   déclaré dans variables.css à côté de --font-sans.

   AUTO-HÉBERGEMENT, PAS DE CDN. Deux raisons. Technique : un appel tiers ajoute
   résolution DNS, négociation TLS et requête bloquante avant le premier rendu,
   ce qui contredit l'esprit offline-first de cette PWA. Juridique : servir
   Google Fonts depuis le CDN transmet l'IP du visiteur à Google (LG München I,
   3 O 17493/20, janvier 2022). L'auto-hébergement supprime la question.

   SOUS-ENSEMBLE : « latin » seul (U+0000-00FF, U+0131, U+0152-0153, U+2000-206F,
   U+20AC…). Vérifié comme suffisant pour le français : é è ê à ç ù î ï ô sont
   dans U+00C0-00FF, œ/Œ sont les U+0152-0153 inclus explicitement, « » sont
   U+00AB/U+00BB, le tiret cadratin et l'apostrophe typographique sont dans
   U+2000-206F. « latin-ext » n'apporterait aucun glyphe utile ici et doublerait
   la charge. Pas de descripteur unicode-range : avec un fichier unique il ne
   sert à rien (le repli glyphe par glyphe vers Georgia est natif) et n'ajoute
   qu'un moyen de bloquer un caractère par erreur.

   AXES EMBARQUÉS : opsz 9-144, wght 100-900, WONK 0-1. Pas de SOFT — décision
   de poids, mesurée et non supposée :

     opsz + wght seuls ........  65,7 Ko
     + WONK ...................  65,9 Ko   (+184 o)
     + SOFT ................... 118,0 Ko   (+52,2 Ko)

   SOFT coûtait 44 % du fichier à lui seul. Rendu à 30px — le plafond réel de
   --font-size-h1 — l'écart entre SOFT 0 et SOFT 40 vaut 45 % des pixels d'encre
   pour un écart max de 113/255, c'est-à-dire une différence d'antialiasing
   invisible à l'œil : il faut un agrandissement x7 pour la distinguer. Sur un
   asset précaché d'une PWA offline-first, 52 Ko pour du sous-perceptible ne se
   justifient pas. WONK reste : à 184 octets il est gratuit, et il apporte une
   esperluette à fioriture (c'est le seul glyphe qu'il modifie visiblement).

   Conséquence : ne PAS écrire 'SOFT' dans un font-variation-settings — l'axe est
   absent du fichier, la mention serait silencieusement ignorée et mensongère.
   ========================================================================= */

@font-face {
  font-family: 'Fraunces';

  /* asset_path, et non un url() relatif : ces feuilles sont en .css et
     Sprockets 4 ne fait passer un .css par aucun préprocesseur — un chemin
     relatif ne serait donc PAS réécrit vers le chemin digéré. D'où l'extension
     .erb sur ce seul fichier. La route .scss + font-url() n'est pas disponible
     ici : sassc-rails n'est pas dans le bundle, donc aucun transformer n'est
     enregistré pour text/scss et rien ne compile de Sass dans ce projet. */
  src: url(/assets/fraunces-variable-73a18efbf633e13bee1ba60635d392ae3525587117010ee8b899f74bbd684aeb.woff2) format('woff2');

  font-weight: 100 900;  /* plage de l'axe wght : une seule ressource variable */
  font-style: normal;

  /* « optional », PAS « swap ». variables.css documente le choix de la pile
     système par « aucune requête réseau ni FOUT, le plus robuste pour une PWA
     offline-first ». Ajouter une police custom ne doit pas annuler cet
     arbitrage. Avec « swap », le navigateur affiche la pile système puis
     rebascule à l'arrivée du fichier : c'est exactement le FOUT que ce choix
     écartait. Avec « optional », le navigateur n'attend jamais la police et ne
     rebascule jamais dans la page en cours : première visite rendue en pile
     système pendant que le fichier se met en cache, puis toutes les visites
     suivantes — le cas normal d'une PWA installée — en Fraunces immédiatement,
     sans flash. On obtient l'identité typographique à FOUT nul.

     Corollaire : AUCUN <link rel="preload"> sur cette police. Le preload la
     téléchargerait en priorité haute, donc au détriment des ressources du
     premier rendu, pour une police que « optional » garantit inutilisée à ce
     rendu-là. Preload et optional se contredisent. */
  font-display: optional;
}
/* ============================================
   VARIABLES CSS - EasyMeal Design System
   ============================================
   Charte graphique « Slate Craft Premium »
   Interface premium de type éditorial/magazine culinaire.
   Anthracite comme couleur principale, ambre comme seul accent coloré.
   ============================================ */

:root {
  /* === COULEURS PRINCIPALES ===
     Toute la rampe neutre (principales, encres, fonds, bordures, surfaces
     sombres) est générée à ~70° de teinte en OKLCH. Elle dérivait auparavant
     de 49° (les gris) à 95° (les fonds) : les fonds tiraient vers le
     jaune-vert pendant que les gris tiraient vers l'orange. Imperceptible sur
     un échantillon isolé, c'est ce désaccord qui donnait l'effet « blanc
     sale » à l'échelle d'une page. */
  --color-primary: #1C1916;
  --color-primary-hover: #45403B;
  --color-primary-light: rgba(28, 25, 22, 0.08);

  --color-secondary: #E8E2DB;
  --color-secondary-hover: #E0D9D1;
  --color-secondary-text: #45403B;

  /* Corail du logotype. Rôle strictement décoratif — état sélectionné des
     options de régime, moments de marque. Jamais un bouton, un lien ni un état
     d'erreur : il n'est qu'à 6,7° de teinte de --color-danger, et les confondre
     sur une app où l'on supprime des recettes coûterait cher. */
  --color-brand: #D73A49;
  --color-brand-light: #FFF8F8;

  /* === RÔLES SÉMANTIQUES ===
     Socle commun à tous les rôles :
       --color-{rôle} · -light · -border · -text · --color-on-{rôle}
     --color-on-{rôle} se lit « texte ou icône posé sur l'aplat de base ».

     -hover n'est pas dans ce socle : il ne se justifie que pour un rôle
     réellement porté par une surface cliquable. Seuls accent et danger le sont
     ici. Un token de survol pour success, warning ou info serait une variante
     que personne ne consomme — donc une valeur qu'aucun rendu ne valide, et
     qu'on finirait par recopier de travers le jour où le besoin arrive.
     Ajouter -hover au rôle concerné à ce moment-là, pas avant.

     Accent et warning sont deux rôles distincts : l'ambre est le highlight de
     marque (charte « anthracite + ambre », jamais un remplissage interactif),
     l'orange est l'avertissement. Ils partageaient jusqu'ici les mêmes valeurs
     de -light et -text, c'est-à-dire deux noms pour un seul rôle. Séparations
     obtenues en teinte OKLCH : accent/warning 28,4°, warning/danger 28,7°. */

  /* Accent — ambre.
     -hover et -deep partagent la même valeur mais pas le même sens : -hover est
     l'état survolé de .btn-accent, -deep est l'ambre foncé posé à plat sur une
     surface non interactive (bordure de carte, cible de drop, filet décoratif).
     Les distinguer permet de faire évoluer le survol du bouton sans repeindre du
     même coup sept éléments qui ne réagissent à rien.
     Ni l'un ni l'autre ne portent du texte : ils ne dépassent pas 2,15:1 sur une
     surface claire. Pour du texte ambre, --color-accent-text. */
  --color-accent: #FBBF24;
  --color-accent-hover: #F59E0B;
  --color-accent-deep: #F59E0B;
  --color-accent-light: #FEF3C7;
  --color-accent-border: #FCD34D;
  --color-accent-text: #92400E;
  --color-on-accent: #1C1916;   /* 10,48:1 — jamais de blanc sur l'ambre (1,67:1) */

  /* Warning — orange. */
  --color-warning: #EB7D06;
  --color-warning-light: #FFEDDE;
  --color-warning-border: #FBC48E;
  --color-warning-text: #823600;  /* 7,44:1 sur --color-warning-light */
  --color-on-warning: #1C1916;    /* 6,22:1 */

  /* Success — lime. */
  --color-success: #65A30D;
  --color-success-light: #ECFCCB;
  --color-success-border: #D9F99D;
  --color-success-text: #365314;  /* 8,05:1 sur --color-success-light */
  --color-on-success: #1C1916;    /* 5,66:1 — le blanc plafonne à 3,09:1 */

  /* Danger — rouge. */
  --color-danger: #DC2626;
  --color-danger-hover: #B91C1C;
  --color-danger-light: #FEF2F2;
  --color-danger-border: #FECACA;
  --color-danger-text: #991B1B;   /* 7,60:1 sur --color-danger-light */
  --color-on-danger: #FFFFFF;     /* 4,83:1 */

  /* Info — bleu. */
  --color-info: #3B82F6;
  --color-info-light: #EFF6FF;
  --color-info-border: #BFDBFE;
  --color-info-text: #1E40AF;     /* 8,01:1 sur --color-info-light */
  --color-on-info: #1C1916;       /* 4,76:1 — le blanc plafonne à 3,68:1 sur ce bleu */

  /* Contour de focus clavier. Vaut --color-primary, isolé pour pouvoir diverger
     sans toucher aux boutons. Consommé par le bloc :focus-visible de global.css. */
  --color-focus-ring: #1C1916;

  /* === NEUTRALS / TEXTE === */
  --color-ink: #1C1916;
  --color-ink-2: #45403B;
  /* ink-3 assombri (#78716C → #6B645E → #615B54) pour atteindre WCAG AA
     (≥ 4.5:1) sur les trois fonds clairs de l'app. Les fonds ayant été
     assombris avec la rampe à 70°, #6B645E n'y suffisait plus (4,16:1 sur
     --color-bg-tertiary). #615B54 donne 5,61:1 sur --color-bg-main,
     5,21:1 sur --color-bg-secondary et 4,79:1 sur --color-bg-tertiary. */
  --color-ink-3: #615B54;
  --color-ink-4: #A9A29A;

  /* === FONDS ===
     bg-main assombri : le blanc n'avait que 1,07:1 sur l'ancien #F8F7F4, donc
     aucune surface blanche ne se détachait du fond. Il est maintenant à 1,19:1. */
  --color-bg-main: #F0EAE3;
  --color-bg-secondary: #E8E2DB;
  --color-bg-tertiary: #E0D9D1;
  --color-bg-white: #FFFFFF;

  /* === SURFACES SOMBRES ===
     Carte « menu actif » rendue sur fond anthracite (--color-primary).
     Tons neutres chauds et variantes danger dédiés au fond sombre,
     absents de la palette claire principale. */
  --color-surface-dark: #292521;               /* sous-carte / séparateur le plus foncé */
  --color-surface-dark-border: #393530;        /* bordure sur fond sombre */
  /* Éclairci (#57524C → #7A736C) : à 1,96:1 sur --color-surface-dark, l'ancienne
     valeur passait sous le seuil UI de 3:1, donc un survol de bordure qu'on ne
     voyait pas. oklch(0.56 0.014 70) donne 3,26:1 sur la même surface. */
  --color-surface-dark-border-hover: #7A736C;  /* bordure au survol sur fond sombre */
  --color-ink-on-dark: #D5CEC6;                /* texte clair sur fond sombre */
  /* La variante danger ne suit pas la teinte des neutres : le rouge doit rester
     lisible comme rouge sur ce fond. */
  --color-danger-on-dark: #F87171;             /* texte/bordure danger sur fond sombre */

  /* Ni les couleurs de jour (cartes de repas, UC7), ni les pastilles de régime,
     ni la pastille d'icône de catégorie ne sont des tokens globaux : elles n'ont
     de sens que posées sur leur composant et vivent donc dans menus.css. */

  /* === BORDURES === */
  --color-border: #DCD5CD;
  --color-border-light: #E8E2DB;
  --color-border-strong: #C9C1B8;

  /* Épaisseur de bordure canonique : 1px (rendu fiable partout, contrairement à
     0.5px qui s'arrondit à 0 ou 1px selon le device non-retina). L'aspect « fin »
     est porté par la couleur claire de la bordure, pas par une épaisseur inférieure. */
  --border-width: 1px;
  --border-thin: var(--border-width) solid var(--color-border);

  /* === BORDER RADIUS === */
  --radius-button: 4px;
  --radius-badge: 2px;
  --radius-card: 6px;
  --radius-card-compact: 4px;
  /* Aligné sur --radius-card (était 10px). Dans un formulaire, le champ était
     plus arrondi que le bouton qui le valide (4px) — un écart de 6px visible à
     l'œil et sans intention. La famille est maintenant cohérente : 4px pour les
     contrôles, 6px pour les surfaces qui les contiennent. */
  --radius-input: 6px;
  --radius-icon-btn: 4px;

  /* === ESPACEMENTS (inchangés) === */
  --spacing-xs: 0.25rem;
  --spacing-sm: 0.5rem;
  --spacing-md: 1rem;
  --spacing-lg: 1.5rem;
  --spacing-xl: 2rem;
  --spacing-2xl: 3rem;

  /* === OMBRES ===
     Teintées de l'encre plutôt qu'en noir pur : sur des fonds à 70° de teinte,
     un gris parfaitement neutre lit légèrement bleuté et éteint la chaleur du
     fond. Alphas très légèrement relevés, une ombre teintée paraissant plus
     faible qu'une ombre neutre à luminosité égale. */
  --shadow-sm: 0 1px 2px rgba(28, 25, 22, 0.05);
  --shadow-md: 0 1px 4px rgba(28, 25, 22, 0.07);
  --shadow-lg: 0 2px 8px rgba(28, 25, 22, 0.09);

  /* === TRANSITIONS (inchangées) === */
  --transition-fast: 0.15s ease;
  --transition-normal: 0.2s ease;
  --transition-slow: 0.3s ease;

  /* === TYPOGRAPHIE === */
  /* Pile système : rendu natif sur chaque OS, sans requête réseau ni FOUT.
     Choix « le plus robuste » pour une PWA offline-first (aucun asset à
     précacher). Source unique de vérité — référencée par body dans global.css. */
  --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;

  /* Police d'affichage, réservée aux titres. Le @font-face auto-hébergé est
     dans fonts.css.erb, qui explique pourquoi font-display vaut « optional » et
     pourquoi cela n'annule pas l'arbitrage offline-first ci-dessus.
     Repli sur Georgia : serif à empattements présent sur tous les OS, ce qui
     rend le premier rendu (avant mise en cache de Fraunces) cohérent en style
     plutôt que renvoyé au serif générique du navigateur.
     Ce token n'a encore AUCUN consommateur : le branchement sur les titres est
     le sujet du lot typographie, pas de celui-ci. */
  --font-serif: 'Fraunces', Georgia, 'Times New Roman', serif;

  /* Échelle typographique cohérente et non redondante.
     Source unique de vérité — un pas net entre chaque token, aucun doublon.
     Plancher de lisibilité : le texte de contenu vise « base » (16px),
     jamais en dessous de « xs » (12px). */
  --font-size-xs: 0.75rem;    /* 12px — labels secondaires, badges, méta */
  --font-size-sm: 0.875rem;   /* 14px — sous-titres, texte de bouton */
  --font-size-base: 1rem;     /* 16px — texte de contenu (défaut) */
  --font-size-lg: 1.125rem;   /* 18px */
  --font-size-xl: 1.25rem;    /* 20px */
  --font-size-2xl: 1.5rem;    /* 24px */
  --font-size-3xl: 1.875rem;  /* 30px */
  --font-size-4xl: 2.25rem;   /* 36px */

  /* Titre de page (h1) : taille fluide, sans media query.
     Interpole linéairement entre le plancher mobile et le plafond desktop —
     20px à 375px de large, ~24px à 768px, 30px à partir de 1280px.
     Source unique de vérité : consommée par la règle de base h1 (global.css),
     donc tout titre de page suit l'échelle sans la redéclarer. */
  --font-size-h1: clamp(1.25rem, 1rem + 1.1vw, var(--font-size-3xl));

  /* === LAYOUT === */
  /* Hauteur du header sticky. Valeur de repli (desktop) : la hauteur réelle,
     qui dépend du breakpoint et de la zone sûre iOS, est publiée ici au runtime
     par le controller sticky-header. Consommée par tout bloc qui doit s'ancrer
     sous le header (sidebars sticky, scroll-padding des ancres internes). */
  --header-height: 60px;

  /* === BREAKPOINTS (inchangés) === */
  --breakpoint-mobile: 768px;
  --breakpoint-tablet: 1024px;
  --breakpoint-desktop: 1280px;

  --min-touch-target: 44px;
}
/* ============================================
   GLOBAL STYLES - FLASH MESSAGES & ALERTS
   ============================================ */

/* === Global Body Styles === */
html {
  height: 100%;
  /* Header sticky : une cible d'ancre (skip-link → #main-content) s'arrête sous
     le header au lieu d'être masquée par lui. */
  scroll-padding-top: var(--header-height);
}

body {
  background: var(--color-bg-main);
  font-family: var(--font-sans);
  color: var(--color-ink-2);
  margin: 0;
  padding: 0;
  width: 100%;
  min-height: 100%;
  display: flex;
  flex-direction: column;
  /* Lissage des polices : déclaré une seule fois, ici. */
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* === Titres === */
/* Fraunces sur les titres. Spécificité 0,0,1 : cette règle ne couvre que les
   titres nus. Les classes de titre des feuilles de fin de cascade (menus.css,
   recipes.css) portent leur propre font-family — elles ont été basculées sur le
   MÊME token plutôt que laissées sur leur Georgia en dur, ce qui évite d'avoir à
   surenchérir en spécificité ici. Aucun !important, aucune classe utilitaire.

   Pas de font-variation-settings, volontairement. Fraunces a un axe de taille
   optique (opsz) que font-optical-sizing:auto — la valeur par défaut — ajuste
   déjà d'après la font-size : empattements plus fins sur les grands titres,
   formes plus robustes en petit. Or écrire font-variation-settings ÉCRASE ce
   réglage automatique, et il faudrait alors répéter un 'opsz' correct sur chacune
   des dix classes de titre, qui vont de 17px à 28px. On laisse donc faire le
   navigateur. Les deux autres axes ne justifient pas d'y toucher : SOFT n'est pas
   embarqué (voir fonts.css.erb) et WONK ne modifie visiblement que l'esperluette.

   font-weight 500 : Fraunces est une variable 100-900, et le gras par défaut du
   navigateur (700) est trop lourd pour un display serif à ces tailles. */
h1, h2, h3 {
  font-family: var(--font-serif);
  font-weight: 500;
}

/* Base responsive pour tout h1 : sans cette règle, un titre sans font-size
   explicite hérite du défaut navigateur (2em, soit 32px) — identique sur mobile.
   Spécificité 0,0,1 : les classes de titre existantes gardent la main. */
h1 {
  font-size: var(--font-size-h1);
  line-height: 1.2;
}

/* === Utility Classes === */
.hidden {
  display: none !important;
}

/* === Page d'accueil : pleine hauteur minimum, scrollable si contenu déborde === */
body.home-page {
  min-height: 100vh;
}

/* Flash Messages Container — supporte .flash-messages et #flash (cible Turbo Stream) */
.flash-messages,
#flash {
  position: fixed;
  /* env(safe-area-inset-bottom) : en PWA installée sur écran à encoche, le toast
    reste au-dessus de la zone système en bas d'écran. */
  bottom: calc(1.5rem + env(safe-area-inset-bottom));
  right: 1.5rem;
  z-index: 9999;
  max-width: 420px;
  width: 100%;
}

/* Individual Flash Message */
.flash-message {
  padding: 1rem 1.25rem;
  border-radius: var(--radius-card-compact);
  margin-bottom: 0.75rem;
  box-shadow: var(--shadow-md);
  animation: slideInRight 0.3s ease-out;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  border-left: 4px solid;
}

@keyframes slideInRight {
  from {
    opacity: 0;
    transform: translateX(100%);
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}

/* Les quatre toasts tiraient leurs fonds et leurs textes de valeurs en dur qui
   divergeaient des tokens de même rôle — une seconde palette clandestine (le
   vert des toasts n'était même pas celui du design system). Ils passent tous sur
   les tokens : si un fond paraît trop pâle, on ajuste le token, pas la règle. */

/* Notice (Success) */
p.notice,
.flash-message.notice {
  background-color: var(--color-success-light);
  color: var(--color-success-text);
  border-left-color: var(--color-success);
}

/* Alert (Error) */
p.alert,
.flash-message.alert {
  background-color: var(--color-danger-light);
  color: var(--color-danger-text);
  border-left-color: var(--color-danger);
}

/* Info */
.flash-message.info {
  background-color: var(--color-info-light);
  color: var(--color-info-text);
  border-left-color: var(--color-info);
}

/* Warning */
.flash-message.warning {
  background-color: var(--color-warning-light);
  color: var(--color-warning-text);
  border-left-color: var(--color-warning);
}

/* Default Flash Styling (for old style in layout) */
body > p.notice,
body > p.alert {
  padding: 0.875rem 1.25rem;
  margin: 0;
  font-size: 0.9375rem;
  font-weight: 500;
  text-align: center;
  border-bottom: 1px solid;
}

body > p.notice {
  border-bottom-color: var(--color-success);
}

body > p.alert {
  border-bottom-color: var(--color-danger);
}

/* Hide empty flash messages */
p.notice:empty,
p.alert:empty,
.flash-message:empty {
  display: none;
}

/* Responsive */
@media (max-width: 640px) {
  .flash-messages,
  #flash {
    bottom: calc(1rem + env(safe-area-inset-bottom));
    right: 1rem;
    left: 1rem;
    max-width: none;
  }

  body > p.notice,
  body > p.alert {
    font-size: 0.875rem;
    padding: 0.75rem 1rem;
  }
}

/* ============================================
   GLOBAL LAYOUT ADJUSTMENTS
   ============================================ */

/* Link styles */
a {
  color: var(--color-ink-3);
  text-decoration: none;
  transition: var(--transition-normal);
}

/* Survol des liens TEXTE uniquement. On exclut les <a> stylés en boutons
   (toute classe contenant « btn ») : sinon la couleur ink rendait le libellé
   illisible sur les boutons foncés/colorés au survol.
   Pas de soulignement au survol : sur la nav, les cartes de recette et les
   liens divers il alourdissait inutilement le rendu ; l'affordance passe par
   le changement de couleur (et les composants gèrent leur propre survol). */
a:not([class*="btn"]):hover {
  color: var(--color-ink);
}

/* Simple reset for better consistency */
*, *::before, *::after {
  box-sizing: border-box;
}

/* ============================================
   ACCESSIBILITÉ GLOBALE (R1.3)
   ============================================ */

/* --- Skip-link « Aller au contenu » ---
   Hors écran par défaut, ramené dans le flux visuel uniquement au focus clavier. */
.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 10000;
  padding: 0.75rem 1.25rem;
  background: var(--color-primary);
  color: var(--color-bg-white);
  font-weight: 600;
  border-radius: 0 0 var(--radius-button) 0;
  text-decoration: none;
}

/* Même contour que tous les autres éléments focusables. L'ambre utilisé ici
   auparavant était le seul contour non conforme du bloc accessibilité :
   outline-offset le pose en partie sur le fond de page, où il ne dépassait pas
   1,40:1 (seuil UI : 3:1). L'anthracite y atteint 14,65:1. */
.skip-link:focus,
.skip-link:focus-visible {
  left: 0;
  outline: 2px solid var(--color-focus-ring);
  outline-offset: 2px;
}

/* Forcer le texte blanc au survol/focus : la règle globale a:hover le
   repasserait en anthracite (invisible sur ce fond foncé). */
.skip-link:hover,
.skip-link:focus {
  color: var(--color-bg-white);
  text-decoration: none;
}

/* --- Focus visible cohérent sur tous les éléments interactifs ---
   Uniquement au focus clavier (:focus-visible), pour ne pas gêner la souris. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
[role="button"]:focus-visible,
.active-filter-badge:focus-visible,
.filter-tag-check-label:focus-visible,
.recipe-card-link:focus-visible {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: 2px;
}

/* Le conteneur principal reçoit le focus via le skip-link (tabindex=-1) mais
   ne doit pas afficher de contour visible (ce n'est pas un contrôle interactif). */
#main-content:focus {
  outline: none;
}

/* --- Réduction des animations (préférence système) ---
   Neutralise animations et transitions décoratives sur tout le site. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
/* ============================================
   LAYOUTS - Structure globale réutilisable
   ============================================
   Navigation, headers, footers, containers
   ============================================ */

/* === Navigation principale === */
.main-header {
  background: var(--color-bg-white);
  border-bottom: 1px solid var(--color-border-light);
  /* Collé en haut du viewport : la navigation reste atteignable pendant tout
     le défilement. Sa hauteur réelle est publiée en --header-height par le
     controller sticky-header, que consomment les blocs sticky des pages. */
  position: sticky;
  top: 0;
  /* Au-dessus du contenu et des éléments fixes de page (rail du menu 160,
     bouton flottant « Filtres » 150), sous le backdrop (180) et le drawer des
     filtres (200) qui doivent pouvoir le recouvrir. */
  z-index: 170;
  /* Padding « design » via variables pour rester DRY entre breakpoints ;
     env(safe-area-inset-*) ajouté ici → le fond du header borde l'écran (encoche/barre d'état)
     tandis que son contenu reste dans la zone sûre. */
  --header-pad-y: 0.5rem;
  --header-pad-x: 2rem;
  padding:
    calc(var(--header-pad-y) + env(safe-area-inset-top))
    calc(var(--header-pad-x) + env(safe-area-inset-right))
    var(--header-pad-y)
    calc(var(--header-pad-x) + env(safe-area-inset-left));
  box-shadow: var(--shadow-sm);
  flex-shrink: 0;
}

.nav-container {
  margin: 0 auto;
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 1rem;
}

.nav-brand {
  display: flex;
  align-items: center;
}

.nav-brand .brand-link {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
  transition: opacity 0.2s;
}

.nav-brand .brand-link:hover {
  opacity: 0.85;
}

.nav-brand .logo {
  height: 40px;
  width: auto;
  max-width: 200px;
  display: block;
}

.nav-content {
  flex: 1;
  display: flex;
  justify-content: center;
  align-items: center;
}

.nav-links {
  display: flex;
  align-items: center;
  gap: 1.5rem;
}

.user-greeting {
  color: var(--color-ink);
  font-size: var(--font-size-lg);
  font-weight: 600;
  text-align: center;
  animation: fadeIn 0.5s ease-in;
}

.user-email {
  color: var(--color-ink-3);
  font-size: var(--font-size-sm);
}

.nav-link {
  color: var(--color-ink-3);
  text-decoration: none;
  font-weight: 500;
  transition: var(--transition-normal);
}

.nav-link:hover {
  color: var(--color-ink);
}

/* === Liens de navigation header (utilisateur connecté) === */
.header-nav-links {
  display: flex;
  align-items: center;
  gap: 20px;
  margin-left: 16px;
}

.header-nav-link {
  font-size: 1rem;
  color: var(--color-ink-3);
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
  transition: color 0.15s;
}

.header-nav-link:hover {
  color: var(--color-ink);
}

.header-nav-link.active {
  color: var(--color-ink);
  border-bottom: 1.5px solid var(--color-accent);
  padding-bottom: 2px;
}

.header-nav-link.active:hover {
  text-decoration: none;
}

/* Compteur porté par le lien « Import » (admin) : nombre de recettes
   importées en attente de validation. Partagé par le header et son miroir
   dans le menu déroulant mobile. */
.nav-badge {
  display: inline-block;
  min-width: 18px;
  padding: 1px 6px;
  border-radius: var(--radius-badge);
  background: var(--color-accent-light);
  color: var(--color-accent-text);
  font-size: 0.6875rem;
  font-weight: 600;
  line-height: 1.45;
  text-align: center;
}

/* Dans le header le lien est inline : l'espacement du badge n'est pas porté
   par le gap flex du menu déroulant, il faut le poser ici. */
.header-nav-link .nav-badge {
  margin-left: 6px;
  vertical-align: middle;
}

.btn-signup {
  background: var(--color-primary);
  color: var(--color-bg-white);
  padding: 0.5rem 1rem;
  border-radius: var(--radius-button);
}

.btn-signup:hover {
  background: var(--color-primary-hover);
  color: var(--color-bg-white);
}

/* === Menu utilisateur dropdown === */
.user-menu {
  position: relative;
  display: flex;
  align-items: center;
}

.user-menu-button {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  background: transparent;
  border: none;
  padding: 0.35rem 0.5rem;
  cursor: pointer;
  transition: opacity 0.15s;
  font-family: inherit;
}

.user-menu-button:hover {
  opacity: 0.8;
}

.user-avatar {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--color-primary);
  color: var(--color-bg-white);
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 500;
  font-size: 0.7rem;
  flex-shrink: 0;
}

.user-name {
  color: var(--color-ink-2);
  font-weight: 500;
  font-size: 13px;
}

.chevron {
  color: var(--color-ink-4);
  transition: transform 0.2s ease;
}

.user-menu-button.active .chevron {
  transform: rotate(180deg);
}

/* Icône hamburger — masquée par défaut (visible uniquement sur mobile) */
.hamburger-icon {
  display: none;
}

/* Dropdown menu */
/* z-index local au contexte d'empilement du header sticky : c'est le z-index du
   header (170) qui décide du rendu face au contenu de la page. */
.user-dropdown {
  position: absolute;
  top: calc(100% + 0.35rem);
  right: 0;
  min-width: 180px;
  background: var(--color-bg-white);
  border-radius: var(--radius-button);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-8px);
  transition: all 0.2s ease;
  z-index: 1000;
  border: var(--border-width) solid var(--color-border-light);
}

.user-dropdown.active {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}

/* En-tête d'identité du dropdown (mobile uniquement) — voir media query.
   Masqué en desktop où l'avatar + nom sont déjà visibles dans le header. */
.dropdown-identity {
  display: none;
  align-items: center;
  gap: 0.6rem;
  padding: 0.75rem 1rem;
}

.dropdown-identity-avatar {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: var(--color-primary);
  color: var(--color-bg-white);
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 500;
  font-size: 0.8rem;
  flex-shrink: 0;
}

.dropdown-identity-body {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.dropdown-identity-name {
  font-size: 14px;
  font-weight: 600;
  color: var(--color-ink);
}

.dropdown-identity-email {
  font-size: 12px;
  color: var(--color-ink-3);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Liens de navigation dans le dropdown (tablette/mobile uniquement) */
.dropdown-nav-links {
  display: none;
}

.dropdown-divider {
  height: 1px;
  background: var(--color-border-light);
  margin: 0;
}

.dropdown-links {
  padding: 0.25rem 0;
}

.dropdown-link {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.625rem 1rem;
  color: var(--color-ink);
  text-decoration: none;
  transition: background 0.15s;
  font-size: 13px;
  font-weight: 500;
}

.dropdown-link:hover {
  background: var(--color-bg-secondary);
  color: var(--color-ink);
}

.dropdown-link svg {
  flex-shrink: 0;
  color: currentColor;
  opacity: 0.7;
}

.dropdown-link:hover svg {
  opacity: 1;
}

.dropdown-link.logout {
  color: var(--color-danger);
}

.dropdown-link.logout:hover {
  background: var(--color-bg-secondary);
  color: var(--color-danger-hover);
}

/* === Contenu principal === */
/* Le contenu prend toute la hauteur restante après le header */
.main-content {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
  /* Zone sûre : le contenu (et le formulaire d'ajout de course qui y vit) reste
     hors de l'encoche latérale (paysage) et de l'indicateur d'accueil (bas). */
  padding-left: env(safe-area-inset-left);
  padding-right: env(safe-area-inset-right);
  padding-bottom: env(safe-area-inset-bottom);
}

/* Sur la home, le contenu peut s'étendre au-delà du viewport */
.home-page .main-content {
  overflow: visible;
}

/* === Container de page === */
.page-container {
  max-width: 1400px;
  margin: 0 auto;
  padding: 2rem 1rem;
}

/* === Header de page === */
.page-header {
  display: flex;
  flex-direction: column;
  align-items: center;
  max-width: 800px;
  margin-left: auto;
  margin-right: auto;
  /* Respiration entre le titre de page et le bloc de contenu qui suit. */
  margin-bottom: 1.5rem;
}

/* La taille suit l'échelle fluide de la règle de base h1 (global.css). */
.page-header h1 {
  font-weight: 600;
  color: var(--color-ink);
  margin: 0;
  text-align: center;
  width: 100%;
}

.page-header .btn-link {
  align-self: flex-end;
}

/* === Responsive navigation === */
@media (max-width: 1024px) {
  .main-header {
    --header-pad-x: 1rem;
  }

  .nav-container {
    gap: 0.5rem;
    flex-wrap: nowrap;
  }

  .nav-brand .logo {
    height: 32px;
    max-width: 160px;
  }

  /* Masquer les nav links desktop */
  .header-nav-links {
    display: none;
  }

  /* Masquer avatar, nom, chevron — afficher le hamburger */
  .user-avatar,
  .user-name,
  .chevron {
    display: none;
  }

  .hamburger-icon {
    display: block;
    color: var(--color-ink);
  }

  .user-menu-button {
    padding: 0.5rem;
  }

  /* Afficher l'en-tête d'identité (profil) en haut du dropdown mobile */
  .dropdown-identity {
    display: flex;
  }

  /* Afficher les liens de navigation dans le dropdown mobile */
  .dropdown-nav-links {
    display: block;
    padding: 0.25rem 0;
  }

  /* Dropdown pleine largeur sur mobile */
  .user-dropdown {
    right: 0;
    left: auto;
    min-width: 200px;
    max-width: calc(100vw - 2rem);
  }

  .page-container {
    padding: 1rem 0.75rem;
  }

  .page-header {
    flex-direction: column;
    gap: 1rem;
    align-items: stretch;
  }

  .page-header h1 {
    text-align: center;
  }
}

@media (min-width: 768px) and (max-width: 1024px) {
  .main-header {
    --header-pad-y: 1rem;
    --header-pad-x: 1.5rem;
  }

  .nav-brand .logo {
    height: 36px;
    max-width: 180px;
  }

  .user-email {
    display: none;
  }

  .page-container {
    padding: 1.5rem 1rem;
  }
}
/* ============================================
   COMPONENTS - Composants UI réutilisables
   ============================================
   Boutons, cards, badges, modals, forms
   Utilise les variables CSS définies dans variables.css
   pour une gestion centralisée des couleurs.
   ============================================ */

/* === BOUTONS ===
   Système de boutons unique — source de vérité.
   Base       : .btn
   Variantes  : .btn-primary   → action principale (anthracite)
                .btn-secondary → action neutre
                .btn-white     → action neutre sur fond clair (blanc franc + bordure fine)
                .btn-accent    → unique CTA accent (ambre) — cf. « Démarrer un menu »
                .btn-danger    → action destructive (rouge)
                .btn-link      → variante « ghost » / tertiaire (transparent, sans bordure)
   Tailles    : .btn-sm / .btn-md / .btn-lg (+ .btn-icon pour un padding icône compact)
   Sémantique couleur : une action principale = anthracite partout ; l'ambre est
   réservé au seul CTA accent et aux accents décoratifs (étoiles/saison, badges). */

.btn {
  padding: 0.75rem 1.5rem;
  border-radius: var(--radius-button);
  min-height: var(--min-touch-target);
  min-width: var(--min-touch-target);
  font-weight: 500;
  letter-spacing: 0.02em;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem; /* espace icône ↔ libellé pour les boutons avec icône SVG */
  transition: background var(--transition-fast), color var(--transition-fast),
              border-color var(--transition-fast), box-shadow var(--transition-fast),
              transform var(--transition-fast);
  border: none;
  cursor: pointer;
  font-size: var(--font-size-base);
  text-align: center;
}

.btn-primary {
  background: var(--color-primary);
  color: var(--color-bg-white);
  border: none;
}

.btn-primary:hover {
  background: var(--color-primary-hover);
  color: var(--color-bg-white);
  text-decoration: none;
}

.btn-secondary {
  background: var(--color-secondary);
  color: var(--color-secondary-text);
  border: var(--border-thin);
}

/* Survol des boutons clairs : on garde le fond (le virer en gris le rapprochait
   trop du fond de page). Feedback discret et moderne par légère élévation
   (ombre + montée d'1px), bordure plus marquée et texte assombri. */
.btn-secondary:hover {
  background: var(--color-secondary);
  color: var(--color-ink);
  border-color: var(--color-border-strong);
  box-shadow: var(--shadow-md);
  transform: translateY(-1px);
  text-decoration: none;
}

/* Variante claire du bouton neutre : blanc franc et bordure fine — même langage
   visuel que le bouton secondaire du hero d'accueil (.btn-hero--secondary, qui en
   est la déclinaison à l'échelle du hero). À préférer à .btn-secondary là où le
   beige se confondrait avec le fond de la page (--color-bg-main / -secondary).
   Sélecteur composé avec .btn (deux classes) : authentication.css redéfinit .btn
   avec border: none et est chargé après ce fichier — à spécificité égale la
   bordure serait perdue, et un bouton blanc sans bordure disparaîtrait sur les
   fonds clairs de l'app. */
.btn.btn-white {
  background: var(--color-bg-white);
  color: var(--color-secondary-text);
  border: var(--border-thin);
}

/* Même feedback discret que .btn-secondary : le blanc reste, l'affordance passe
   par l'élévation et une bordure plus marquée. */
.btn.btn-white:hover {
  color: var(--color-ink);
  border-color: var(--color-border-strong);
  box-shadow: var(--shadow-md);
  transform: translateY(-1px);
  text-decoration: none;
}

/* Unique CTA accent de l'app (ambre). Texte en --color-ink : accent-text
   (#92400E) ne passe que 4,24:1 sur l'ambre et échoue sur accent-hover ;
   ink passe AA sur les deux fonds (cf. audit R1.3). */
.btn-accent {
  background: var(--color-accent);
  color: var(--color-ink);
  border: none;
}

.btn-accent:hover {
  background: var(--color-accent-hover);
  color: var(--color-ink);
  text-decoration: none;
}

.btn-link {
  background: transparent;
  color: var(--color-ink-3);
  border: none;
  text-decoration: none;
  padding: 0.5rem 1rem;
}

.btn-link:hover {
  color: var(--color-ink);
  text-decoration: none;
}

.btn-danger {
  background: var(--color-danger);
  color: var(--color-bg-white);
  border: none;
}

.btn-danger:hover {
  background: var(--color-danger-hover);
  color: var(--color-bg-white);
}

/* Tailles partagées du système (utilisées avec .btn) */
.btn-sm {
  padding: 0.375rem 0.875rem;
  font-size: var(--font-size-xs);
  min-height: unset;
}

/* Taille intermédiaire, pour une action posée dans une ligne de liste : la
   taille par défaut (16 px, 44 px de haut) écrase une liste dense, et .btn-sm
   n'y offre plus une cible confortable — c'est ce manque qui poussait les
   composants à se fabriquer des boutons rétrécis maison. */
.btn-md {
  padding: 0.5rem 1.125rem;
  font-size: var(--font-size-sm);
  min-height: 38px;
}

.btn-lg {
  padding: 0.875rem 2rem;
  font-size: var(--font-size-lg);
}

.btn-icon {
  padding: 0.375rem 0.625rem;
  font-size: var(--font-size-xs);
}

/* Boutons icône */
.icon-btn, .icon-btn-edit, .icon-btn-delete {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-icon-btn);
  color: var(--color-ink-4);
  background: transparent;
  border: var(--border-thin);
  width: var(--min-touch-target);
  height: var(--min-touch-target);
  cursor: pointer;
  transition: var(--transition-normal);
  margin: 0 0.25rem;
  padding: 0;
}

.icon-btn:hover, .icon-btn-edit:hover {
  color: var(--color-ink);
  border-color: var(--color-border-strong);
}

.icon-btn-delete:hover {
  color: var(--color-danger);
  border-color: var(--color-danger);
}

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

.edit-btn:hover {
  color: var(--color-ink);
  border-color: var(--color-border-strong);
}

.delete-btn:hover {
  color: var(--color-danger);
  border-color: var(--color-danger);
}

/* Responsive boutons */
@media (max-width: 767px) {
  .btn {
    padding: 0.75rem 1rem;
    font-size: var(--font-size-sm);
  }

  /* .btn-sm annule min-height pour rester compact en desktop ; au doigt, il faut
     rétablir une cible tactile d'au moins 44px. */
  .btn-sm {
    min-height: var(--min-touch-target);
  }

  .icon-btn {
    min-width: var(--min-touch-target);
    min-height: var(--min-touch-target);
    width: 32px;
    height: 32px;
    border-radius: var(--radius-icon-btn);
    background: var(--color-bg-main);
    flex-shrink: 0;
  }

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

  .icon-btn:hover {
    background: var(--color-bg-secondary);
  }
}

/* Variante discrète : icône seule, sans cadre ni fond, pour une action
   secondaire glissée dans un en-tête (ex. supprimer son avis). La cible
   tactile de 44px est conservée, seul le cadre disparaît.
   Placée après le bloc responsive pour neutraliser le fond appliqué à
   `.icon-btn` en mobile. */
.icon-btn--quiet {
  border-color: transparent;
  background: transparent;
  margin: 0;
}

/* Suppression discrète : rouge dès le repos, atténué tant qu'on ne survole pas. */
.icon-btn-delete.icon-btn--quiet {
  color: var(--color-danger);
  opacity: 0.65;
}

.icon-btn-delete.icon-btn--quiet:hover {
  opacity: 1;
  background: var(--color-danger-light);
}

/* === CARDS === */

.card {
  background: var(--color-bg-white);
  border: var(--border-thin);
  border-radius: var(--radius-card);
  box-shadow: none;
  padding: 2rem;
}

.card-compact {
  background: var(--color-bg-white);
  border-radius: var(--radius-card-compact);
  padding: 1rem;
  box-shadow: none;
  border: var(--border-thin);
}

.card-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 0.5rem;
  margin-bottom: 0.75rem;
}

.card-title {
  font-size: var(--font-size-lg);
  font-weight: 600;
  color: var(--color-ink);
  line-height: 1.3;
  flex: 1;
  margin: 0;
}

.card-subtitle {
  font-size: var(--font-size-xs);
  color: var(--color-ink-3);
  font-style: italic;
  font-weight: 400;
  margin-top: 0.125rem;
}

.card-body {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
}

/* === BADGES === */

.badge {
  padding: 0.375rem 0.875rem;
  border-radius: var(--radius-badge) !important;
  font-size: var(--font-size-xs);
  font-weight: 500;
  white-space: nowrap;
  display: inline-block;
}

.badge-orange {
  background: var(--color-bg-secondary);
  color: var(--color-ink-3);
  border: var(--border-thin);
}

/* === PALETTE DES RAYONS ===
   Une couleur par rayon de supermarché, portée par l'attribut data-category
   (la clé de l'enum Ingredient#category). Partagée par les deux écrans qui
   affichent des rayons — sections de la liste de courses et badges du catalogue
   d'ingrédients — pour qu'un rayon garde la même couleur de l'un à l'autre.

   Décor uniquement : ces teintes n'ont pas le contraste d'un texte, le libellé
   reste écrit en toutes lettres et en --color-ink. La couleur différencie d'un
   coup d'œil, elle ne porte jamais l'information à elle seule.

   Le repli gris couvre « Autre » et le rayon absent (ligne de courses libre). */
[data-category]                                { --category-color: #9E9E9E; }
[data-category="fruits_legumes"]               { --category-color: #5A8A5B; }
[data-category="boucherie_viande"]             { --category-color: #B05B5B; }
[data-category="charcuterie_traiteur"]         { --category-color: #C4768A; }
[data-category="poissonnerie"]                 { --category-color: #4E7FA8; }
[data-category="fromagerie_coupe"]             { --category-color: #C49A27; }
[data-category="boulangerie_patisserie"]       { --category-color: #B08060; }
[data-category="produits_laitiers"]            { --category-color: #6A9BB5; }
[data-category="produits_frais_libre_service"] { --category-color: #6B9B72; }
[data-category="glaces_desserts_glaces"]       { --category-color: #7AABC0; }
[data-category="legumes_surgeles"]             { --category-color: #4E8A8A; }
[data-category="viandes_poissons_surgeles"]    { --category-color: #6E8A9E; }
[data-category="produits_aperitifs_surgeles"]  { --category-color: #C47845; }
[data-category="epicerie_salee"]               { --category-color: #8A7055; }
[data-category="epicerie_sucree"]              { --category-color: #9B7AB5; }
[data-category="boissons"]                     { --category-color: #3D85C8; }
[data-category="petit_dejeuner"]               { --category-color: #C49A27; }
[data-category="produits_monde"]               { --category-color: #B08040; }
[data-category="hygiene_beaute"]               { --category-color: #B87A8A; }
[data-category="entretien_maison"]             { --category-color: #7A8A9E; }
[data-category="papeterie_fournitures"]        { --category-color: #6E9B9B; }

/* Badge de catégorie (rayon) - Charte graphique EasyMeal
   Même vocabulaire visuel que les sections de la liste de courses : liseré
   coloré à gauche, aplat très dilué, texte en encre pleine. */
.category-badge {
  /* Sans rayon, les deux termes du mélange sont identiques : le badge retombe
     exactement sur le beige neutre d'avant la couleur. */
  background: color-mix(in srgb, var(--category-color, var(--color-bg-secondary)) 12%, var(--color-bg-secondary));
  color: var(--color-ink);
  border: var(--border-thin);
  border-left: 2.5px solid var(--category-color, var(--color-border-strong));
  padding: 0.375rem 0.875rem;
  border-radius: var(--radius-badge);
  font-size: var(--font-size-sm);
  font-weight: 500;
  white-space: nowrap;
  display: inline-block;
}

.badge-beige {
  background: var(--color-bg-secondary);
  color: var(--color-ink-3);
  border: var(--border-thin);
}

/* Badges de régime alimentaire */
.badge-omnivore {
  background: var(--color-bg-secondary);
  color: var(--color-ink-3);
  border: var(--border-thin);
  border-radius: var(--radius-badge);
  font-size: 0.75rem;
  font-weight: 500;
  padding: 2px 8px;
}

.badge-vegetarien {
  background: var(--color-success-light);
  color: var(--color-success-text);
  border-radius: var(--radius-badge);
  font-size: 0.75rem;
  font-weight: 500;
  padding: 2px 8px;
}

.badge-vegan {
  background: var(--color-success-light);
  color: var(--color-success-text);
  border-radius: var(--radius-badge);
  font-size: 0.75rem;
  font-weight: 500;
  padding: 2px 8px;
}

.badge-pescetarien {
  background: var(--color-info-light);
  color: var(--color-info-text);
  border-radius: var(--radius-badge);
  font-size: 0.75rem;
  font-weight: 500;
  padding: 2px 8px;
}

.badge-season {
  background: var(--color-accent-light);
  color: var(--color-accent-text);
  border-radius: var(--radius-badge);
  font-size: 0.75rem;
  font-weight: 500;
  padding: 2px 8px;
}

/* Section headings */
.section-heading {
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-ink-3);
  padding-bottom: 6px;
  border-bottom: var(--border-thin);
  margin-bottom: 1rem;
}

.badge-red {
  background: var(--color-danger);
  color: var(--color-bg-white);
}

.badge-green {
  background: var(--color-success-light);
  color: var(--color-success-text);
}

.badge-blue {
  background: var(--color-info-light);
  color: var(--color-info-text);
}

/* Tags de mois */
.month-tag {
  padding: 0.25rem 0.5rem;
  border-radius: var(--radius-badge);
  font-size: 0.7rem;
  font-weight: 500;
  background: var(--color-bg-secondary);
  color: var(--color-ink-3);
}

/* Responsive badges */
@media (max-width: 767px) {
  .badge,
  .category-badge {
    padding: 0.6rem 0.9rem;
    border-radius: var(--radius-badge);
  }

  .month-tag {
    font-size: var(--font-size-xs);
    padding: 0.125rem 0.375rem;
  }
}

/* === MODAL === */

.modal-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.5);
  z-index: 10000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
  animation: fadeIn 0.2s ease-out;
}

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

.modal-container {
  background: var(--color-bg-white);
  border-radius: var(--radius-card);
  box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
  max-width: 500px;
  width: 100%;
  /* Plafonne la hauteur et rend le contenu défilable : une modale longue (ou un
     écran en paysage) ne déborde plus hors de l'écran. */
  max-height: 90dvh;
  overflow-y: auto;
  animation: slideUp 0.3s ease-out;
}

@keyframes slideUp {
  from {
    opacity: 0;
    transform: translateY(20px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.modal-content {
  padding: 2rem;
}

.modal-header {
  margin-bottom: 1.5rem;
  padding-bottom: 1rem;
  border-bottom: 2px solid var(--color-bg-secondary);
}

.modal-header h3 {
  color: var(--color-ink-2);
  font-size: var(--font-size-xl);
  font-weight: 600;
  margin: 0;
}

.modal-body {
  margin-bottom: 2rem;
  color: var(--color-ink-2);
  line-height: 1.6;
}

.modal-body p {
  margin: 0 0 0.75rem 0;
}

.modal-warning {
  color: var(--color-danger);
  font-weight: 500;
  font-size: var(--font-size-sm);
}

.modal-actions {
  display: flex;
  gap: 1rem;
  justify-content: flex-end;
}

/* Modal responsive */
@media (max-width: 767px) {
  .modal-overlay {
    padding: 0.5rem;
  }

  .modal-content {
    padding: 1.5rem;
  }

  .modal-header h3 {
    font-size: var(--font-size-lg);
  }

  .modal-actions {
    flex-direction: column-reverse;
  }

  .modal-actions .btn {
    width: 100%;
  }
}

/* === SLIDEOUT PANEL (Panneau latéral) === */

.slideout-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.5);
  z-index: 9998;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.3s ease, visibility 0.3s ease;
}

.slideout-overlay.open {
  opacity: 1;
  visibility: visible;
}

.slideout-panel {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  width: 100%;
  max-width: 480px;
  background: var(--color-bg-white);
  z-index: 9999;
  transform: translateX(100%);
  transition: transform 0.3s ease;
  display: flex;
  flex-direction: column;
  box-shadow: -4px 0 20px rgba(0, 0, 0, 0.15);
}

.slideout-panel.open {
  transform: translateX(0);
}

.slideout-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1.25rem 1.5rem;
  border-bottom: 2px solid var(--color-bg-secondary);
  background: var(--color-bg-secondary);
}

.slideout-header h3 {
  margin: 0;
  font-size: var(--font-size-lg);
  font-weight: 600;
  color: var(--color-ink);
}

.slideout-close {
  background: none;
  border: none;
  font-size: 1.5rem;
  cursor: pointer;
  color: var(--color-ink);
  padding: 0.25rem;
  line-height: 1;
  transition: color 0.2s ease;
}

.slideout-close:hover {
  color: var(--color-ink-2);
}

.slideout-content {
  flex: 1;
  overflow-y: auto;
  padding: 1.5rem;
}

/* Formulaire dans le slideout */
.ingredient-form-slideout {
  display: flex;
  flex-direction: column;
  min-height: 100%;
}

.slideout-form-content {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

/* .form-group porte un margin-bottom global (authentication.css) : ici
   l'espacement vertical est déjà assuré par le gap des conteneurs flex
   (.slideout-form-content et .optional-fields-content), le margin s'y ajoutait. */
.ingredient-form-slideout .form-group {
  margin-bottom: 0;
}

.slideout-form-actions {
  display: flex;
  gap: 0.75rem;
  padding-top: 1.5rem;
  margin-top: auto;
  border-top: 1px solid var(--color-border);
}

.slideout-form-actions .btn {
  flex: 1;
}

/* Champs optionnels repliables */
.optional-fields {
  margin-top: 1rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-input);
}

.optional-fields-toggle {
  padding: 0.75rem 1rem;
  cursor: pointer;
  color: var(--color-ink);
  font-weight: 500;
  font-size: var(--font-size-sm);
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.optional-fields-toggle:hover {
  color: var(--color-ink-2);
}

.optional-fields[open] .optional-fields-toggle {
  border-bottom: 1px solid var(--color-border);
}

.optional-fields-content {
  padding: 1rem;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

/* Bouton créer un ingrédient */
.preparations-actions {
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
  margin-bottom: 1rem;
}

/* Responsive slideout */
@media (max-width: 767px) {
  .slideout-panel {
    max-width: 100%;
  }

  .preparations-actions {
    flex-direction: column;
  }
}

/* === FORMS === */

/* SimpleForm overrides pour disposition verticale */
.simple_form .input {
  margin-bottom: 0;
}

.simple_form .form-group,
.simple_form div.input {
  display: flex;
  flex-direction: column;
}

.simple_form label {
  margin-bottom: 0.5rem;
}

.simple_form .hint {
  display: block;
  margin-top: 0.375rem;
}

.ingredient-form {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.form-container {
  max-width: 800px;
  margin: 0 auto;
  background: var(--color-bg-white);
  padding: 2rem;
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-md);
}

.form-group {
  display: flex;
  flex-direction: column;
  gap: 0.8rem;
}

.form-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
  margin-bottom: 1rem;
}

/* Label de formulaire — définition canonique unique, utilisée par toutes les
   vues. Les formulaires recette la surchargent via .rf-form (voir recipes.css)
   et l'espacement propre à l'auth via .auth-form (voir authentication.css). */
.form-label {
  display: block;
  font-weight: 600;
  color: var(--color-ink);
  font-size: var(--font-size-sm);
  margin-bottom: 0.5rem;
}

/* Astérisque « champ requis » (simple_form) */
.form-label abbr[title] {
  color: var(--color-danger);
  text-decoration: none;
  margin-left: 0.125rem;
}

.form-hint {
  font-size: var(--font-size-base);
  color: var(--color-ink-3);
  margin-top: -0.25rem;
}

.form-input,
.form-select,
.form-textarea {
  padding: 0.75rem 1rem;
  border: var(--border-thin);
  border-radius: var(--radius-input);
  font-size: var(--font-size-base);
  background: var(--color-bg-white);
  color: var(--color-ink);
  transition: var(--transition-normal);
  font-family: inherit;
}

.form-input:focus,
.form-select:focus,
.form-textarea:focus {
  outline: none;
  border-color: var(--color-primary);
  box-shadow: 0 0 0 2px var(--color-primary-light);
}

.form-input[readonly] {
  background: var(--color-bg-secondary);
  color: var(--color-ink-4);
  border-color: var(--color-border-light);
  cursor: not-allowed;
}

.form-textarea {
  min-height: 120px;
  resize: vertical;
}



.form-errors {
  background: var(--color-danger-light);
  border: 2px solid var(--color-danger);
  border-radius: var(--radius-card-compact);
  padding: 1rem;
  margin-bottom: 1.5rem;
}

.form-errors h3 {
  color: var(--color-danger-hover);
  font-size: var(--font-size-base);
  margin: 0 0 0.5rem 0;
  font-weight: 600;
}

.form-errors ul {
  margin: 0;
  padding-left: 1.5rem;
  color: var(--color-danger-hover);
}

/* === ERREURS INLINE SOUS LES CHAMPS === */

.field-errors {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  margin-top: 0.25rem;
}

.field-error {
  color: var(--color-danger);
  font-size: var(--font-size-base);
  font-weight: 500;
}

/* Style pour les champs en erreur */
.form-group.has-error .form-input,
.form-group.has-error .form-select,
.form-group.has-error .form-textarea,
.has-error .form-input,
.has-error .form-select,
.has-error .form-textarea {
  border-color: var(--color-danger);
  background: var(--color-danger-light);
}

.form-group.has-error .form-input:focus,
.form-group.has-error .form-select:focus,
.form-group.has-error .form-textarea:focus,
.has-error .form-input:focus,
.has-error .form-select:focus,
.has-error .form-textarea:focus {
  box-shadow: 0 0 0 2px rgba(28, 25, 22, 0.08);
}

.form-group.has-error .form-label,
.has-error .form-label {
  color: var(--color-danger);
}

/* === SIMPLE_FORM ERROR STYLES === */

/* Wrapper avec erreur (simple_form) */
.field_with_errors .form-input,
.field_with_errors .form-select,
.field_with_errors .form-textarea {
  border-color: var(--color-danger);
  background-color: var(--color-danger-light);
}

.field_with_errors .form-input:focus,
.field_with_errors .form-select:focus,
.field_with_errors .form-textarea:focus {
  box-shadow: 0 0 0 2px rgba(220, 38, 38, 0.1);
}

/* Message d'erreur inline (simple_form) */
.invalid-feedback,
span.error {
  color: var(--color-danger);
  font-size: var(--font-size-xs);
  font-weight: 500;
  margin-top: 6px;
  display: block;
}

/* Notification d'erreur en haut du formulaire (simple_form) */
.error-notification,
.error_notification {
  background: var(--color-danger-light);
  border: 1px solid var(--color-danger);
  border-radius: var(--radius-card-compact);
  padding: 16px 20px;
  margin-bottom: 24px;
  color: var(--color-danger-text);
  font-weight: 500;
}

.error-notification p,
.error_notification p {
  margin: 0;
}

.form-actions {
  display: flex;
  gap: 1rem;
  padding-top: 2rem;
  border-top: 2px solid var(--color-bg-secondary);
}

/* Checkboxes et radio buttons */
input[type="checkbox"],
input[type="radio"] {
  width: 18px;
  height: 18px;
  cursor: pointer;
  accent-color: var(--color-primary);
}

/* Grille de checkboxes (pour les mois) */
.months-grid {
  display: grid;
  /* Le nombre de colonnes suit la largeur du conteneur (formulaire pleine page
     ou slideout de 480 px), pas celle du viewport : une colonne ne descend
     jamais sous 5rem et ne dépasse jamais un sixième de la ligne, ce qui donne
     6 colonnes au large, 4 dans le slideout et 3 sur mobile — sans media query,
     donc sans risque de débordement dans un conteneur étroit. */
  grid-template-columns: repeat(auto-fill, minmax(max(5rem, (100% - 5 * 0.5rem) / 6), 1fr));
  gap: 0.5rem;
  padding: 0.75rem;
  background: var(--color-bg-main);
  border-radius: var(--radius-card-compact);
}

.month-checkbox {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem;
  cursor: pointer;
  border-radius: var(--radius-input);
  transition: background 0.2s;
  font-size: var(--font-size-sm);
}

.month-checkbox:hover {
  background: var(--color-bg-secondary);
}

.month-checkbox input[type="checkbox"] {
  accent-color: var(--color-primary);
}

/* Responsive forms */
@media (max-width: 767px) {
  .ingredient-form {
    gap: 0.75rem;
  }

  .form-container {
    padding: 1.5rem 1rem;
  }

  .form-row {
    grid-template-columns: 1fr;
  }

  .form-input,
  .form-select,
  .form-textarea {
    font-size: 16px; /* Évite le zoom sur iOS */
  }

  .form-actions {
    flex-direction: column;
  }

  .form-actions .btn {
    width: 100%;
  }
}

/* === UTILITIES === */

.text-muted {
  color: var(--color-ink-3);
  font-style: italic;
  font-size: var(--font-size-sm);
}

.unit-inline {
  font-size: var(--font-size-sm);
  font-weight: 400;
  font-style: italic;
  color: var(--color-ink-3);
}

/* Empty state */
.empty-state {
  text-align: center;
  padding: 3rem 1rem;
  background: var(--color-bg-white);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-md);
}

.empty-state p {
  font-size: var(--font-size-lg);
  color: var(--color-ink-3);
  margin-bottom: 1.5rem;
}

@media (max-width: 767px) {
  .empty-state {
    padding: 2rem 1rem;
  }

  .empty-state p {
    font-size: var(--font-size-base);
  }
}

/* ============================================
   ICÔNES SVG INLINE
   ============================================
   Système de colorisation via CSS.
   Monocouleur : le SVG utilise fill="currentColor",
     contrôlé par la propriété CSS `color`.
   Bicouleur   : le SVG utilise fill="var(--icon-color-1)"
     et fill="var(--icon-color-2)", définis via style inline.

   Usage dans le helper (application_helper.rb) :
     = inline_svg("mon-icone", css_class: "icon-title", color: "var(--color-primary)")
   ============================================ */

/* Base commune à toutes les icônes SVG inline */
.svg-icon {
  display: inline-block;
  vertical-align: middle;
  flex-shrink: 0;
}

/* Tailles standard */
.icon-sm   { width: 18px; height: 18px; }
.icon-md   { width: 24px; height: 24px; }
.icon-lg   { width: 32px; height: 32px; }
.icon-xl   { width: 48px; height: 48px; }

/* Icône de titre (h1, h2) — légèrement plus grande que le texte.
   Dimensionnée en em : elle suit la taille fluide du titre qui la porte,
   donc elle rétrécit avec lui sur mobile sans media query. */
.icon-title {
  width: 1.4em;
  height: 1.4em;
  margin-right: 0.4em;
}
/* ============================================
   CONTEXT BAR - Composant unifié de navigation contextuelle
   ============================================

   Deux modes d'affichage :
   - Mode back-link : lien retour simple
   - Mode breadcrumb : fil d'Ariane hiérarchique

   Le composant n'est pas une barre : ni fond, ni bordure, ni largeur
   contrainte. Il se pose en haut du contenu et occupe la gouttière libre à
   gauche de l'écran, là où les conteneurs de page (centrés et bornés) ne vont
   jamais — d'où l'absence de max-width, qui alignerait le lien sur la carte
   plutôt que sur le bord.

   Responsive :
   - Desktop : breadcrumb complet
   - Mobile (< 768px) : collapse vers lien retour
   ============================================ */

/* === Base === */
.context-bar {
  /* Taille de texte du composant, posée une seule fois ici : les deux modes en
     héritent, et le chevron comme le séparateur s'y accordent en em. Un lien de
     navigation reste lisible au premier coup d'œil, d'où le corps de texte
     plein plutôt qu'une taille de méta. */
  font-size: var(--font-size-base);
  /* Pas de padding bas : la respiration sous le lien est portée par la règle
     ci-dessous, sans quoi les deux s'additionnent. */
  padding: 14px 28px 0;
}

/* Les conteneurs de page réservent en haut la place d'un titre qui démarrait
   juste sous le header ; le lien retour s'intercalant, ce padding fait doublon
   et décroche le contenu (jusqu'à 48 px sous le lien sur la fiche
   utilisateur). On le remplace ici par une valeur unique, la même partout.

   Le sélecteur de voisinage cible exactement les cas concernés : les mêmes
   conteneurs employés sans lien retour (index des tags, des utilisateurs…)
   gardent leur padding d'origine.

   .rf-hero est écarté : son padding ne sépare pas, il dessine la hauteur de la
   bande sombre du titre. */
.context-bar + *:not(.rf-hero) {
  padding-top: 20px;
}

.context-bar__inner {
  display: flex;
  align-items: center;
  gap: 6px;
}

/* === Mode Back Link === */
.context-bar__back {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  color: var(--color-ink-3);
  text-decoration: none;
  transition: color var(--transition-fast);
}

.context-bar__back:hover {
  color: var(--color-ink);
  text-decoration: none;
}

/* Le chevron suit la taille du texte : une seule valeur pour les deux
   points de rupture. */
.context-bar__back-icon {
  width: 1em;
  height: 1em;
  flex-shrink: 0;
  opacity: 0.7;
}

/* === Mode Breadcrumb === */
.context-bar__breadcrumb {
  display: flex;
  align-items: center;
  gap: 6px;
}

.context-bar__crumb {
  color: var(--color-ink-3);
  text-decoration: none;
  transition: color var(--transition-fast);
}

.context-bar__crumb:hover {
  color: var(--color-ink);
  text-decoration: none;
}

/* Le chevron de séparation reste en retrait des libellés qu'il sépare, en em
   pour suivre la taille du composant. */
.context-bar__sep {
  font-size: 0.85em;
  color: var(--color-ink-4);
  user-select: none;
}

.context-bar__current {
  font-weight: 500;
  color: var(--color-ink);
}

/* === Responsive Mobile === */
@media (max-width: 767px) {
  .context-bar {
    font-size: var(--font-size-sm);
    padding: 10px 16px 0;
  }

  .context-bar + *:not(.rf-hero) {
    padding-top: 12px;
  }

  /* Sur mobile, le lien « retour » est une commande de navigation principale :
     hauteur de cible tactile de 44px (l'inline-flex centre le contenu). */
  .context-bar__back {
    min-height: var(--min-touch-target);
  }

  /* Sur mobile, on masque les crumbs intermédiaires pour n'afficher que le lien retour */
  .context-bar__breadcrumb .context-bar__crumb:not(:last-of-type),
  .context-bar__breadcrumb .context-bar__sep:not(:last-of-type),
  .context-bar__breadcrumb .context-bar__current {
    display: none;
  }

  /* Le dernier crumb devient un back-link sur mobile */
  .context-bar__breadcrumb .context-bar__crumb:last-of-type {
    display: inline-flex;
    align-items: center;
    gap: 4px;
  }

  /* Préfixe avec chevron sur mobile */
  .context-bar__breadcrumb .context-bar__crumb:last-of-type::before {
    content: "←";
    margin-right: 4px;
  }

  /* Masque le séparateur final sur mobile */
  .context-bar__breadcrumb .context-bar__sep:last-of-type {
    display: none;
  }
}
/* ======================================
   PAGE D'ACCUEIL - HOME
   ====================================== */

/* Hero Section - Section principale de la page d'accueil */
.hero-section {
  position: relative;
  --hero-logo-max-width: 300px;
  --hero-logo-margin-bottom: 3rem;
  --hero-content-padding: 2rem;
  --hero-content-max-width: 900px;
  --hero-greeting-size: 2.4rem;
  --hero-greeting-subtext-size: 1.3rem;
  --hero-greeting-subtext-margin-bottom: 32px;
  --hero-title-size: 2.8rem;
  --hero-title-margin-bottom: 1.5rem;
  --hero-subtitle-size: var(--font-size-2xl);
  --hero-subtitle-margin-bottom: 3rem;
  --hero-button-padding: 1.25rem 3rem;
  --hero-button-font-size: var(--font-size-xl);
  --hero-cta-direction: row;
  flex: 1;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: visible;
  background-color: var(--color-ink);
}

/* Image de fond avec overlay gris foncé */
.hero-background {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 1;
}

.hero-background::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  /* Voile porté de 0,40 à 0,55. Ici le texte est SOMBRE sur voile CLAIR, donc le
     pire cas n'est pas la zone la plus claire de la photo mais la plus SOMBRE —
     l'inverse du raisonnement habituel. Pire cas retenu : noir pur, cohérent avec
     la méthode employée pour les autres voiles du projet.
       voile 0,40 -> fond composé #666666 -> --color-ink à 3,05:1  ✗ (seuil 4,5:1)
       voile 0,51 -> seuil atteint tout juste
       voile 0,55 -> fond composé #8C8C8C -> --color-ink à 5,20:1  ✓
     Le titre et le sous-titre de la vitrine, ainsi que la salutation du tableau de
     bord, reposent directement sur cette surface : au-dessus d'un grain de poivre
     ou d'une ombre, ils passaient sous le seuil AA. */
  background: rgba(255, 255, 255, 0.55);
  z-index: 2;
}

.hero-background img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

/* Contenu principal du hero */
.hero-content {
  position: relative;
  z-index: 3;
  text-align: center;
  padding: var(--hero-content-padding);
  max-width: var(--hero-content-max-width);
  margin: 0 auto;
}

/* Logo EasyMeal */
.hero-logo {
  margin-bottom: var(--hero-logo-margin-bottom);
  animation: fadeIn 0.5s ease-out;
}

.hero-logo img {
  max-width: var(--hero-logo-max-width);
  width: 100%;
  height: auto;
}

/* ======================================
   GREETING (utilisateur connecté)
   ====================================== */

.hero-greeting {
  text-align: center;
}

/* Phrase de salutation — serif, couleur accent fort */
.hero-greeting-text {
  font-size: var(--hero-greeting-size);
  font-weight: 700;
  color: var(--color-primary);
  line-height: 1.2;
  margin-bottom: 1.5rem;
}

/* Sous-texte contextuel — voix douce, sans-serif système.
   La douceur est portée par la graisse (400) et la taille, PAS par la couleur :
   ce texte repose directement sur la photo voilée, où --color-ink-3 ne donnait que
   1,99:1 dans le pire cas. Aucun voile raisonnable ne le rattrapait — il aurait
   fallu 0,83 d'opacité, ce qui effacerait la photo. --color-ink donne 5,20:1. */
.hero-greeting-subtext {
  font-family: var(--font-sans);
  font-size: var(--hero-greeting-subtext-size);
  font-weight: 400;
  color: var(--color-ink);
  line-height: 1.6;
  margin-top: 10px;
  margin-bottom: var(--hero-greeting-subtext-margin-bottom);
}

/* Titre et sous-titre */
.hero-title {
  --hero-entry-delay: 0.05s;
  font-size: var(--hero-title-size);
  font-weight: 700;
  color: var(--color-ink);
  margin-bottom: var(--hero-title-margin-bottom);
  line-height: 1.2;
}

.hero-subtitle {
  --hero-entry-delay: 0.12s;
  font-size: var(--hero-subtitle-size);
  color: var(--color-ink);
  margin-bottom: var(--hero-subtitle-margin-bottom);
  line-height: 1.6;
  font-weight: 300;
  max-width: 700px;
  margin-left: auto;
  margin-right: auto;
}

/* Call-to-Action Button */
.hero-cta {
  --hero-entry-delay: 0.2s;
  display: flex;
  flex-direction: var(--hero-cta-direction);
  justify-content: center;
  gap: var(--spacing-md);
}

.btn-hero {
  display: inline-block;
  padding: var(--hero-button-padding, 1.25rem 3rem);
  font-size: var(--hero-button-font-size, var(--font-size-xl));
  font-weight: 600;
  color: var(--color-bg-white);
  background: var(--color-primary);
  border: none;
  border-radius: var(--radius-button);
  text-decoration: none;
  transition:
    color var(--transition-slow) ease,
    background-color var(--transition-slow) ease,
    border-color var(--transition-slow) ease,
    box-shadow var(--transition-slow) ease,
    transform var(--transition-slow) ease;
  cursor: pointer;
  box-shadow: var(--shadow-lg);
  position: relative;
  overflow: hidden;
  isolation: isolate;
}

.btn-hero:is(:hover, :focus, :active, :visited) {
  color: var(--color-bg-white);
  text-decoration: none;
}

.btn-hero::before {
  content: '';
  position: absolute;
  top: 0;
  left: -100%;
  width: 100%;
  height: 100%;
  background: var(--color-primary-hover);
  transition: left 0.4s ease;
  z-index: -1;
}

.btn-hero:hover::before {
  left: 0;
}

.btn-hero:hover {
  transform: translateY(-3px);
  box-shadow: var(--shadow-lg);
}

.btn-hero:active {
  transform: translateY(-1px);
  box-shadow: var(--shadow-md);
}

/* ======================================
   BTN-HERO SECONDARY (fond blanc, texte foncé)
   ====================================== */
.btn-hero--secondary {
  background: var(--color-bg-white);
  color: var(--color-ink-2);
  box-shadow: none;
  border: var(--border-thin);
}

.btn-hero--secondary:is(:hover, :focus, :active, :visited) {
  color: var(--color-ink);
  text-decoration: none;
}

/* Survol : bordure plus marquée. L'élévation (montée + ombre) vient de la règle
   .btn-hero:hover. On garde le fond blanc. */
.btn-hero--secondary:hover {
  border-color: var(--color-border-strong);
}

/* Pas de remplissage gris qui glisse (trop proche du fond) : on neutralise le
   pseudo-élément hérité de .btn-hero pour la variante claire. */
.btn-hero--secondary::before {
  display: none;
}

/* ======================================
   DASHBOARD — CARTE DE SYNTHÈSE (connecté)
   ====================================== */

/* Carte opérationnelle posée sur le hero (surface claire translucide,
   même langage visuel que .hero-step). */
.hero-card {
  --hero-entry-delay: 0.1s;
  margin: 0 auto 3rem;
  padding: var(--spacing-lg);
  text-align: left;
  background: rgba(255, 255, 255, 0.92);
  backdrop-filter: blur(6px);
  border: var(--border-thin);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-lg);
}

/* En-tête : titre du menu + pastille de statut */
.hero-card-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--spacing-sm);
  margin-bottom: var(--spacing-sm);
}

.hero-card-title-group {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--spacing-sm);
  min-width: 0;
}

.hero-card-title {
  font-size: var(--font-size-xl);
  font-weight: 700;
  color: var(--color-ink);
  line-height: 1.2;
}

.hero-card-meal-count {
  font-size: var(--font-size-base);
  font-style: italic;
  font-weight: 400;
  color: var(--color-ink-4);
  white-space: nowrap;
}

/* Pastille de statut — variantes sémantiques */
.hero-card-status {
  flex-shrink: 0;
  padding: 5px 12px;
  border-radius: var(--radius-badge);
  font-size: var(--font-size-base);
  font-weight: 500;
  white-space: nowrap;
}

.hero-card-status--active {
  background: var(--color-success-light);
  color: var(--color-success-text);
}

.hero-card-status--draft {
  background: var(--color-accent-light);
  color: var(--color-accent-text);
}

/* Note explicative (brouillon / état vide) */
.hero-card-note {
  margin-bottom: var(--spacing-md);
  font-size: var(--font-size-sm);
  color: var(--color-ink-3);
  line-height: 1.5;
}

/* Progression de la liste de courses */
.hero-card-progress {
  margin-bottom: var(--spacing-lg);
}

.hero-card-progress-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: var(--spacing-xs);
}

.hero-card-progress-label {
  font-size: var(--font-size-sm);
  font-weight: 600;
  color: var(--color-ink-2);
}

.hero-card-progress-count {
  font-size: var(--font-size-xs);
  color: var(--color-ink-3);
}

.hero-progress-bar {
  height: 8px;
  border-radius: var(--radius-badge);
  background: var(--color-bg-tertiary);
  overflow: hidden;
}

.hero-progress-bar-fill {
  height: 100%;
  background: var(--color-success);
  border-radius: inherit;
  transition: width var(--transition-slow) ease;
}

/* Vignettes recettes cliquables dans la carte Home */
.hero-card-recipes {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(128px, 1fr));
  gap: var(--spacing-sm);
  margin-bottom: var(--spacing-lg);
}

.hero-card-recipes .mc-recipe-card {
  min-width: 0;
  background: var(--color-bg-white);
}

.hero-card-recipes .mc-card-photo {
  aspect-ratio: 4 / 3;
}

.hero-card-recipes .mc-card-name {
  font-size: var(--font-size-sm);
  line-height: 1.3;
}

/* Sélecteur doublé (comme dans menus.css) : le retrait bas de la carte y est
   posé à specificité (0,2,0) — et menus.css est requis après home.css. */
.hero-card-recipes .mc-recipe-card.mc-recipe-card--readonly {
  padding-bottom: 8px;
}

/* Zone d'actions : bouton principal + bouton secondaire, côte à côte et de
   largeur égale (empilés en colonne sur mobile, cf. media query dédiée). */
.hero-card-actions {
  display: flex;
  flex-direction: row;
  gap: var(--spacing-md);
}

.hero-card-actions .btn-hero {
  flex: 1;
  padding: 0.9rem 2rem;
  font-size: var(--font-size-base);
  text-align: center;
}

.hero-card-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-md);
  margin-top: var(--spacing-md);
}

.hero-card-link {
  font-size: var(--font-size-sm);
  font-weight: 500;
  color: var(--color-ink-2);
  text-decoration: underline;
  text-underline-offset: 2px;
  transition: color var(--transition-fast);
}

.hero-card-link:is(:hover, :focus) {
  color: var(--color-ink);
}

/* Variante bouton (button_to) stylisé comme un lien discret */
.hero-card-link--btn {
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
}

/* Sur mobile, ces liens sont de vraies actions de navigation (dont « Valider le
   menu ») : on leur donne une hauteur de cible tactile de 44px. */
@media (max-width: 767px) {
  .hero-card-link {
    display: inline-flex;
    align-items: center;
    min-height: var(--min-touch-target);
  }
}

/* ======================================
   SECTION « COMMENT ÇA MARCHE » (vitrine)
   ====================================== */
.hero-steps {
  --hero-entry-delay: 0.28s;
  display: flex;
  gap: var(--spacing-xl);
  justify-content: center;
  margin-top: var(--spacing-2xl);
}

.hero-content,
.hero-greeting,
.hero-title,
.hero-subtitle,
.hero-cta,
.hero-card,
.hero-steps {
  animation: fadeInUp 0.5s ease-out var(--hero-entry-delay, 0s) both;
}

.hero-step {
  background: rgba(255, 255, 255, 0.85);
  backdrop-filter: blur(6px);
  border-radius: var(--radius-card);
  padding: var(--spacing-md);
  max-width: 220px;
  text-align: center;
}

.hero-step-number {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: var(--color-primary);
  color: var(--color-bg-white);
  font-weight: 700;
  font-size: var(--font-size-lg);
  margin-bottom: var(--spacing-sm);
}

.hero-step-title {
  font-size: var(--font-size-base);
  font-weight: 600;
  color: var(--color-ink);
  margin-bottom: var(--spacing-xs);
}

.hero-step-text {
  /* Était --font-size-s, qui n'existe pas : coquille de --font-size-sm. Un var()
     non résolu échoue en silence, la taille retombait sur l'héritage du parent. */
  font-size: var(--font-size-sm);
  color: var(--color-ink-3);
  line-height: 1.5;
}

/* Animations */
@keyframes fadeIn {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

@keyframes fadeInUp {
  from {
    opacity: 0;
    transform: translateY(30px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* Accessibilité : on neutralise les animations d'entrée pour les personnes
   ayant demandé à réduire les mouvements (le contenu reste pleinement visible). */
@media (prefers-reduced-motion: reduce) {
  .hero-content,
  .hero-logo,
  .hero-greeting,
  .hero-title,
  .hero-subtitle,
  .hero-cta,
  .hero-card,
  .hero-steps {
    animation: none;
  }
}

/* ======================================
   RESPONSIVE DESIGN - MOBILE FIRST
   ====================================== */

/* Mobile (< 768px) - Base par défaut */
@media (max-width: 767px) {
  .hero-section {
    --hero-logo-max-width: 280px;
    --hero-content-padding: 1rem;
    --hero-content-max-width: 100%;
    --hero-greeting-size: 22px;
    --hero-greeting-subtext-size: 16px;
    --hero-greeting-subtext-margin-bottom: 24px;
    --hero-title-size: var(--font-size-3xl);
    --hero-title-margin-bottom: 1rem;
    --hero-subtitle-size: 1.1rem;
    --hero-subtitle-margin-bottom: 2rem;
    --hero-button-padding: 1rem 2rem;
    --hero-button-font-size: 1.1rem;
    --hero-cta-direction: column;
    padding: 1rem 0;
    width: 100%;
  }

  .hero-content {
    width: 100%;
    box-sizing: border-box;
  }

  .hero-cta .btn-hero {
    width: 100%;
  }

  /* Carte dashboard mobile : pleine largeur, actions empilées et de largeur égale */
  .hero-card {
    max-width: 100%;
    padding: var(--spacing-md);
  }

  .hero-card-actions {
    flex-direction: column;
  }

  .hero-card-links {
    justify-content: center;
  }

  /* Steps empilées sur mobile */
  .hero-steps {
    flex-direction: column;
    align-items: center;
    gap: var(--spacing-md);
  }

  .hero-step {
    max-width: 100%;
    width: 100%;
  }

  .hero-cta {
    align-items: center;
    gap: var(--spacing-sm);
  }
}

/* Tablette (768px - 1024px) */
@media (min-width: 768px) and (max-width: 1024px) {
  .hero-section {
    --hero-logo-max-width: 350px;
    --hero-greeting-size: 22px;
    --hero-title-size: 2.8rem;
    --hero-subtitle-size: 1.3rem;
    --hero-button-padding: 1.15rem 2.5rem;
  }

  /* Steps sur tablette : 3 colonnes serrées */
  .hero-steps {
    gap: var(--spacing-md);
  }

  .hero-step {
    max-width: 200px;
  }
}

/* Desktop (> 1024px) */
@media (min-width: 1025px) {
  .hero-section {
    --hero-content-padding: 3rem;
    min-height: 100vh;
    min-height: 90dvh;
  }
}

/* Desktop intermédiaire (1025px - 1440px) */
@media (min-width: 1025px) and (max-width: 1440px) {
  .hero-section {
    --hero-logo-max-width: 440px;
  }
}

/* Très grands écrans (> 1440px) */
@media (min-width: 1441px) {
  .hero-section {
    --hero-title-size: 2.8rem;
    --hero-subtitle-size: 1.5rem;
  }
}

/* Adaptation pour petits mobiles (< 375px) */
@media (max-width: 374px) {
  .hero-section {
    --hero-logo-max-width: 240px;
    --hero-greeting-size: 1rem;
    --hero-title-size: 1.75rem;
    --hero-subtitle-size: var(--font-size-base);
    --hero-button-padding: 0.9rem 1.75rem;
    --hero-button-font-size: var(--font-size-base);
  }
}

/* Mode paysage sur mobile */
@media (max-height: 600px) and (orientation: landscape) {
  .hero-section {
    --hero-logo-max-width: 200px;
    --hero-logo-margin-bottom: 1.5rem;
    --hero-title-size: 1.75rem;
    --hero-title-margin-bottom: 0.75rem;
    --hero-subtitle-size: var(--font-size-base);
    --hero-subtitle-margin-bottom: 1.5rem;
    min-height: auto;
    padding: 2rem 1rem;
  }
}
/* ============================================
   INGREDIENTS - Styles spécifiques
   ============================================
   Liste, table, filtres pour les ingrédients
   ============================================ */

/* === Container principal === */
.ingredients-container {
  width: 100%;
  max-width: 1400px;
  margin: 0 auto;
  padding: 2rem 1rem;
}

.ingredients-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 2rem;
}

/* La taille suit l'échelle fluide de la règle de base h1 (global.css). */
.ingredients-header h1 {
  font-weight: 600;
  color: var(--color-ink);
  margin: 0;
}

/* === Filtres de recherche === */
.ingredients-filters {
  background: var(--color-bg-white);
  padding: 1.5rem;
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-md);
  margin-bottom: 2rem;
}

.search-form {
  display: flex;
  gap: 1rem;
  flex-wrap: wrap;
}

.search-group {
  display: flex;
  gap: 1rem;
  flex: 1;
  align-items: center;
  flex-wrap: wrap;
}

.search-input {
  flex: 1;
  min-width: 250px;
  padding: 0.75rem 1rem;
  border: var(--border-thin);
  border-radius: var(--radius-input);
  font-size: var(--font-size-base);
  background: var(--color-bg-white);
  color: var(--color-ink);
  transition: all var(--transition-normal);
}

.search-input:focus {
  outline: none;
  border-color: var(--color-primary);
  box-shadow: 0 0 0 2px var(--color-primary-light);
}

.filter-select {
  padding: 0.75rem 1rem;
  border: var(--border-thin);
  border-radius: var(--radius-input);
  font-size: var(--font-size-base);
  background: var(--color-bg-white);
  color: var(--color-ink);
  transition: all var(--transition-normal);
  min-width: 180px;
}

.filter-select:focus {
  outline: none;
  border-color: var(--color-primary);
}

.season-checkbox {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 1rem;
  border-radius: var(--radius-card-compact);
  background: var(--color-bg-main);
  cursor: pointer;
  user-select: none;
}

.season-checkbox input[type="checkbox"] {
  width: 18px;
  height: 18px;
  cursor: pointer;
  accent-color: var(--color-primary);
}

.season-checkbox span {
  font-size: var(--font-size-sm);
  font-weight: 500;
  color: var(--color-ink);
}

/* === Table des ingrédients === */
.table-container {
  background: var(--color-bg-white);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-md);
  overflow: hidden;
  margin-bottom: 2rem;
}

.ingredients-table {
  width: 100%;
  border-collapse: collapse;
}

.ingredients-table thead {
  background: var(--color-ink-4);
}

.ingredients-table th {
  padding: 1rem 1.5rem;
  text-align: left;
  font-weight: 600;
  color: var(--color-ink);
  font-size: var(--font-size-sm);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  border-bottom: var(--border-thin);
}

.ingredients-table td {
  padding: 1rem 1.5rem;
  border-bottom: 1px solid var(--color-border);
}

.ingredients-table tbody tr:hover {
  background: var(--color-bg-main);
}

/* Colonne Rayon : largeur fixe pour que tous les badges tombent à la même
   largeur, quel que soit le libellé. Le plus long (« Produits frais en
   libre-service ») tient sur une ligne dans cette largeur ; s'il déborde
   malgré tout — zoom texte, police système large — il passe à la ligne plutôt
   que d'étirer la colonne, d'où le white-space remis à normal. */
.category-column,
.category-cell {
  width: 18rem;
}

.category-cell .category-badge {
  display: block;
  width: 100%;
  text-align: center;
  white-space: normal;
}

/* Le frame des résultats se recharge à chaque filtre : on l'estompe le temps de
   la requête plutôt que de laisser croire que rien ne s'est passé. */
#ingredients_list[busy] {
  opacity: 0.6;
  transition: opacity var(--transition-fast);
}

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

.ingredient-name-cell strong {
  color: var(--color-ink);
  font-size: var(--font-size-base);
  font-weight: 600;
}

.ingredient-name-cell .aliases {
  color: var(--color-ink-3);
  font-style: italic;
  font-size: var(--font-size-sm);
  display: block;
  margin-top: 0.25rem;
}

.season-months {
  display: flex;
  gap: 0.25rem;
  flex-wrap: wrap;
}

/* Colonne « Recettes » : un nombre nu, donc une colonne étroite et centrée sous
   son intitulé. */
.recipes-column,
.recipes-cell {
  width: 7rem;
  text-align: center;
}

/* Un ingrédient que personne n'utilise : le zéro s'efface pour laisser l'œil
   aux décomptes qui, eux, comptent. */
.recipes-cell.is-unused {
  color: var(--color-ink-4);
}

/* En-têtes de colonne cliquables : ils gardent l'allure d'un intitulé de
   tableau, la flèche seule signalant la colonne qui trie. */
.sort-link {
  color: inherit;
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
}

.sort-link:hover {
  color: var(--color-ink-3);
}

/* Ambre : le highlight de la charte, ici pour dire « c'est cette colonne qui
   range la liste ». */
.sort-link.is-active {
  color: var(--color-accent-text);
}

.sort-arrow {
  font-size: 0.625em;
  vertical-align: middle;
}

/* Compteur des densités estimées : une ligne discrète au-dessus de la liste,
   qui n'existe que lorsqu'il reste quelque chose à vérifier. */
.to-check-notice {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.75rem;
  text-decoration: none;
  color: inherit;
}

.to-check-hint {
  font-size: var(--font-size-sm);
  color: var(--color-ink-3);
}

.to-check-notice:hover .to-check-hint {
  color: var(--color-ink-2);
}

/* Filtre posé : le compteur reste, et c'est lui qui ramène au catalogue. */
.to-check-notice.is-active .to-check-hint {
  color: var(--color-ink-2);
  text-decoration: underline;
}

/* Barre de tri des cartes : réservée aux largeurs où le thead n'existe plus. */
.sort-bar {
  display: none;
}

.actions-column {
  width: 120px;
  text-align: center;
}

.actions-cell {
  text-align: center;
  /* Boutons icône (modifier/supprimer) maintenus sur une seule ligne — cf. tags.css. */
  white-space: nowrap;
}

/* === Pagination === */
.pagination-container {
  display: flex;
  justify-content: center;
  margin-top: 2rem;
}

.pagination-container nav {
  display: flex;
  align-items: center;
}

.pagination-container nav span {
  padding: 0.5rem 0.75rem;
  border-radius: var(--radius-input);
  text-decoration: none;
  font-size: var(--font-size-sm);
  transition: all var(--transition-normal);
  min-width: 38px;
  text-align: center;
  display: inline-block;
}

.pagination-container nav span.prev,
.pagination-container nav span.next {
  margin: 0 0.5rem;
}

.pagination-container nav a {
  color: var(--color-ink-2);
  text-decoration: none;
  border-radius: var(--radius-input);
  display: inline-block;
  min-width: 38px;
  text-align: center;
  transition: all var(--transition-normal);
}

.pagination-container nav span.page a:hover {
  background: var(--color-bg-secondary);
}

.pagination-container nav span.prev a:hover,
.pagination-container nav span.next a:hover {
  background: var(--color-primary);
  color: var(--color-bg-white);
}

.pagination-container nav span.page.active {
  background: var(--color-primary);
  color: var(--color-bg-white);
  font-weight: 600;
}

.pagination-container nav span.page.disabled,
.pagination-container nav span.prev.disabled,
.pagination-container nav span.next.disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.pagination-container nav span.gap {
  color: var(--color-ink-3);
  cursor: default;
}

/* === RESPONSIVE === */

@media (max-width: 767px) {
  .ingredients-container {
    padding: 1rem 0.75rem;
    width: auto;
  }

  .ingredients-header {
    flex-direction: column;
    gap: 1rem;
    align-items: stretch;
  }

  .ingredients-header h1 {
    text-align: center;
  }

  .ingredients-header .btn-primary {
    width: 100%;
    text-align: center;
  }

  /* Filtres responsive */
  .ingredients-filters {
    padding: 1rem;
  }

  .search-group {
    flex-direction: column;
    gap: 0.75rem;
  }

  .search-input,
  .filter-select {
    width: 100%;
    min-width: 100%;
    font-size: 16px; /* Évite le zoom sur iOS */
  }

  .season-checkbox {
    width: 100%;
    justify-content: center;
  }

  .btn-secondary,
  .btn-link {
    width: 100%;
  }

  /* Le thead disparaît avec le tableau (voir plus bas) : la barre de tri prend
     le relais, seul endroit d'où l'on peut encore ranger la liste. */
  .sort-bar {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.75rem;
    margin-bottom: 0.75rem;
    font-size: var(--font-size-sm);
  }

  .sort-bar-label {
    color: var(--color-ink-3);
  }

  /* Table en mode cartes sur mobile */
  .table-container {
    overflow-x: visible;
    box-shadow: none;
    background: transparent;
  }

  .ingredients-table {
    display: block;
    border: none;
  }

  .ingredients-table thead {
    display: none;
  }

  .ingredients-table tbody {
    display: block;
  }

  .ingredients-table tr {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    background: var(--color-bg-white);
    border-radius: var(--radius-card-compact);
    padding: 1rem;
    margin-bottom: 0.75rem;
    box-shadow: var(--shadow-md);
    gap: 0.5rem;
  }

  .ingredients-table td {
    padding: 0;
    border: none;
  }

  /* Masquer tous les labels data-label sur mobile */
  .ingredients-table td::before {
    display: none !important;
  }

  /* LIGNE 1 : Nom (gauche) + Actions (droite) sur la même ligne */
  .ingredient-name-cell {
    display: contents;
  }

  .ingredient-name-cell strong {
    order: 1;
    flex: 1;
    font-size: 1.0625rem;
    font-weight: 600;
    color: var(--color-ink-2);
    line-height: 1.3;
  }

  /* LIGNE 2 : Aliases */
  .ingredient-name-cell .aliases {
    order: 3;
    width: 100%;
    font-size: var(--font-size-xs);
    color: var(--color-ink-3);
    font-style: italic;
    margin-bottom: 0.25rem;
  }

  /* LIGNE 1 : Actions (côté droit) */
  .actions-cell {
    display: flex !important;
    gap: 0.375rem;
    order: 2;
    flex-shrink: 0;
  }

  /* LIGNE 3 : Rayon */
  .ingredients-table .category-cell {
    order: 4;
    width: 100%;
    margin-bottom: 0.25rem;
  }

  /* En carte, le badge reprend sa largeur naturelle : la colonne qu'il alignait
     n'existe plus, et un aplat pleine largeur écraserait la carte. */
  .ingredients-table .category-cell .category-badge {
    display: inline-block;
    width: auto;
  }

  /* LIGNE 4 : Mois de saison */
  .ingredients-table td[data-label="Saison"] {
    order: 5;
    width: 100%;
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem;
  }

  .season-months {
    display: inline-flex;
    gap: 0.25rem;
  }

  /* LIGNE 5 : emploi dans les recettes. La carte n'a plus de colonnes : ni la
     largeur fixe ni le centrage du tableau n'ont plus lieu d'être. */
  .ingredients-table .recipes-cell {
    order: 6;
    width: auto;
    text-align: left;
    font-size: var(--font-size-xs);
  }

  /* Un nombre nu ne dirait rien hors de sa colonne : en carte, il reprend
     l'intitulé que le thead ne porte plus. */
  .ingredients-table .recipes-cell::before {
    display: inline !important;
    content: attr(data-label) " : ";
    color: var(--color-ink-3);
  }

  .text-muted {
    font-size: var(--font-size-xs);
  }

  /* Pagination mobile */
  .pagination-container {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    padding: 0.5rem 0;
  }

  .pagination-container nav {
    justify-content: flex-start;
    min-width: min-content;
  }

  .pagination-container nav span.page,
  .pagination-container nav span.prev,
  .pagination-container nav span.next {
    min-width: 40px;
    min-height: 40px;
    font-size: var(--font-size-sm);
  }
}

@media (min-width: 768px) and (max-width: 1024px) {
  .ingredients-container {
    padding: 1.5rem 1rem;
  }

  .search-group {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.75rem;
  }

  .search-input {
    grid-column: 1 / -1;
  }

  .table-container {
    overflow-x: auto;
  }

  /* Largeur plancher sous laquelle les colonnes se compriment plutôt que de
     scroller — relevée avec l'arrivée de la colonne « Recettes ». */
  .ingredients-table {
    min-width: 820px;
  }

  .ingredients-table th,
  .ingredients-table td {
    padding: 0.875rem 1rem;
    font-size: 0.9375rem;
  }
}

/* ============================================
   Nom cliquable dans l'index (→ fiche ingrédient)
   ============================================ */
.ingredient-name-link {
  color: var(--color-ink);
  text-decoration: none;
}

.ingredient-name-link:hover {
  color: var(--color-primary);
  text-decoration: underline;
}

/* ============================================
   Fiche ingrédient (show)
   ============================================ */
.show-container {
  max-width: 640px;
  margin: 0 auto;
  padding: 2rem 1rem;
}

.show-header {
  margin-bottom: 1.5rem;
}

/* La taille suit l'échelle fluide de la règle de base h1 (global.css). */
.show-header h1 {
  font-weight: 600;
  color: var(--color-ink);
  margin: 0;
}

.show-card {
  background: var(--color-bg-white);
  border: var(--border-thin);
  border-radius: var(--radius-card);
  padding: 1.5rem;
}

.detail-grid {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
}

.detail-item {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.detail-label {
  font-size: var(--font-size-xs);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-ink-3);
}

.detail-value {
  font-size: var(--font-size-base);
  color: var(--color-ink);
}

.show-actions {
  display: flex;
  gap: 0.75rem;
  margin-top: 1.5rem;
  padding-top: 1.5rem;
  border-top: var(--border-thin);
}

/* Sur mobile, les actions (Modifier / Supprimer) s'empilent en pleine largeur. */
@media (max-width: 767px) {
  .show-actions {
    flex-direction: column;
  }

  .show-actions .btn {
    width: 100%;
  }
}
/* ============================================
   RECETTES - Styles pour les pages recipes
   ============================================ */

/* === Layout général === */

.recipes-container {
  width: 100%;
  max-width: 1600px;
  margin: 0 auto;
  padding: 2rem 1.5rem;
  /* Gabarit du rail « menu à valider » en mobile (barre fixe en bas d'écran) :
     hauteur de la barre compteur (cible tactile du bouton + respiration), puis
     du bandeau de vignettes déployé (vignette 56px + padding).
     Source unique : consommée par .draft-rail et par --draft-rail-space. */
  --rail-bar-h: 3.5rem;
  --rail-strip-h: 4.5rem;
}

/* Place occupée en bas d'écran par le rail, selon qu'il est déployé ou replié
   (classe posée par mobile-accordion sur .recipes-container). Ce qui flotte
   au-dessus du contenu s'en décale. Le :has() la laisse indéfinie — donc nulle
   via la valeur de repli — quand aucun rail n'est rendu (visiteur non connecté). */
.recipes-container:has(.draft-rail) {
  --draft-rail-space: calc(var(--rail-bar-h) + var(--rail-strip-h));
}

.recipes-container:has(.draft-rail).mobile-accordion--collapsed {
  --draft-rail-space: var(--rail-bar-h);
}

/* === Layout principal === */
/* Base mobile-first : sidebar en drawer hors flux */

.recipes-body {
  display: block;
}

.recipes-main {
  min-width: 0;
  /* Espace en bas pour que ni le rail du menu ni le bouton flottant ne cachent
     la pagination */
  padding-bottom: calc(var(--draft-rail-space, 0px) + 5rem);
}

/* === Sidebar des filtres === */
/* Base mobile : drawer fixe glissant depuis la gauche */

.recipes-sidebar {
  position: fixed;
  top: 0;
  left: -100%;
  width: 320px;
  max-width: 90vw;
  height: 100vh;
  background: var(--color-bg-white);
  border-radius: 0 var(--radius-input) var(--radius-input) 0;
  padding: 1.5rem;
  overflow-y: auto;
  overflow-x: hidden;
  scrollbar-width: thin;
  scrollbar-color: var(--color-border) transparent;
  transition: left 0.3s ease;
  z-index: 200;
  box-shadow: none;
}

.recipes-sidebar.is-open {
  left: 0;
  box-shadow: 4px 0 24px rgba(0, 0, 0, 0.15);
}

.sidebar-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 1.25rem;
  padding-bottom: 0.75rem;
  border-bottom: 2px solid var(--color-border);
}

.sidebar-title {
  font-size: var(--font-size-lg);
  font-weight: 700;
  color: var(--color-ink);
  margin: 0;
}

/* Bouton ✕ : toujours visible pour fermer le drawer */
.sidebar-close-btn {
  display: flex;
  width: 36px;
  height: 36px;
  min-width: 36px;
  border: none;
  background: none;
  font-size: 1.25rem;
  cursor: pointer;
  color: var(--color-ink);
  border-radius: 50%;
  align-items: center;
  justify-content: center;
  transition: background 0.2s;
}

.sidebar-close-btn:hover {
  background: var(--color-border);
}

.sidebar-footer {
  margin-top: 1rem;
  padding-top: 0.75rem;
  border-top: 1px dashed var(--color-border);
}

/* === Bouton flottant (visible sur tous les écrans) === */

.filters-mobile-btn {
  display: flex;
  position: fixed;
  /* Au-dessus du rail du menu (s'il est rendu) et de l'indicateur d'accueil iOS */
  bottom: calc(var(--draft-rail-space, 0px) + 1.5rem + env(safe-area-inset-bottom));
  left: 50%;
  transform: translateX(-50%);
  z-index: 150;
  align-items: center;
  gap: 0.5rem;
  padding: 0.75rem 1.5rem;
  background: var(--color-primary);
  color: var(--color-bg-white);
  border: none;
  border-radius: var(--radius-button);
  font-size: var(--font-size-base);
  font-weight: 600;
  cursor: pointer;
  box-shadow: var(--shadow-lg);
  transition: background 0.2s, transform 0.2s;
  white-space: nowrap;
}

.filters-mobile-btn:hover {
  background: var(--color-primary-hover);
  transform: translateX(-50%) translateY(-2px);
}

.filters-mobile-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 20px;
  padding: 0 4px;
  background: var(--color-bg-white);
  color: var(--color-primary);
  border-radius: 10px;
  font-size: 0.7rem;
  font-weight: 700;
}

/* === Backdrop overlay (mobile) === */

.filters-backdrop {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
  z-index: 180;
  backdrop-filter: blur(2px);
}

.filters-backdrop.is-open {
  display: block;
}

.recipes-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 1rem;
}

/* La taille suit l'échelle fluide de la règle de base h1 (global.css).
   inline-flex : centre verticalement l'icône avec le texte, quelle que soit
   leur taille — l'espacement vient de la marge de .icon-title. */
.recipes-header h1 {
  display: inline-flex;
  align-items: center;
  color: var(--color-ink); /* Brun caramel */
  margin: 0;
}

/* Actions empilées : « Créer un menu » se place sous « Ajouter une recette ».
   align-items: stretch les met à la largeur du plus large, pour un bloc aligné —
   chacun peut être absent (rôle, présence d'un menu à valider). */
.recipes-header-actions {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0.5rem;
  flex-shrink: 0;
}

/* === Filtres & Recherche === */

/* ── Barre de recherche ── */

.filters-search-row {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-bottom: 1rem;
}

/* Wrapper pour le champ + icône intégrée */
.search-field-wrapper {
  position: relative;
  display: flex;
  align-items: center;
  width: 100%;
}

.filters-search-row .search-input {
  width: 100%;
  padding: 0.7rem 2.5rem 0.7rem 1rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-input);
  font-size: var(--font-size-base);
  background: var(--color-bg-white);
}

.filters-search-row .search-input:focus {
  outline: none;
  border-color: var(--color-primary);
}

/* Icône loupe intégrée à droite du champ */
.search-inline-btn {
  position: absolute;
  right: 0.4rem;
  top: 50%;
  transform: translateY(-50%);
  border: none;
  background: none;
  cursor: pointer;
  font-size: 1rem;
  line-height: 1;
  padding: 0.3rem;
  /* L'icône est le seul contenu du bouton submit : un contrôle fonctionnel,
     soumis au seuil UI de 3:1, pas un ornement. #999 n'y répondait pas
     (2,85:1 sur --color-bg-white, fond de la sidebar) ; ink-3 donne 6,70:1. */
  color: var(--color-ink-3);
  transition: color 0.2s;
}

.search-inline-btn:hover {
  color: var(--color-ink);
}

/* Rétro-compat : ancien bouton submit pleine largeur (mobile) */
.search-submit-btn {
  width: 100%;
  justify-content: center;
  padding: 0.7rem;
}

/* ── Filtres empilés verticalement dans la sidebar ── */

.filters-quick-column {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  margin-bottom: 1rem;
}

.filter-group {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  min-width: 120px;
}

.filter-group label,
.filter-label {
  font-size: var(--font-size-sm);
  color: var(--color-ink);
  font-weight: 500;
}

.filter-group select,
.filter-select {
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-input);
  background: var(--color-bg-white);
  font-size: var(--font-size-sm);
  cursor: pointer;
  transition: border-color var(--transition-normal);
}

.filter-group select:focus,
.filter-select:focus {
  outline: none;
  border-color: var(--color-primary);
}

/* Select avec valeur active */
.filter-select--active {
  border-color: var(--color-primary);
  color: var(--color-ink);
  font-weight: 600;
  background-color: var(--color-bg-secondary);
}

/* Selects de filtres : 16px sur mobile pour éviter le zoom iOS au focus.
   Ils n'ont pas la classe .form-select, donc le correctif global ne les couvre pas. */
@media (max-width: 767px) {
  .filter-group select,
  .filter-select {
    font-size: 16px;
  }
}

/* Filtre checkbox (De saison) */
.filter-checkbox {
  flex-direction: row;
  align-items: center;
  gap: 0.5rem;
  min-width: auto;
  padding: 0.5rem 0.75rem;
  background: var(--color-bg-white);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-input);
  cursor: pointer;
  transition: border-color var(--transition-normal);
}

.filter-checkbox.filter-checkbox--active {
  border-color: var(--color-primary);
  background-color: var(--color-bg-secondary);
}

.filter-checkbox input[type="checkbox"] {
  width: 1rem;
  height: 1rem;
  cursor: pointer;
}

.filter-label-inline {
  font-size: var(--font-size-sm);
  color: var(--color-ink);
  font-weight: 500;
  cursor: pointer;
  margin: 0;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}

/* Badge compteur (nb filtres ou nb tags) */
.filter-count-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  background: var(--color-primary);
  color: var(--color-bg-white);
  border-radius: 50%;
  font-size: 0.7rem;
  font-weight: 700;
  line-height: 1;
}

/* ── Barre filtres actifs (badges supprimables) ── */

.active-filters-bar {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  flex-wrap: wrap;
  margin-top: 0.85rem;
  padding-top: 0.85rem;
  border-top: 1px dashed var(--color-border);
}

.active-filters-label {
  font-size: var(--font-size-sm);
  /* #888 échouait AA (3,54:1 sur --color-bg-white, fond de la sidebar) ;
     ink-3 donne 6,70:1. */
  color: var(--color-ink-3);
  white-space: nowrap;
  flex-shrink: 0;
  margin-right: 0.1rem;
}

.active-filter-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.25rem 0.7rem;
  background: var(--color-primary);
  color: var(--color-bg-white);
  border: none;
  border-radius: var(--radius-button);
  font-size: var(--font-size-sm);
  font-family: inherit;
  cursor: pointer;
  transition: background var(--transition-normal);
}

.active-filter-badge:hover {
  background: var(--color-primary-hover);
}

.badge-remove {
  font-weight: 700;
  font-size: 0.9rem;
  line-height: 1;
  opacity: 0.75;
}

/* ── Tags : section dans la sidebar ── */

.filters-section-title {
  display: block;
  font-size: var(--font-size-sm);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--color-ink);
  margin: 0.5rem 0 0.75rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--color-border);
}

.filters-tags-section {
  margin-top: 0.25rem;
}

/* ── Accordéon de tags (un par type) : <details>/<summary> natif ── */
.filters-tags-accordion {
  border-bottom: 1px solid var(--color-border-light);
}

.filters-tags-accordion:last-child {
  border-bottom: none;
}

.filters-tags-summary {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.6rem 0.15rem;
  cursor: pointer;
  list-style: none;
  font-size: var(--font-size-sm);
  font-weight: 600;
  color: var(--color-ink);
  user-select: none;
}

/* Masque le marqueur natif (triangle) sur tous les navigateurs */
.filters-tags-summary::-webkit-details-marker {
  display: none;
}

.filters-tags-summary:hover {
  color: var(--color-primary);
}

.filters-tags-summary-label {
  flex: 1;
}

/* Compteur de tags sélectionnés dans ce type */
.filters-tags-summary-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  background: var(--color-primary);
  color: var(--color-bg-white);
  border-radius: 9px;
  font-size: 0.7rem;
  font-weight: 700;
  line-height: 1;
}

/* Chevron : pivote quand l'accordéon est ouvert */
.filters-tags-chevron {
  display: inline-flex;
  color: var(--color-ink-3);
  transition: transform var(--transition-normal);
}

.filters-tags-accordion[open] .filters-tags-chevron {
  transform: rotate(180deg);
}

.filters-tags-list {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  padding-bottom: 0.5rem;
}

/* Case à cocher tag */
.filter-tag-check {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.3rem 0.25rem;
  border-radius: 4px;
  transition: background 0.15s;
}

.filter-tag-check:hover {
  background: var(--color-bg-secondary);
}

/* Tiroir de filtres sur mobile : cases plus grandes et mieux espacées pour
   éviter les erreurs de tap (l'espacement de 0.1rem était bien trop serré). */
@media (max-width: 767px) {
  .filters-tags-list {
    gap: 0.35rem;
  }

  .filter-tag-check {
    padding: 0.6rem 0.4rem;
  }

  .filter-tag-input {
    width: 18px;
    height: 18px;
  }
}

.filter-tag-input {
  width: 15px;
  height: 15px;
  accent-color: var(--color-primary);
  cursor: pointer;
  flex-shrink: 0;
}

.filter-tag-check-label {
  font-size: var(--font-size-sm);
  color: var(--color-ink);
  cursor: pointer;
  line-height: 1.4;
}

.filter-tag-check-label.active,
.filter-tag-input:checked + .filter-tag-check-label {
  color: var(--color-ink);
  font-weight: 600;
}

/* Bouton "Réinitialiser" dans le header sidebar — visible et explicite
   (icône ✕ + libellé), ne s'affiche que si au moins un filtre est actif. */
.reset-filters-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.35rem 0.7rem;
  font-size: var(--font-size-sm);
  font-weight: 600;
  color: var(--color-primary);
  background: var(--color-bg-secondary);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-button);
  text-decoration: none;
  white-space: nowrap;
  transition: all var(--transition-normal);
  line-height: 1.2;
}

.reset-filters-btn svg {
  flex-shrink: 0;
}

.reset-filters-btn:hover {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: var(--color-bg-white);
}

/* Aucun filtre actif : bouton discret (rien à effacer) mais toujours présent */
.reset-filters-btn--idle {
  color: var(--color-ink-3);
  background: transparent;
  border-color: var(--color-border);
}

/* Rétro-compat : barre de recherche utilisée sur d'autres pages */
.recipes-search-bar {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
  align-items: center;
}

.recipes-search-bar .search-input,
.recipes-search-bar input[type="search"] {
  flex: 1;
  min-width: 200px;
  padding: 0.75rem 1rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-input);
  font-size: var(--font-size-base);
}

.recipes-search-bar .search-input:focus,
.recipes-search-bar input[type="search"]:focus {
  outline: none;
  border-color: var(--color-primary);
}

.search-buttons {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
}

/* === Loader de recherche === */

.recipes-loader {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
  padding: 1.5rem;
  margin-bottom: 1rem;
  background: var(--color-bg-secondary);
  border-radius: var(--radius-input);
  color: var(--color-ink);
  font-size: var(--font-size-base);
}

.recipes-loader.hidden {
  display: none;
}

.loader-spinner {
  width: 24px;
  height: 24px;
  border: 3px solid var(--color-border);
  border-top-color: var(--color-primary);
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}

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

/* État de chargement pour les résultats */
.recipes-results {
  min-height: 500px;
}

.recipes-results.loading {
  opacity: 0.5;
  pointer-events: none;
  transition: opacity 0.2s ease;
}

/* === Compteur de résultats === */

.recipes-count {
  font-size: var(--font-size-sm);
  /* #666 tenait tout juste AA (4,81:1 sur --color-bg-main) ; ink-3 donne 5,61:1
     sur la même surface. */
  color: var(--color-ink-3);
  margin-bottom: 1.25rem;
}

.recipes-count strong {
  color: var(--color-ink);
  font-size: var(--font-size-lg);
  font-weight: 700;
  margin-right: 0.2rem;
}

/* === Grille de cartes recettes === */

.recipes-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 1.5rem;
  margin-bottom: 2rem;
}

/* === Carte recette (index) === */

.recipe-card {
  background: var(--color-bg-white);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-input);
  overflow: hidden;
  transition: transform var(--transition-normal), box-shadow var(--transition-normal);
  cursor: pointer;
  /* Hauteur pleine + colonne flex : toutes les cartes d'une même rangée ont la
     même hauteur (grille étirée), qu'il y ait des tags ou non. */
  display: flex;
  flex-direction: column;
  height: 100%;
}

.recipe-card:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-lg);
}

.recipe-card-image {
  width: 100%;
  height: 200px;
  object-fit: cover;
  background: var(--color-bg-main); /* Gris chaud clair si pas d'image */
}

.recipe-card-body {
  padding: 1rem;
  /* Occupe l'espace restant pour pousser le pied de carte tout en bas. */
  flex: 1;
  display: flex;
  flex-direction: column;
}

.recipe-card-title {
  font-size: var(--font-size-lg);
  font-weight: 600;
  color: var(--color-ink);
  margin: 0 0 0.5rem 0;
  text-decoration: none;
}

.recipe-card-title:hover {
  color: var(--color-ink-3);
}

.recipe-card-meta {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin-bottom: 0.75rem;
  font-size: var(--font-size-sm);
  color: var(--color-ink-3);
}

/* Temps total : méta principale, mise en avant (icône + libellé) */
.recipe-card-time {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-weight: 600;
  color: var(--color-ink);
}

.recipe-card-time svg {
  color: var(--color-ink-3);
  flex-shrink: 0;
}

/* Badge « De saison » sur la carte : pastille verte + feuille alignée.
   Même padding vertical que les badges de régime (héritent de .badge) pour
   garder une hauteur identique sur la ligne de méta. */
.recipe-card-meta .badge-season {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.375rem 0.875rem;
  background: var(--color-success-light);
  color: var(--color-success-text);
}

.recipe-card-meta .badge-season svg {
  flex-shrink: 0;
}

.recipe-card-tags {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin-bottom: 0.75rem;
}

.recipe-card-footer {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-top: 0.75rem;
  border-top: 1px solid var(--color-border);
  /* Ancré en bas de la carte, aligné sur les autres cartes de la rangée. */
  margin-top: auto;
}

.recipe-rating {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  font-size: var(--font-size-sm);
}

.recipe-rating-stars {
  color: var(--color-accent);
}

/* Note moyenne en clair, à côté des étoiles */
.recipe-rating-value {
  font-weight: 700;
  color: var(--color-ink);
}

.recipe-rating-count {
  color: var(--color-ink-3);
}

/* Recette sans avis : mention discrète plutôt que « 0 étoile » */
.recipe-rating-empty {
  font-size: var(--font-size-sm);
  color: var(--color-ink-4);
  font-style: italic;
}

/* === Actions sociales (favoris, notes) === */

.favorite-btn {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.75rem 1.5rem;
  border: 1px solid var(--color-border-strong);
  background: var(--color-bg-white);
  color: var(--color-ink-3);
  border-radius: var(--radius-input);
  cursor: pointer;
  font-weight: 500;
  transition: all var(--transition-normal);
}

.favorite-btn:hover {
  background: var(--color-primary);
  color: var(--color-bg-white);
  border-color: var(--color-primary);
}

.favorite-btn.favorited {
  background: var(--color-primary);
  border-color: var(--color-ink-3);
  color: var(--color-bg-white);
  animation: heart-pop 0.3s ease-out;
}

.favorite-btn.favorited:hover {
  background: var(--color-primary-hover);
  border-color: var(--color-primary-hover);
}

@keyframes heart-pop {
  0%   { transform: scale(1); }
  50%  { transform: scale(1.12); }
  100% { transform: scale(1); }
}

/* Les cartes d'ingrédients (.preparation-*) sont définies dans la section
   « Ingrédients — nested fields » du formulaire recette, plus bas. */

/* === Responsive === */

/* ── Desktop/tablette : sidebar fixe pleine hauteur, toujours visible ── */
@media (min-width: 768px) {
  /* Container pleine largeur, pas de padding horizontal — sidebar flush left */
  .recipes-container {
    padding: 0;
    max-width: none;
  }

  /* 3ᵉ colonne : rail « menu à valider » (.draft-rail, plus bas dans ce fichier).
     Piste auto → largeur nulle quand aucun rail n'est rendu (visiteur non connecté). */
  .recipes-body {
    display: grid;
    grid-template-columns: 300px 1fr auto;
    gap: 0;
    align-items: start;
  }

  .recipes-main {
    padding: 1.5rem 2rem 2rem;
    min-width: 0;
  }

  /* Sidebar collée à gauche, pleine hauteur, fond quasi-blanc, fixe au scroll.
     Ancrée sous le header sticky pour ne pas passer dessous.
     Densité aérée : tailles ≥ --font-size-sm pour rester lisibles sur desktop. */
  .recipes-sidebar {
    position: sticky;
    top: var(--header-height);
    left: auto;
    width: auto;
    max-width: none;
    height: calc(100vh - var(--header-height));
    max-height: none;
    background: var(--color-bg-white);
    border-radius: 0;
    border-right: 1px solid var(--color-border-light);
    padding: 1.25rem 1.1rem;
    font-size: var(--font-size-sm);
    overflow-x: hidden;
    transition: none;
    box-shadow: none !important;
    z-index: auto;
  }

  /* ── Éléments de la sidebar (desktop) ── */

  .sidebar-header {
    margin-bottom: 1rem;
    padding-bottom: 0.6rem;
  }

  .sidebar-title {
    font-size: var(--font-size-lg);
  }

  .filters-search-row {
    gap: 0.5rem;
    margin-bottom: 1rem;
  }

  .search-field-wrapper .search-input {
    padding: 0.55rem 2.25rem 0.55rem 0.8rem;
    font-size: var(--font-size-sm);
  }

  .search-inline-btn {
    font-size: 1rem;
    padding: 0.3rem;
  }

  .filters-quick-column {
    gap: 0.6rem;
    margin-bottom: 1rem;
  }

  .filter-group {
    gap: 0.3rem;
  }

  .filter-group label,
  .filter-label {
    font-size: var(--font-size-sm);
  }

  .filter-group select,
  .filter-select {
    padding: 0.45rem 0.65rem;
    font-size: var(--font-size-sm);
  }

  .filter-checkbox {
    padding: 0.45rem 0.65rem;
  }

  .filter-label-inline {
    font-size: var(--font-size-sm);
  }

  .filters-section-title {
    font-size: var(--font-size-xs);
    margin: 0.75rem 0 0.5rem;
    padding-top: 0.75rem;
  }

  .filters-tags-summary {
    font-size: var(--font-size-sm);
    padding: 0.55rem 0.15rem;
  }

  .filter-tag-check {
    padding: 0.3rem 0.15rem;
  }

  .filter-tag-check-label {
    font-size: var(--font-size-sm);
  }

  .filter-tag-input {
    width: 16px;
    height: 16px;
  }

  .active-filters-bar {
    gap: 0.4rem;
    margin-top: 0.85rem;
    padding-top: 0.85rem;
  }

  .active-filter-badge {
    padding: 0.25rem 0.65rem;
    font-size: var(--font-size-xs);
  }

  .sidebar-footer {
    margin-top: 0.85rem;
    padding-top: 0.6rem;
  }

  .reset-filters-btn {
    font-size: var(--font-size-xs);
    padding: 0.35rem 0.7rem;
  }

  /* La sidebar est toujours visible, pas besoin du bouton flottant ni du backdrop */
  .filters-mobile-btn {
    display: none;
  }

  .filters-backdrop {
    display: none !important;
  }

  .sidebar-close-btn {
    display: none;
  }

  /* Sur desktop, ne pas flouter les résultats pendant le chargement des filtres */
  .recipes-results.loading {
    opacity: 1;
    pointer-events: auto;
  }
}

/* ── Mobile : drawer + bouton flottant (styles de base, redéfinis ici si besoin) ── */
@media (max-width: 767px) {
  .recipes-sidebar {
    width: 85vw;
  }

  .recipes-grid {
    grid-template-columns: 1fr;
  }

  .recipes-header {
    flex-direction: column;
    align-items: flex-start;
  }

  /* Barre de recherche (rétro-compat) */
  .recipes-search-bar {
    flex-direction: column;
  }

  .recipes-search-bar .search-input,
  .recipes-search-bar input[type="search"] {
    width: 100%;
    min-width: auto;
  }

  .search-buttons {
    width: 100%;
    flex-direction: column;
  }

  .recipes-search-bar .btn,
  .search-buttons .btn {
    width: 100%;
    justify-content: center;
  }

  .recipe-meta-row {
    flex-direction: column;
  }

  .recipe-actions {
    flex-direction: column;
  }
}

/* ── Très petit écran : bouton flottant un peu plus compact ── */
@media (max-width: 360px) {
  .filters-mobile-btn {
    bottom: 1rem;
    padding: 0.65rem 1.25rem;
    font-size: var(--font-size-sm);
  }
}

/* === Formulaires recettes (admin) === */

/* .form-row : définition canonique (grid) dans components.css. Le formulaire
   recette (.rf-form) opte pour une disposition flex-wrap — voir la section
   « Overrides de formulaire dans le contexte rf » plus bas. */

.form-textarea {
  width: 100%;
  padding: 0.75rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-input);
  font-family: inherit;
  font-size: var(--font-size-base);
  line-height: 1.6;
  resize: vertical;
}

.form-textarea:focus {
  outline: none;
  border-color: var(--color-primary);
}

/* Le champ natif est masqué visuellement par .sr-only (et non display: none)
   pour rester atteignable au clavier : c'est .rf-dropzone qui porte son rendu
   et son état de focus. */

/* Espacement porté par le gap du conteneur (.rf-photo-upload) */
.photo-upload-notice {
  margin: 0;
  font-size: var(--font-size-md);
  color: var(--color-accent-deep);
  font-weight: 500;
}

.photo-preview {
  display: block;
  border: var(--border-thin);
  border-radius: var(--radius-card);
}

/* Aperçu du fichier qui vient d'être choisi (dépôt, collage ou clic) : à la
   différence de la vignette Cloudinary déjà recadrée, l'image est brute — on
   borne sa largeur et on laisse la hauteur suivre pour ne pas la déformer. */
.photo-preview--chosen {
  max-width: 200px;
  height: auto;
}

/* Sans cette règle, le display: block ci-dessus l'emporterait sur l'attribut
   hidden et l'aperçu vide s'afficherait avant tout choix. */
.photo-preview[hidden] {
  display: none;
}

/* Les capsules de tags (.tags-grid / .tag-checkbox) ne servent que dans le
   formulaire de recette : leur définition canonique est dans la section
   « Tags — capsules » plus bas, avec le reste des styles .rf-form. */

/* .icon-btn / .icon-btn-edit / .icon-btn-delete : voir components.css
   (version canonique avec cibles tactiles et hover accessibles). */

/* .btn-danger : voir components.css (définition canonique partagée). */

.text-danger {
  color: var(--color-danger);
}

/* === Empty state === */

.empty-state {
  text-align: center;
  padding: 3rem;
  background: var(--color-bg-main);
  border-radius: var(--radius-input);
}

.empty-state-icon {
  margin-bottom: 1rem;
  color: var(--color-ink-4);
}

.empty-state h2 {
  color: var(--color-ink);
  margin-bottom: 0.5rem;
}

.empty-state p {
  /* 4,81:1 → 5,61:1 sur --color-bg-main, fond porté par .empty-state. */
  color: var(--color-ink-3);
  margin-bottom: 1.5rem;
}

/* === Wrapper carte avec bouton favori en overlay === */

.recipe-card-wrapper {
  position: relative;
}

.recipe-card-wrapper .recipe-favorite-container {
  position: absolute;
  top: 0.75rem;
  right: 0.75rem;
  z-index: 1;
}

/* Bouton favori compact (icône ronde) — index uniquement */
.favorite-btn.favorite-btn-compact {
  width: 38px;
  height: 38px;
  padding: 0;
  border-radius: 50%;
  font-size: 1.1rem;
  justify-content: center;
  background: rgba(255, 255, 255, 0.92);
  backdrop-filter: blur(4px);
  gap: 0;
}

/* État favorié en compact : fond rouge explicite (spécificité plus haute) */
.favorite-btn.favorite-btn-compact.favorited {
  background: darkred;
  color: var(--color-bg-white);
}

/* Hover compact non-favorié : fond blanc opaque + cœur rouge pour indiquer l'action */
.favorite-btn.favorite-btn-compact:not(.favorited):hover {
  background: rgba(255, 255, 255, 1);
  border-color: var(--color-primary);
  color: var(--color-primary);
}

/* === Recipe card link === */

.recipe-card-link {
  text-decoration: none;
  color: inherit;
  display: block;
  /* Remplit la hauteur de la cellule de grille pour l'égalisation des cartes. */
  height: 100%;
}

.recipe-card-link:hover {
  text-decoration: none;
}

/* === Global rating on show page === */

.recipe-global-rating {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.75rem 1.5rem;
  background: var(--color-bg-main);
  border-radius: var(--radius-input);
}

.rating-label {
  color: var(--color-ink);
}

.rating-stars {
  color: var(--color-secondary);
  font-size: var(--font-size-xl);
}

.rating-value {
  /* 4,81:1 → 5,61:1 sur --color-bg-main, fond du bloc de notation. */
  color: var(--color-ink-3);
  font-size: var(--font-size-sm);
}

/* === Favorite indicator on cards === */

.recipe-favorite-indicator {
  color: var(--color-secondary);
  font-size: var(--font-size-xl);
}

/* === Recipe tags === */

.recipe-tags {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin-bottom: 1rem;
}

/* === Recipe info list === */

.recipe-info-list {
  list-style: none;
  padding: 0;
  margin: 0;
}

.recipe-info-list li {
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--color-bg-main);
}

.recipe-info-list li:last-child {
  border-bottom: none;
}

/* === SR only (screen reader) === */

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ============================================
   FICHE RECETTE (SHOW) — Slate Craft Premium
   ============================================
   Préfixe : rs- (recipe show)
   5 zones empilées verticalement.
   ============================================ */

/* === Zone 1 — Lien retour === */
/* NOTE: .rs-back-bar et .rs-back-link supprimés — utilise .context-bar */

/* === Zone 2 — Hero fond sombre === */

/* Couleurs dérivées pour le contexte sombre du hero */
.rs-hero {
  --rs-border-dark: var(--color-primary-hover);
  --rs-bg-dark: #292524;
  background: var(--color-primary);
}

/* Largeur de la colonne de droite — partagée par la photo du hero et par la
   sidebar ingrédients. Ce sont deux grilles distinctes empilées : seule une
   valeur commune garantit que leurs bords restent alignés, à toutes les
   largeurs. Ne redéclarer que la variable, jamais la largeur en dur. */
.rs-hero-inner,
.rs-body {
  --rs-aside-width: 340px;
  grid-template-columns: 1fr var(--rs-aside-width);
}

.rs-hero-inner {
  display: grid;
  max-width: 1400px;
  margin: 0 auto;
}

/* -- Colonne gauche -- */

.rs-hero-content {
  padding: 32px 28px;
  display: flex;
  flex-direction: column;
  gap: 18px;
}

.rs-kicker {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: var(--font-size-xs);
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-accent);
}

.rs-kicker-line {
  display: inline-block;
  width: 20px;
  height: 1px;
  background: var(--color-accent);
  flex-shrink: 0;
}

.rs-title {
  font-family: var(--font-serif);
  font-size: 28px;
  font-weight: 400;
  color: var(--color-bg-white);
  line-height: 1.15;
  margin: 0;
}

/* -- Bloc méta (cases) -- */

.rs-meta-box {
  display: inline-flex;
  border: var(--border-width) solid var(--rs-border-dark);
  border-radius: var(--radius-button);
  overflow: hidden;
  align-self: flex-start;
}

.rs-meta-cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 10px 18px;
  gap: 2px;
}

.rs-meta-cell + .rs-meta-cell {
  border-left: var(--border-width) solid var(--rs-border-dark);
}

.rs-meta-value {
  font-size: 15px;
  font-weight: 500;
  color: var(--color-bg-white);
}

.rs-meta-label {
  font-size: var(--font-size-xs);
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  /* Sur le hero sombre (--color-primary) : ink-4 (gris clair) passe AA, alors
     qu'ink-3 (gris foncé) était quasi illisible (audit Lighthouse R1.3). */
  color: var(--color-ink-4);
}

/* -- Badges hero (fond sombre) -- */

.rs-badges {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}

.rs-badge {
  display: inline-block;
  padding: 3px 10px;
  font-size: var(--font-size-xs);
  font-weight: 500;
  border-radius: var(--radius-button);
  background: var(--rs-bg-dark);
  /* Était #D6D3D1, un gris FROID hérité de l'ancienne palette, sur une surface
     sombre. --color-ink-on-dark porte exactement ce rôle et suit la teinte 70°
     des neutres : 9,73:1 sur --rs-bg-dark, contre 10,18:1 avant — les deux très
     au-dessus du seuil, on ne perd donc rien en lisibilité et on gagne la
     cohérence de teinte avec le reste des surfaces sombres. */
  color: var(--color-ink-on-dark);
  border: var(--border-width) solid var(--rs-border-dark);
  white-space: nowrap;
}

/* -- Boutons d'action hero -- */

.rs-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: 4px;
}

/* CTA accent de la recette (« Démarrer un menu » / « Ajouter au menu à valider »). Couleurs héritées de
   .btn-accent (components.css) ; ici uniquement le gabarit compact, aligné sur
   les boutons frères .rs-btn-fav / .rs-btn-icon. À utiliser avec .btn-accent. */
.rs-btn-accent {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 9px 18px;
  font-size: var(--font-size-sm);
  font-weight: 500;
  border-radius: var(--radius-button);
  text-decoration: none;
  cursor: pointer;
  transition: background var(--transition-fast);
}

/* État « recette déjà dans le menu » (CTA hero + CTA mobile persistant) :
   vert confirmé pour signaler la présence, bascule en rouge au survol pour
   annoncer le retrait. Rendu par button_to → <button> (pas de conflit a:hover). */
.rs-btn-accent.is-in-draft,
.rs-mobile-cta-btn.is-in-draft {
  background: var(--color-success-light);
  color: var(--color-success-text);
  border: var(--border-width) solid transparent;
  transition: background var(--transition-fast), color var(--transition-fast);
}

.rs-btn-accent.is-in-draft:hover,
.rs-mobile-cta-btn.is-in-draft:hover {
  background: var(--color-danger-light);
  color: var(--color-danger-text);
}

/* Bouton favori variante hero */
.rs-btn-fav {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 9px 14px;
  font-size: var(--font-size-sm);
  font-weight: 500;
  background: transparent;
  color: var(--color-ink-4);
  border: var(--border-width) solid var(--rs-border-dark);
  border-radius: var(--radius-button);
  cursor: pointer;
  transition: color var(--transition-fast), border-color var(--transition-fast);
}

.rs-btn-fav:hover {
  color: var(--color-bg-white);
  /* 3,26:1 sur --rs-bg-dark : le token de bordure survolée sur fond sombre,
     là où #78716C était une copie en dur de l'ancien ink-3 rejeté par l'audit R1.3. */
  border-color: var(--color-surface-dark-border-hover);
}

.rs-btn-fav.favorited {
  color: var(--color-accent);
  border-color: var(--color-accent);
}

.rs-btn-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  background: transparent;
  color: var(--color-ink-4);
  border: var(--border-width) solid var(--rs-border-dark);
  border-radius: var(--radius-button);
  cursor: pointer;
  transition: color var(--transition-fast), border-color var(--transition-fast);
}

.rs-btn-icon:hover {
  color: var(--color-bg-white);
  /* 3,26:1 sur --rs-bg-dark, même bordure survolée que .rs-btn-fav. */
  border-color: var(--color-surface-dark-border-hover);
}

/* -- Colonne droite — Photo -- */

.rs-hero-photo {
  position: relative;
  min-height: 280px;
  background: var(--rs-bg-dark);
  overflow: hidden;
}

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

.rs-photo-overlay {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  padding: 14px 16px;
  background: linear-gradient(transparent, rgba(0, 0, 0, 0.65));
  display: flex;
  align-items: center;
  gap: 8px;
}

.rs-photo-stars {
  color: var(--color-accent);
  font-size: var(--font-size-sm);
}

.rs-photo-score {
  /* Était #D6D3D1, un gris FROID hérité de l'ancienne palette, sur une surface
     sombre. --color-ink-on-dark porte exactement ce rôle et suit la teinte 70°
     des neutres : 9,73:1 sur --rs-bg-dark, contre 10,18:1 avant — les deux très
     au-dessus du seuil, on ne perd donc rien en lisibilité et on gagne la
     cohérence de teinte avec le reste des surfaces sombres. */
  color: var(--color-ink-on-dark);
  font-size: var(--font-size-xs);
}

/* === Zone 3 — Corps principal en deux colonnes === */

.rs-body {
  display: grid;
  width: 100%;
  max-width: 1400px;
  margin: 0 auto;
  min-height: 400px;
}

.rs-main {
  padding: 28px;
  border-right: var(--border-thin);
}

/* -- Titre de section générique -- */

.rs-section-title {
  font-size: var(--font-size-xs);
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--color-ink-3);
  padding-bottom: 8px;
  border-bottom: var(--border-thin);
  margin-bottom: 16px;
}

/* -- Étapes de préparation -- */

.rs-step {
  display: grid;
  grid-template-columns: 32px 1fr;
  gap: 14px;
  padding: 16px 0;
  border-bottom: var(--border-thin);
}

.rs-step--last {
  border-bottom: none;
}

.rs-step-number {
  width: 28px;
  height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--color-primary);
  color: var(--color-bg-white);
  font-size: var(--font-size-sm);
  font-weight: 500;
  border-radius: var(--radius-button);
  margin-top: 2px;
  flex-shrink: 0;
}

.rs-step-text {
  font-size: var(--font-size-base);
  color: var(--color-ink-3);
  line-height: 1.6;
}

/* === Zone 4 — Section Avis === */

.rs-section--reviews {
  margin-top: 36px;
}

/* -- Résumé de notation -- */

.rs-rating-summary {
  display: flex;
  background: var(--color-bg-secondary);
  border-radius: var(--radius-card);
  padding: 16px;
  gap: 0;
  margin-bottom: 20px;
}

.rs-rating-left {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 0 20px;
  border-right: var(--border-thin);
  min-width: 90px;
}

.rs-rating-big {
  font-size: 36px;
  font-weight: 500;
  color: var(--color-ink);
  line-height: 1;
}

.rs-rating-stars-row {
  font-size: var(--font-size-sm);
  color: var(--color-accent);
  margin: 4px 0;
}

.rs-rating-count {
  font-size: var(--font-size-xs);
  color: var(--color-ink-3);
}

.rs-rating-right {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 0 16px;
  justify-content: center;
}

.rs-histo-row {
  display: flex;
  align-items: center;
  gap: 6px;
}

.rs-histo-label {
  font-size: var(--font-size-xs);
  color: var(--color-ink-3);
  width: 12px;
  text-align: right;
  flex-shrink: 0;
}

.rs-histo-bar {
  flex: 1;
  height: 4px;
  background: var(--color-border);
  border-radius: 2px;
  overflow: hidden;
}

.rs-histo-fill {
  height: 100%;
  background: var(--color-accent);
  border-radius: 2px;
  transition: width var(--transition-normal);
}

.rs-histo-count {
  font-size: var(--font-size-xs);
  color: var(--color-ink-3);
  width: 16px;
  flex-shrink: 0;
}

/* -- Formulaire d'avis -- */

.rs-review-form {
  border: var(--border-thin);
  border-radius: var(--radius-button);
  padding: 16px;
  margin-bottom: 16px;
}

.rs-review-form-title {
  font-size: var(--font-size-sm);
  font-weight: 500;
  color: var(--color-ink);
  margin: 0 0 10px;
}

.rs-rating-input {
  display: flex;
  gap: 4px;
  margin-bottom: 12px;
}

.rs-star-radio {
  display: none;
}

.rs-star-label {
  cursor: pointer;
}

.rs-star {
  font-size: 22px;
  color: var(--color-ink-4);
  transition: color var(--transition-fast);
}

.rs-star.star-filled {
  color: var(--color-accent);
}

.rs-star-label:hover .rs-star {
  color: var(--color-accent);
}

/* Notation par étoiles : cibles plus grandes et mieux espacées au doigt. */
@media (max-width: 767px) {
  .rs-rating-input {
    gap: 8px;
  }

  .rs-star-label {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: var(--min-touch-target);
    min-height: var(--min-touch-target);
  }

  .rs-star {
    font-size: 28px;
  }
}

/* Message d'erreur du formulaire d'avis, affiché sous le champ concerné. */
.rs-form-error {
  margin: -6px 0 12px;
  font-size: var(--font-size-sm);
  font-weight: 500;
  color: var(--color-danger);
}

.rs-form-group .rs-form-error {
  margin: 6px 0 0;
}

.rs-form-textarea {
  width: 100%;
  padding: 10px 12px;
  font-size: var(--font-size-base);
  font-family: inherit;
  color: var(--color-ink);
  background: var(--color-bg-secondary);
  border: var(--border-thin);
  border-radius: var(--radius-button);
  resize: vertical;
  min-height: 70px;
  transition: background var(--transition-fast), border-color var(--transition-fast);
  box-sizing: border-box;
}

.rs-form-textarea:focus {
  outline: none;
  background: var(--color-bg-white);
  border-color: var(--color-primary);
}

.rs-form-actions {
  margin-top: 10px;
}

.rs-btn-submit {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 9px 18px;
  font-size: var(--font-size-sm);
  font-weight: 500;
  background: var(--color-primary);
  color: var(--color-bg-white);
  border: none;
  border-radius: var(--radius-button);
  cursor: pointer;
  transition: background var(--transition-fast);
}

.rs-btn-submit:hover {
  background: var(--color-primary-hover);
}

/* -- Liste des avis -- */

.rs-reviews-list {
  margin-top: 16px;
}

.rs-review {
  padding: 14px 0;
  border-bottom: var(--border-thin);
}

.rs-review:last-child {
  border-bottom: none;
}

.rs-review-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 6px;
}

.rs-review-author-row {
  display: flex;
  align-items: center;
  gap: 8px;
}

.rs-review-avatar {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--color-bg-secondary);
  border: var(--border-thin);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--font-size-xs);
  color: var(--color-ink-3);
  font-weight: 500;
  flex-shrink: 0;
}

.rs-review-author {
  font-size: var(--font-size-base);
  font-weight: 500;
  color: var(--color-ink);
}

.rs-review-stars {
  font-size: var(--font-size-xs);
  color: var(--color-accent);
}

/* Date + action de suppression regroupées à droite de l'en-tête */
.rs-review-meta {
  display: flex;
  align-items: center;
  gap: 4px;
  flex-shrink: 0;
}

.rs-review-date {
  font-size: var(--font-size-xs);
  color: var(--color-ink-3);
  flex-shrink: 0;
}

.rs-review-text {
  font-size: var(--font-size-base);
  color: var(--color-ink-3);
  line-height: 1.6;
}

.rs-review-delete {
  display: flex;
  align-items: center;
}

.rs-empty-reviews {
  text-align: center;
  padding: 2rem;
  color: var(--color-ink-3);
}

/* -- Barre admin -- */

.rs-admin-bar {
  display: flex;
  gap: 8px;
  border-top: var(--border-thin);
  padding-top: 16px;
  margin-top: 8px;
}

/* Les deux actions utilisent les boutons canoniques du système
   (.btn-primary / .btn-danger, cf. components.css) : même gabarit que
   « Créer la recette » du formulaire. */

.rs-admin-delete {
  display: inline;
}

/* === Colonne droite — Ingrédients (sidebar) === */

.rs-sidebar {
  background: var(--color-bg-white);
  padding: 28px 20px;
}

.rs-sidebar-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 8px;
}

.rs-sidebar-header .rs-section-title {
  margin-bottom: 0;
  border-bottom: none;
  padding-bottom: 0;
}

.rs-sidebar-servings {
  display: flex;
  align-items: center;
  gap: 6px;
}

.rs-servings-btn {
  width: 26px;
  height: 26px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  color: var(--color-ink-3);
  border: var(--border-thin);
  border-radius: var(--radius-button);
  cursor: pointer;
  font-size: var(--font-size-sm);
  font-weight: 500;
  transition: background var(--transition-fast), color var(--transition-fast);
  padding: 0;
}

.rs-servings-btn:hover {
  background: var(--color-primary);
  color: var(--color-bg-white);
  border-color: var(--color-primary);
}

.rs-servings-val {
  font-size: var(--font-size-sm);
  font-weight: 500;
  color: var(--color-ink);
  min-width: 18px;
  text-align: center;
}

/* Steppers de portions : cible tactile agrandie sur mobile (recalcul des quantités). */
@media (max-width: 767px) {
  .rs-servings-btn {
    width: 40px;
    height: 40px;
  }
}

/* -- Liste des ingrédients -- */

.rs-ing-list {
  margin-top: 16px;
  border-top: var(--border-thin);
}

.rs-ing-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 7px 0;
  border-bottom: var(--border-width) solid var(--color-border-light);
}

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

.rs-ing-name {
  font-size: var(--font-size-base);
  color: var(--color-ink-2);
}

.rs-ing-qty {
  display: inline-block;
  padding: 2px 8px;
  background: var(--color-bg-secondary);
  border-radius: 2px;
  font-size: var(--font-size-sm);
  font-weight: 500;
  color: var(--color-ink-3);
  white-space: nowrap;
}

.rs-empty {
  font-size: var(--font-size-sm);
  color: var(--color-ink-3);
  font-style: italic;
  padding: 16px 0;
}

/* === CTA persistant (mobile) === */
/* Masqué par défaut : n'apparaît qu'en mobile (voir media query max-width: 767px).
   Le hero porte déjà le CTA sur desktop. */

.rs-mobile-cta {
  display: none;
}

.rs-mobile-cta form {
  margin: 0;
}

.rs-mobile-cta-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: var(--min-touch-target);
  padding: 12px 18px;
  font-size: var(--font-size-base);
  font-weight: 500;
  border-radius: var(--radius-button);
  text-decoration: none;
  cursor: pointer;
  box-sizing: border-box;
}

/* === Toast de confirmation de partage (« Lien copié ») === */
/* Créé à la volée par share_controller lors du repli presse-papier. */

.share-toast {
  position: fixed;
  left: 50%;
  bottom: calc(1.5rem + env(safe-area-inset-bottom));
  transform: translate(-50%, 1rem);
  z-index: 300;
  padding: 10px 18px;
  background: var(--color-ink);
  color: var(--color-bg-white);
  font-size: var(--font-size-sm);
  font-weight: 500;
  border-radius: var(--radius-button);
  box-shadow: var(--shadow-lg);
  opacity: 0;
  transition: opacity var(--transition-fast), transform var(--transition-fast);
  pointer-events: none;
}

.share-toast.is-visible {
  opacity: 1;
  transform: translate(-50%, 0);
}

/* === Desktop : colonne INGRÉDIENTS sticky === */
/* La sidebar reste visible pendant la lecture des étapes. align-self: start
   empêche l'étirement de la cellule de grille (condition d'un sticky effectif). */
@media (min-width: 768px) {
  .rs-sidebar {
    position: sticky;
    /* Ancrée sous le header sticky pour ne pas passer dessous */
    top: var(--header-height);
    align-self: start;
    max-height: calc(100vh - var(--header-height));
    overflow-y: auto;
  }
}

/* ============================================
   FICHE RECETTE — RESPONSIVE
   ============================================ */

/* Mobile (max-width: 767px) */
@media (max-width: 767px) {
  /* NOTE: .rs-back-bar responsive supprimé — utilise .context-bar */

  .rs-hero-inner {
    grid-template-columns: 1fr;
  }

  .rs-hero-photo {
    order: -1;
    min-height: 220px;
    max-height: 260px;
  }

  .rs-hero-content {
    padding: 16px;
    gap: 14px;
  }

  .rs-title {
    font-size: 1.375rem;
  }

  .rs-meta-box {
    align-self: stretch;
  }

  .rs-meta-cell {
    flex: 1;
    padding: 8px 10px;
  }

  /* Le CTA du hero étant masqué (règle ci-dessous), il ne reste que favori +
     partage : on conserve la rangée pour les garder sur une seule ligne, sans
     retour à la ligne. */
  .rs-actions {
    flex-wrap: nowrap;
  }

  /* Le favori occupe toute la largeur restante à gauche du partage */
  .rs-actions .recipe-favorite-container {
    flex: 1;
    min-width: 0;
  }

  .rs-actions .recipe-favorite-container,
  .rs-actions .rs-btn-fav {
    width: 100%;
    justify-content: center;
    min-height: var(--min-touch-target);
    box-sizing: border-box;
  }

  /* Le CTA « Ajouter/Retirer » du hero est repris par la barre persistante
     (.rs-mobile-cta) : on le masque ici pour éviter le doublon. Favori + partage
     restent dans le hero. On masque le lien direct (visiteur non connecté) ainsi
     que le wrapper #draft-cta-hero (utilisateur connecté). */
  .rs-actions .rs-btn-accent,
  .rs-actions > div:has(.rs-btn-accent) {
    display: none;
  }

  /* Cible tactile carrée : ne se comprime pas à côté du favori extensible */
  .rs-btn-icon {
    width: var(--min-touch-target);
    height: var(--min-touch-target);
    flex: 0 0 auto;
  }

  .rs-body {
    grid-template-columns: 1fr;
    min-width: 100%;
    /* Réserve la place du CTA fixe pour ne masquer aucun contenu (pagination, avis). */
    padding-bottom: 6rem;
  }

  /* CTA menu persistant, ancré en bas de l'écran */
  .rs-mobile-cta {
    display: block;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 150;
    padding: 12px 16px calc(12px + env(safe-area-inset-bottom));
    background: var(--color-bg-white);
    border-top: var(--border-thin);
    box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.08);
  }

  .rs-sidebar {
    order: -1;
    padding: 16px;
    border-top: none;
    border-bottom: var(--border-thin);
  }

  .rs-main {
    padding: 16px;
    border-right: none;
  }

  .rs-rating-summary {
    flex-direction: column;
  }

  .rs-rating-left {
    border-right: none;
    border-bottom: var(--border-thin);
    padding: 0 0 12px;
    margin-bottom: 12px;
  }

  .rs-rating-right {
    padding: 0;
  }

  .rs-admin-bar {
    flex-direction: column;
  }

  /* Empilés : chaque action prend toute la largeur — y compris le bouton de
     suppression, enveloppé dans le form généré par button_to. */
  .rs-admin-bar .btn,
  .rs-admin-delete,
  .rs-admin-delete form {
    width: 100%;
  }
}

/* Tablette (768px – 1024px) */
@media (min-width: 768px) and (max-width: 1024px) {
  .rs-hero-inner,
  .rs-body {
    --rs-aside-width: 260px;
  }

  .rs-hero-content {
    padding: 24px 20px;
  }

  .rs-main {
    padding: 20px;
  }

  .rs-sidebar {
    padding: 20px 16px;
  }

  .rs-title {
    font-size: 24px;
  }
}

/* === Filter checkbox inline === */

.filter-checkbox {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.filter-label-inline {
  cursor: pointer;
}

/* === Button small === */

.btn-small {
  padding: 0.5rem 1rem;
  font-size: var(--font-size-sm);
}

/* ============================================
   FORMULAIRE RECETTE — Slate Craft Premium
   ============================================
   Préfixe : rf- (recipe form)
   Design éditorial cohérent avec la fiche recette (rs-).
   Compact, bordures fines, typo serrée.
   ============================================ */

/* ============================================
   DRAFT RAIL — Menu à valider visible pendant la recherche
   ============================================
   Base mobile : barre fixe en bas d'écran (compteur repliable, bandeau de
   vignettes, accès au menu). Desktop : colonne sombre à droite de .recipes-body,
   pleine hauteur et fixe au scroll (voir le @media en fin de section).
   La grille interne est décrite par zones : un seul ordre DOM sert les deux
   gabarits (bouton à droite du compteur en mobile, sous la liste en desktop).
   ============================================ */

.draft-rail {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  /* Au-dessus du bouton flottant « Filtres » (150), sous le backdrop des filtres
     (180) qui doit pouvoir le recouvrir quand le drawer s'ouvre. */
  z-index: 160;
  display: grid;
  grid-template-columns: 1fr auto;
  /* Bandeau en piste auto : replié (display: none) il ne réserve aucune hauteur. */
  grid-template-rows: var(--rail-bar-h) auto;
  grid-template-areas:
    "count action"
    "list  list";
  align-items: center;
  column-gap: 0.75rem;
  padding: 0 1rem env(safe-area-inset-bottom, 0px);
  background: var(--color-primary);
  box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.18);
}

/* Compteur : <button> déclencheur de l'accordéon quand il y a des vignettes à
   replier, <p> sinon — mêmes styles dans les deux cas (d'où le reset bouton). */
.draft-rail-count {
  grid-area: count;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  width: 100%;
  min-width: 0;
  margin: 0;
  padding: 0;
  background: none;
  border: 0;
  font: inherit;
  font-size: var(--font-size-sm);
  font-weight: 500;
  color: var(--color-bg-white);
  text-align: left;
}

/* Affordance réservée au cas où le compteur plie réellement le bandeau : en
   desktop le JS le passe à disabled, et le <p> n'est pas cliquable. */
button.draft-rail-count:not(:disabled) {
  cursor: pointer;
}

.draft-rail-count > span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.draft-rail-chevron {
  color: var(--color-ink-4);
  transition: transform var(--transition-fast);
}

/* Replié, le chevron pointe vers le haut : le bandeau s'ouvre vers le haut. */
.draft-rail-count[aria-expanded="false"] .draft-rail-chevron {
  transform: rotate(180deg);
}

.draft-rail-list {
  grid-area: list;
  display: flex;
  gap: 0.5rem;
  /* Hauteur exacte du bandeau : vignette (56px, bordure comprise) + padding. */
  height: var(--rail-strip-h);
  margin: 0;
  padding: 0.5rem 0;
  list-style: none;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: thin;
  scrollbar-color: var(--color-surface-dark-border-hover) transparent;
}

.draft-rail-list li {
  flex: 0 0 auto;
}

/* Bandeau replié — classe posée par mobile-accordion sur .recipes-container,
   donc uniquement sous le point de rupture mobile. */
.mobile-accordion--collapsed .draft-rail-list {
  display: none;
}

.draft-rail-thumb {
  display: block;
  width: 56px;
  height: 56px;
  border: var(--border-width) solid var(--color-surface-dark-border);
  border-radius: var(--radius-card-compact);
  background: var(--color-surface-dark);
  overflow: hidden;
  transition: border-color var(--transition-fast);
}

.draft-rail-thumb:hover {
  border-color: var(--color-bg-white);
}

.draft-rail-thumb img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Croix de retrait d'une vignette : ancrée sur le <li> et posée DANS le coin
   de l'image — un débord serait rogné par le défilement du bandeau (overflow),
   même contrainte que l'infobulle native du lien. Pastille overlay compacte,
   comme les actions des cartes du menu (.mc-act-btn) : la cible tactile
   canonique de 44px ne tient pas sur une vignette de 56px. */
.draft-rail-item {
  position: relative;
}

.draft-rail-remove {
  position: absolute;
  top: 3px;
  right: 3px;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.55);
  color: var(--color-bg-white);
  cursor: pointer;
  transition: background var(--transition-fast);
}

.draft-rail-remove:hover,
.draft-rail-remove:focus-visible {
  background: var(--color-danger);
}

.draft-rail-btn {
  grid-area: action;
  white-space: nowrap;
}

/* authentication.css redéfinit .btn et est chargé après ce fichier dans le
   manifest : à spécificité égale, son padding (0.75rem 1.5rem) et sa police
   (1rem) écrasent .btn-sm. On rétablit le gabarit .btn-sm en portant la règle
   sur .draft-rail (deux classes) — sans quoi le libellé se casse sur trois
   lignes dans la colonne desktop.
   La cible tactile de 44px, elle, vient déjà du .btn-sm mobile de components.css. */
.draft-rail .draft-rail-btn {
  padding: 0.375rem 0.875rem;
  font-size: var(--font-size-xs);
}

/* ── Desktop : colonne sombre à droite, pleine hauteur, fixe au scroll ──
   (la piste de grille qui l'accueille est déclarée avec .recipes-body, plus haut) */
@media (min-width: 768px) {
  .draft-rail {
    position: sticky;
    /* Ancré sous le header sticky pour ne pas passer dessous */
    top: var(--header-height);
    left: auto;
    right: auto;
    bottom: auto;
    z-index: auto;
    width: 120px;
    height: calc(100vh - var(--header-height));
    grid-template-columns: 1fr;
    /* Compteur en haut et bouton en bas toujours visibles : seule la liste des
       vignettes défile (piste 1fr bornée à 0 pour autoriser le scroll interne). */
    grid-template-rows: auto minmax(0, 1fr) auto;
    grid-template-areas:
      "count"
      "list"
      "action";
    align-items: stretch;
    row-gap: 0.75rem;
    padding: 1rem 0.75rem;
    box-shadow: none;
  }

  /* Colonne étroite : le libellé se déploie sur plusieurs lignes, centré. */
  .draft-rail-count {
    display: block;
    font-size: var(--font-size-xs);
    line-height: 1.3;
    text-align: center;
  }

  .draft-rail-count > span {
    white-space: normal;
  }

  /* Rien à replier en desktop : le compteur rend comme un simple libellé. */
  .draft-rail-chevron {
    display: none;
  }

  .draft-rail-list {
    flex-direction: column;
    height: auto;
    min-height: 0;
    padding: 0;
    overflow-x: hidden;
    overflow-y: auto;
  }

  .draft-rail-thumb {
    width: 100%;
    height: auto;
    aspect-ratio: 1 / 1;
  }

  /* Colonne étroite : padding horizontal resserré pour garder « Voir le menu »
     sur une seule ligne. white-space: normal reste un filet si la police est
     agrandie par l'utilisateur. */
  .draft-rail .draft-rail-btn {
    width: 100%;
    padding-left: 0.375rem;
    padding-right: 0.375rem;
    white-space: normal;
  }
}

/* ============================================
   DRAFT BUTTON — Bouton d'ajout au menu brouillon (overlay sur carte)
   ============================================ */

.recipe-card-wrapper .recipe-draft-container {
  position: absolute;
  top: 0.75rem;
  left: 0.75rem;
  z-index: 1;
}

.draft-btn.draft-btn-compact {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  padding: 0;
  border-radius: 50%;
  border: 1.5px solid var(--color-border-strong);
  font-size: 1.1rem;
  cursor: pointer;
  background: rgba(255, 255, 255, 0.92);
  backdrop-filter: blur(4px);
  color: var(--color-ink-3);
  transition: background var(--transition-fast), border-color var(--transition-fast), color var(--transition-fast), transform var(--transition-fast);
  line-height: 1;
}

/* État : recette dans le brouillon */
.draft-btn.draft-btn-compact.in-draft {
  background: var(--color-accent);
  border-color: var(--color-accent-deep);
  color: var(--color-bg-white);
}

/* Hover : pas dans le brouillon → indiquer l'action d'ajout */
.draft-btn.draft-btn-compact:not(.in-draft):hover {
  background: rgba(255, 255, 255, 1);
  border-color: var(--color-primary);
  color: var(--color-primary);
  transform: scale(1.08);
}

/* Hover : déjà dans le brouillon → indiquer le retrait */
.draft-btn.draft-btn-compact.in-draft:hover {
  background: var(--color-primary-hover);
  border-color: var(--color-primary-hover);
}

/* === Mini Hero sombre === */

.rf-hero {
  background: var(--color-primary);
  padding: 20px 28px 22px;
}

.rf-hero-inner {
  max-width: 820px;
  margin: 0 auto;
}

.rf-kicker {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-accent);
  margin-bottom: 6px;
}

.rf-kicker-line {
  display: inline-block;
  width: 20px;
  height: 1px;
  background: var(--color-accent);
  flex-shrink: 0;
}

.rf-hero-title {
  font-family: var(--font-serif);
  font-size: 24px;
  font-weight: 400;
  color: var(--color-bg-white);
  line-height: 1.15;
  margin: 0;
}

/* === Body (conteneur centré du formulaire) === */

.rf-body {
  max-width: 820px;
  margin: 0 auto;
  padding: 20px 28px 32px;
}

/* Variante large, réservée aux pages en liste (brouillons IA) : 820 px est la
   bonne mesure pour lire un formulaire, trop étroit pour une carte qui aligne
   nom, statut et actions. Hero et body partagent la valeur pour rester alignés. */

.rf-hero-inner--wide,
.rf-body--wide {
  max-width: 1080px;
}

/* === Formulaire === */

.rf-form {
  display: flex;
  flex-direction: column;
}

/* === Sections === */

.rf-section {
  padding: 16px 0;
}

.rf-section:first-child {
  padding-top: 0;
}

/* Ambre foncé plutôt que gris : les intitulés de section sont les repères qui
   découpent un formulaire long, ils doivent accrocher l'œil avant les libellés
   de champ. L'ambre est le seul accent coloré de la charte, et
   --color-accent-text en est la déclinaison lisible sur fond clair
   (5,94:1 sur --color-bg-main). Les familles de tags (.rf-tags-category)
   restent en gris : elles vivent à l'intérieur d'une section, et c'est cet
   écart de couleur qui porte la hiérarchie entre les deux niveaux. */
.rf-section-label {
  font-size: var(--font-size-base);
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--color-accent-text);
  padding-bottom: 6px;
  /* Le filet reprend la couleur du libellé, un cran plus épais que la bordure
     fine des champs : il souligne le titre au lieu de séparer deux blocs. */
  border-bottom: 2px solid var(--color-accent-text);
  margin-bottom: 12px;
}

/* === Overrides de formulaire dans le contexte rf === */

.rf-form .form-label {
  font-size: var(--font-size-sm);
  font-weight: 500;
  color: var(--color-ink-2);
  /* L'écart label ↔ champ est porté par le gap du .form-group : sans cette
     remise à zéro, le margin-bottom global s'y ajoute (double espacement). */
  margin-bottom: 0;
}

/* Le margin-bottom global de .form-group (authentication.css) s'ajoutait au gap
   des rangées : ici l'espacement vertical est porté par .form-row et .rf-section. */
.rf-form .form-group {
  gap: 4px;
  margin-bottom: 0;
}

/* Le formulaire recette dispose ses champs en flex-wrap (2 à 3 par ligne selon
   la largeur), contrairement au grid 2 colonnes canonique. */
.rf-form .form-row {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  margin-bottom: 15px;
}

.rf-form .form-row:last-child {
  margin-bottom: 0;
}

.rf-form .form-row .form-group {
  flex: 1;
  min-width: 200px;
}

.rf-form .form-input,
.rf-form .form-select {
  padding: 8px 12px;
  font-size: var(--font-size-sm);
  border: var(--border-thin);
  background: var(--color-bg-white);
  transition: border-color var(--transition-fast), background var(--transition-fast);
}

.rf-form .form-input:focus,
.rf-form .form-select:focus {
  border-color: var(--color-primary);
  background: var(--color-bg-white);
  box-shadow: 0 0 0 2px var(--color-primary-light);
}

/* Blanc comme tous les autres champs : le beige du textarea se confondait avec
   le fond de page, la zone de saisie la plus vaste du formulaire était aussi la
   moins visible. Une seule couleur de champ, une seule règle de lecture. */
.rf-form .form-textarea {
  padding: 8px 12px;
  font-size: var(--font-size-sm);
  border: var(--border-thin);
  background: var(--color-bg-white);
  line-height: 1.7;
  transition: border-color var(--transition-fast), background var(--transition-fast);
}

.rf-form .form-textarea:focus {
  border-color: var(--color-primary);
  box-shadow: 0 0 0 2px var(--color-primary-light);
}

/* Champs du formulaire recette : 16px sur mobile pour éviter le zoom iOS au focus.
   Bat la taille de base (.rf-form …, spécificité 0,2,0) via une media query placée après. */
@media (max-width: 767px) {
  .rf-form .form-input,
  .rf-form .form-select,
  .rf-form .form-textarea {
    font-size: 16px;
  }
}

/* === Nom de la recette — champ « trait » ===

   Le titre de la recette se saisit comme un titre : pas de cadre, un simple
   trait qui se colore au focus, et une taille de texte nettement supérieure au
   reste du formulaire. Placé après la media query mobile ci-dessus (même
   spécificité) pour que sa taille tienne aussi sur petit écran — 24px passe
   largement le seuil des 16px qui déclenche le zoom iOS. */

.rf-form .rf-name-input {
  padding: 4px 0 10px;
  font-size: var(--font-size-2xl);
  font-weight: 600;
  line-height: 1.25;
  /* Même ambre que son filet et que les intitulés de section : le nom est le
     titre de la recette, il appartient à la même famille de repères. Seule la
     valeur saisie la prend — le placeholder reste gris, sinon un champ vide se
     lirait comme un champ rempli. */
  color: var(--color-accent-text);
  border: none;
  /* Même filet que les intitulés de section : 2px ambre. Le nom est le titre de
     la recette, il ouvre le formulaire comme les sections le rythment. */
  border-bottom: 2px solid var(--color-accent-text);
  border-radius: 0;
  background: transparent;
}

.rf-form .rf-name-input::placeholder {
  color: var(--color-ink-4);
  font-weight: 600;
}

/* Au survol le trait se renforce. Sans cette règle, le hover global de
   .form-input (spécificité supérieure) l'éclaircirait au lieu de l'appuyer.
   Il vire à l'anthracite adouci et non à un gris : depuis un ambre foncé, un
   gris moyen se lirait comme un affadissement du trait, pas comme un appui. */
.rf-form .rf-name-input:hover:not(:focus) {
  border-bottom-color: var(--color-primary-hover);
}

/* Le trait remplace le cadre : ni anneau de focus ni fond, seule sa couleur
   change — l'épaisseur, elle, ne bouge pas (le champ sauterait d'un pixel). */
.rf-form .rf-name-input:focus {
  border-bottom-color: var(--color-primary);
  background: transparent;
  box-shadow: none;
}

/* === Champ « valeur + unité » — cadre partagé ===

   Un seul cadre autour du nombre et de son unité : l'input abandonne le sien au
   profit du conteneur, qui porte aussi l'anneau de focus. Partagé par les durées
   de la rangée des valeurs chiffrées et par la quantité de chaque ingrédient. */

.rf-unit-field {
  display: flex;
  align-items: stretch;
  border: var(--border-thin);
  border-radius: var(--radius-input);
  background: var(--color-bg-white);
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}

.rf-unit-field:focus-within {
  border-color: var(--color-primary);
  box-shadow: 0 0 0 2px var(--color-primary-light);
}

.rf-form .rf-unit-field .form-input {
  flex: 1;
  min-width: 0;
  border: none;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
}

/* Champ étroit : les flèches natives du number input mangeraient la place déjà
   occupée par l'unité — et, pour les personnes, par les boutons − / +. */
.rf-unit-field .form-input::-webkit-outer-spin-button,
.rf-unit-field .form-input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

.rf-unit-field .form-input[type="number"] {
  -moz-appearance: textfield;
  appearance: textfield;
}

/* Unité en lecture seule : simple suffixe gris, sans filet de séparation — elle
   fait corps avec le nombre qu'elle qualifie, le cadre commun suffit à la tenir. */
.rf-unit-suffix {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  padding: 0 10px;
  font-size: var(--font-size-sm);
  font-weight: 500;
  color: var(--color-ink-3);
  white-space: nowrap;
}

/* Unité d'une quantité d'ingrédient : un sélecteur qui se lit comme un suffixe.
   Il partage le cadre du nombre qu'il qualifie (.rf-unit-field), donc pas de
   bordure ni de fond propres, et il garde la typographie grise des mots du
   formulaire — l'unité est un mot, la valeur est le nombre. Chevron dessiné
   plutôt que natif, comme le sélecteur d'ingrédient de la même rangée.

   Il n'apparaît comme un champ que là où il y a un choix : un groupe d'unités
   qui n'en offre qu'une (les pièces) ou une ligne sans ingrédient le rendent
   inerte, et il redevient le simple suffixe gris qu'il était. */
.rf-unit-select {
  flex-shrink: 0;
  appearance: none;
  -webkit-appearance: none;
  border: none;
  border-radius: 0;
  padding: 0 26px 0 8px;
  background-color: transparent;
  background-image: url("data:image/svg+xml,%3Csvg width='10' height='6' viewBox='0 0 10 6' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%23615B54' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 8px center;
  font-family: inherit;
  font-size: var(--font-size-sm);
  font-weight: 500;
  color: var(--color-ink-3);
  cursor: pointer;
}

/* Le cadre commun porte déjà le focus (:focus-within) : l'anneau reste discret
   et vient à l'intérieur, sinon il déborderait du cadre. */
.rf-unit-select:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: -2px;
}

/* opacity : sans elle, Safari délave les sélecteurs désactivés jusqu'à rendre
   l'unité illisible. */
.rf-unit-select:disabled {
  padding-right: 8px;
  background-image: none;
  color: var(--color-ink-4);
  cursor: default;
  opacity: 1;
}

/* L'input étant sans bordure, l'état d'erreur est porté par le cadre commun. */
.has-error > .rf-unit-field {
  border-color: var(--color-danger);
  background: var(--color-danger-light);
}

/* === Rangée des valeurs chiffrées (personnes, durées) ===

   « Pour 4 personnes · Préparation 20 min · Cuisson 15 min » : une phrase dont
   les champs sont les mots. D'où le parti pris typographique de toute la rangée —
   les mots (libellés, unités, ponctuation) en gris discret et graisse de texte,
   les valeurs seules en évidence dans leur cadre. Les libellés restent donc en
   ligne plutôt qu'empilés, et la rangée se replie groupe par groupe : un groupe
   (libellé, champ, unité) n'est jamais coupé.

   Elle occupe toute la largeur en écartant les groupes, pas en étirant les
   cadres : un champ deux fois trop large laisse sa valeur flotter dans le vide,
   alors que l'espace entre deux groupes, lui, articule la phrase. Le gap fait
   office de minimum garanti (18 px), y compris une fois la rangée repliée. */

.rf-measures {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px 18px;
  margin-bottom: 14px;
}

.rf-measure {
  display: flex;
  align-items: center;
  gap: 10px;
}

/* Les mots de la phrase : un seul traitement pour les libellés, les unités hors
   cadre et la ponctuation. Ils accompagnent la valeur, ils ne la concurrencent
   pas — et ne reviennent jamais à la ligne au milieu d'un mot. */
.rf-measures .form-label,
.rf-measure-unit,
.rf-measure-sep {
  font-size: var(--font-size-sm);
  font-weight: 400;
  color: var(--color-ink-3);
  white-space: nowrap;
}

.rf-measure-sep {
  color: var(--color-ink-4);
}

/* Largeurs de cadre explicites, taillées sur leur contenu : de quoi loger trois
   chiffres sans jamais serrer. Elles sont indispensables — sans elles, la largeur
   de référence qui décide du retour à la ligne serait celle, énorme, d'un
   input[type=number] par défaut (~180 px), et la rangée se replierait sur deux
   lignes avant même de manquer de place. */
.rf-stepper {
  width: 120px;
}

.rf-duration {
  width: 108px;
}

/* Survol : la bordure se réchauffe, sauf si le champ est déjà mis en avant par
   le focus. Réservé à cette rangée — les cadres de quantité, empilés par
   dizaines dans la liste d'ingrédients, s'en passent très bien. */
.rf-measure .rf-unit-field:hover:not(:focus-within) {
  border-color: var(--color-ink-4);
}

/* La valeur est le seul texte en évidence de la rangée. Elle occupe tout le
   cadre (moins l'unité) : aucune zone morte, on clique n'importe où pour saisir,
   et le nombre reste centré. Chiffres tabulaires : sans eux, la largeur du
   nombre change d'un clic à l'autre sur le stepper et la valeur tressaute. */
.rf-form .rf-measure .form-input {
  text-align: center;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* Durée non renseignée : un tiret discret plutôt qu'un vide. Il s'efface dès
   qu'on entre dans le champ — par défaut un placeholder reste affiché sous le
   curseur tant qu'on n'a pas tapé. */
.rf-duration .form-input::placeholder {
  color: var(--color-ink-4);
}

.rf-duration .form-input:focus::placeholder {
  color: transparent;
}

/* « min » : calé à droite du cadre, en retrait de taille et de couleur — c'est
   une mention, pas une valeur. */
.rf-measure .rf-unit-suffix {
  padding: 0 12px 0 0;
  font-size: var(--font-size-xs);
  font-weight: 500;
  color: var(--color-ink-4);
}

/* Stepper : les deux boutons occupent les extrémités du cadre sur toute sa
   hauteur, le champ prend tout l'espace entre eux — la valeur est donc centrée
   et la moindre zone cliquable du cadre mène à la saisie. */
.rf-form .rf-stepper .form-input {
  padding-left: 0;
  padding-right: 0;
}

.rf-stepper-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 32px;
  padding: 0;
  border: none;
  /* Rayon intérieur du cadre (4 px de rayon moins 1 px de bordure) : le fond de
     survol épouse le coin arrondi au lieu d'y laisser un éclat blanc. */
  border-radius: 3px;
  background: none;
  color: var(--color-ink-3);
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
  /* Sans cela, un double-clic un peu rapide sur « + » sélectionne le glyphe. */
  user-select: none;
  transition: background var(--transition-fast), color var(--transition-fast);
}

.rf-stepper-btn:hover:not(:disabled) {
  background: var(--color-bg-secondary);
  color: var(--color-ink);
}

/* Retour tactile à l'appui : le fond se fonce le temps du clic. */
.rf-stepper-btn:active:not(:disabled) {
  background: var(--color-bg-tertiary);
}

.rf-stepper-btn:disabled {
  opacity: 0.35;
  cursor: not-allowed;
}

/* Les erreurs des trois champs passent sous la rangée entière. */
.rf-measures .field-errors {
  flex-basis: 100%;
}

/* === Sélecteurs segmentés — régime, difficulté, budget, moments === */

/* Rangée « intitulé à gauche, options à droite ». Les quatre rangées partagent
   la même colonne d'intitulés : l'œil descend une seule verticale au lieu de
   chercher où commence chaque groupe d'options. */
.rf-form .rf-choice {
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 14px;
  margin-bottom: 12px;
}

.rf-form .rf-choice:last-child {
  margin-bottom: 0;
}

/* Largeur fixe (et non « au contenu ») : les intitulés n'ont pas la même
   longueur (« Budget » vs « Moment(s) du repas * »), donc sans cette largeur
   commune les rangées d'options ne démarreraient pas au même endroit. */
.rf-form .rf-choice > .form-label {
  flex: 0 0 140px;
}

/* Une erreur passe sous la rangée entière, jamais entre l'intitulé et ses
   options. */
.rf-choice .field-errors {
  flex: 1 0 100%;
}

/* La rangée : un cadre blanc dans lequel les options se partagent la place,
   séparées par un filet. Le rail grisé sur lequel se posait une pastille claire
   a été abandonné — il disait « sélectionné = clair » quelques centimètres
   au-dessus des tags qui disent « sélectionné = anthracite ». Un seul signal de
   sélection dans tout le formulaire : l'aplat anthracite.
   Ce qui distingue une rangée d'options d'une grappe de capsules n'est donc plus
   la couleur mais la forme : ici les options sont soudées dans un cadre unique
   (jeu fermé, on en choisit une), là les capsules flottent librement (liste
   ouverte, on picore). */
.rf-segmented {
  display: flex;
  flex-wrap: wrap;
  background: var(--color-bg-white);
  border: var(--border-thin);
  border-radius: var(--radius-input);
  /* Les aplats des options aux extrémités suivent l'arrondi du cadre sans avoir
     à porter eux-mêmes un rayon. */
  overflow: hidden;
}

.rf-choice.has-error .rf-segmented {
  border-color: var(--color-danger);
}

.rf-segment {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 6px 14px;
  /* Plancher de largeur : sans lui, un libellé d'un caractère (« € ») donnerait
     une option deux fois plus étroite que ses voisines, et une cible au doigt
     trop petite. */
  min-width: 44px;
  font-size: var(--font-size-sm);
  color: var(--color-ink-3);
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--transition-fast), color var(--transition-fast),
    box-shadow var(--transition-fast);
}

/* Le filet de séparation est une ombre interne plutôt qu'une bordure : il ne
   prend pas de place dans la boîte, donc toutes les options gardent la même
   largeur, et il disparaît d'un trait dès que l'option est cochée — un aplat
   anthracite n'a pas besoin qu'on lui souligne le bord. */
.rf-segment + .rf-segment {
  box-shadow: inset 1px 0 0 var(--color-border);
}

/* Le contrôle natif reste dans le DOM — donc focusable au clavier et exposé
   aux lecteurs d'écran — mais tout l'état visuel est porté par l'option. */
.rf-segment input {
  position: absolute;
  width: 0;
  height: 0;
  margin: 0;
  opacity: 0;
}

/* Le contour global des champs focusés se poserait sur un input de 0×0, donc
   invisible : il est reporté sur l'option, seule chose que l'œil voit. */
.rf-segment input:focus-visible {
  outline: none;
}

/* Contour tracé vers l'intérieur : à l'extérieur, il serait rogné par
   l'overflow du cadre sur les deux options des extrémités. */
.rf-segment:has(input:focus-visible) {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: -2px;
}

.rf-segment:hover {
  background: var(--color-bg-secondary);
  color: var(--color-ink);
}

/* Sélectionné : le même aplat anthracite que les capsules de tags. */
.rf-segment:has(input:checked) {
  background: var(--color-primary);
  color: var(--color-bg-white);
  font-weight: 500;
  box-shadow: none;
}

.rf-segment:has(input:checked):hover {
  background: var(--color-primary-hover);
}

/* Sur l'aplat anthracite, un contour anthracite ne se verrait pas : le focus
   clavier repasse en blanc dès que l'option est cochée. */
.rf-segment:has(input:checked:focus-visible) {
  outline-color: var(--color-bg-white);
}

/* === Tags — capsules ===

   Les tags sont un vocabulaire ouvert et nombreux (une quarantaine, répartis en
   quatre familles) : contrairement aux caractéristiques, ils ne tiennent pas sur
   une piste segmentée. Chaque tag est donc une capsule autonome, posée à plat sur
   le fond de la page, et les familles se lisent grâce à leur intitulé en
   capitales. L'arrondi complet (999px, comme les compteurs de groupe de
   tags.css) les distingue au premier coup d'œil des contrôles rectangulaires du
   formulaire : ici on picore dans une liste, on ne choisit pas dans un jeu fermé. */

.rf-tags-group {
  margin-bottom: 18px;
}

.rf-tags-group:last-child {
  margin-bottom: 0;
}

.rf-tags-category {
  font-size: var(--font-size-sm);
  font-weight: 600;
  letter-spacing: 0.1em;
  color: var(--color-ink-3);
  margin-bottom: 10px;
}

.tags-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.tag-checkbox {
  position: relative;
  display: inline-flex;
  align-items: center;
  padding: 7px 16px;
  background: var(--color-bg-white);
  border: var(--border-thin);
  border-radius: 999px;
  font-size: var(--font-size-sm);
  line-height: 1.2;
  color: var(--color-ink-2);
  cursor: pointer;
  transition: background var(--transition-fast), border-color var(--transition-fast),
    color var(--transition-fast), box-shadow var(--transition-fast);
}

/* Le contrôle natif reste dans le DOM — focusable au clavier et exposé aux
   lecteurs d'écran — mais tout l'état visuel est porté par la capsule. */
.tag-checkbox input {
  position: absolute;
  width: 0;
  height: 0;
  margin: 0;
  opacity: 0;
}

/* Le contour global des champs focusés se poserait sur un input de 0×0, donc
   invisible : il est reporté sur la capsule, seule chose que l'œil voit. */
.tag-checkbox input:focus-visible {
  outline: none;
}

.tag-checkbox:has(input:focus-visible) {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: 2px;
}

.tag-checkbox:hover {
  border-color: var(--color-border-strong);
  background: var(--color-bg-secondary);
  color: var(--color-ink);
}

/* Sélectionné : aplat anthracite, comme partout ailleurs dans la charte. */
.tag-checkbox:has(input:checked) {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: var(--color-bg-white);
  box-shadow: var(--shadow-sm);
}

.tag-checkbox:has(input:checked):hover {
  background: var(--color-primary-hover);
  border-color: var(--color-primary-hover);
}

/* === Photo === */

/* .rf-photo est une colonne flex : sans align-items, l'aperçu et le bouton
   « Changer de photo » (inline-flex) seraient étirés sur toute la largeur. */
.rf-form .rf-photo {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 10px;
}

/* La zone de dépôt, elle, occupe toute la largeur du formulaire. */
.rf-form .rf-photo-upload {
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: 100%;
}

/* La zone d'upload est masquée tant qu'une photo est en place : il faut battre
   le display: flex ci-dessus, sinon l'attribut hidden reste sans effet. */
.rf-form .rf-photo-upload[hidden] {
  display: none;
}

/* === Zone de dépôt de la photo === */

.rf-dropzone {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 15px;
  padding: 26px 20px;
  border: var(--border-width) dashed var(--color-border-strong);
  border-radius: var(--radius-input);
  background: var(--color-bg-white);
  text-align: center;
  cursor: pointer;
  transition: border-color var(--transition-fast), background var(--transition-fast);
}

.rf-dropzone:hover {
  border-color: var(--color-ink-3);
}

/* Survol d'un fichier glissé (classe posée par image_preview_controller) et
   focus clavier sur le champ fichier masqué : même mise en avant. */
.rf-dropzone.is-dragging,
.rf-photo-upload:has(.photo-upload-input:focus-visible) .rf-dropzone {
  border-color: var(--color-primary);
}

/* Le contenu ne capte ni le clic ni le survol : sans cela, passer d'un enfant à
   l'autre en glissant émet un dragleave parasite qui fait clignoter la zone. */
.rf-dropzone > * {
  pointer-events: none;
}

.rf-dropzone-icon {
  color: var(--color-ink-3);
}

.rf-dropzone-title {
  font-size: var(--font-size-sm);
  font-weight: 600;
  color: var(--color-ink);
}

.rf-dropzone-hint {
  font-size: var(--font-size-xs);
  color: var(--color-ink-3);
}

/* === Ingrédients — nested fields ===
   Un ingrédient = une rangée. Base mobile : le nom prend la ligne entière, la
   quantité et la croix se partagent celle du dessous. Sur écran large les trois
   éléments se réalignent sur une seule ligne (cf. media query en fin de section).

   Pas de carte autour : elle était blanche et ne contenait que des champs
   blancs — un cadre sans contenu propre, qui décalait en prime les champs de
   12px vers l'intérieur par rapport au reste du formulaire. Les champs se
   détachent d'eux-mêmes sur le fond beige de la page ; ce qui tient la rangée
   ensemble, c'est la proximité, donc l'écart interne (6px) doit rester
   nettement inférieur à l'écart entre deux ingrédients (16px) — sans quoi,
   rangée repliée, chaque quantité se lit comme celle de l'ingrédient suivant. */

.preparations-container {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

/* La croix descend sur la seconde ligne plutôt que de flanquer le nom : elle lui
   coûtait la largeur d'une cible tactile, donc un nom abrégé en plein milieu
   (« Persil frais (persil, bouquet de per… »), alors que la ligne du dessous
   laissait un tiers de l'écran vide à côté de la quantité. */
.preparation-fields {
  /* Gabarit de la quantité, partagé par le champ et par la piste de grille qui
     l'accueille : une seule valeur à ajuster, à mobile comme à desktop. */
  --prep-qty-width: 148px;
  display: grid;
  grid-template-columns: var(--prep-qty-width) minmax(0, 1fr);
  grid-template-areas:
    "ingredient ingredient"
    "quantity   remove";
  align-items: center;
  gap: 6px 8px;
}

/* Ligne marquée pour suppression : toujours soumise (donc dans le DOM) mais
   masquée. Nécessaire pour battre le display: grid ci-dessus. */
.preparation-fields[hidden] {
  display: none;
}

.preparation-ingredient {
  grid-area: ingredient;
  /* Lève le plancher min-content de l'élément de grille : un nom d'ingrédient
     long s'abrège dans sa piste au lieu de la pousser hors de l'écran — le
     débordement latéral se paie ici par la page entière (cf. .rf-body). */
  min-width: 0;
}

/* Valeur courte : largeur fixe, qui sert aussi de gabarit à la colonne « quantité »
   de la grille sur écran large. Elle tient la valeur la plus longue, plus son
   unité et le chevron de son sélecteur — au-delà, c'est de l'air qui éloigne
   inutilement le nombre de la croix. */
.preparation-quantity {
  grid-area: quantity;
  width: var(--prep-qty-width);
}

/* Une ligne d'ingrédient et les deux boutons qui closent la liste appartiennent
   au même empilement : ils partagent donc la hauteur des boutons (.btn, 44px),
   sinon la section descend en marches d'escalier. Le padding vertical des champs
   reste inchangé, c'est la hauteur minimale qui les met au gabarit. */
.rf-form .preparation-ingredient .form-select,
.preparation-quantity .rf-unit-field {
  min-height: var(--min-touch-target);
}

/* Chevron dessiné plutôt que la flèche native, collée au bord dans la plupart
   des navigateurs : elle respire ici des mêmes 12px que le texte du champ. */
.rf-form .preparation-ingredient .form-select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 32px;
  background-image: url("data:image/svg+xml,%3Csvg width='10' height='6' viewBox='0 0 10 6' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%23615B54' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
}

/* Calée sur le bord droit, dans la même colonne qu'en écran large : la croix
   change de ligne au repli, jamais de verticale. */
.preparation-remove {
  grid-area: remove;
  justify-self: end;
}

/* Quantité + unité : cadre partagé .rf-unit-field (défini plus haut avec les
   overrides de formulaire), y compris son état d'erreur. */

/* Croix de suppression : bouton icône canonique (components.css) débarrassé de
   son cadre — la rangée se lit sans qu'on encadre chacun de ses éléments — et
   de sa marge horizontale. L'icône passe en ink-3 : le gris ink-4 par défaut
   tenait sur le blanc de l'ancienne carte, il descend à 2,3:1 sur le beige de
   la page, sous le seuil de 3:1 attendu d'un contrôle. */
.preparation-remove .icon-btn {
  margin: 0;
  border-color: transparent;
  background: transparent;
  color: var(--color-ink-3);
}

/* Spécificité 0,3,0 : bat aussi le fond gris de .icon-btn:hover en mobile. */
.preparation-remove .icon-btn:hover {
  background: var(--color-danger-light);
  border-color: transparent;
  color: var(--color-danger);
}

/* Écran large : nom, quantité et croix se réalignent sur une seule ligne. La
   colonne « quantité » est dimensionnée par la largeur de .preparation-quantity. */
@media (min-width: 768px) {
  /* Un ingrédient tient sur une ligne : plus rien à regrouper verticalement, la
     liste se resserre en un tableau dense. */
  .preparations-container {
    gap: 2px;
  }

  .preparation-fields {
    grid-template-columns: minmax(0, 1fr) auto auto;
    grid-template-areas: "ingredient quantity remove";
  }

  /* Base nulle plutôt que auto : les deux boutons occupent la même largeur quel
     que soit celle de leur libellé, sinon « Créer un nouvel ingrédient » serait
     le plus large des deux alors que c'est le geste le plus rare. */
  .rf-form .preparations-actions .btn {
    flex: 1 1 0;
  }
}

/* Les deux actions se font face sur une seule rangée et se partagent la largeur
   à parts égales (cf. media query desktop plus bas) ; sous 768px elles
   s'empilent, chacune sur toute la largeur. Ce qui les distingue n'est donc pas
   leur encombrement mais leur traitement — tirets contre bouton plein — et leur
   picto. */
.rf-form .preparations-actions {
  margin-top: 16px;
  margin-bottom: 0;
  gap: 10px;
}

/* Ajout d'une ligne : le trait en tirets dit « la liste continue ici » — même
   geste que la zone de dépôt photo (.rf-dropzone) ou l'ajout d'un repas dans un
   menu (.mc-add-btn). Il libère surtout l'aplat anthracite, qui doit rester
   l'apanage de l'action finale : deux boutons noirs identiques dans un même
   écran, l'un pour ajouter une ligne et l'autre pour créer la recette, se
   disputaient le même poids alors qu'ils n'engagent pas du tout autant. */
.rf-form .rf-add-ingredient {
  background: var(--color-bg-white);
  border: var(--border-width) dashed var(--color-border-strong);
  color: var(--color-ink-2);
}

/* Au survol, l'ambre de la charte : la zone s'annonce cliquable sans se
   remplir, et reprend l'accent déjà porté par les intitulés de section. */
.rf-form .rf-add-ingredient:hover {
  border-color: var(--color-accent-deep);
  color: var(--color-accent-text);
  background: var(--color-bg-white);
}

/* === Actions du formulaire === */

.rf-actions {
  display: flex;
  align-items: center;
  gap: 14px;
  padding-top: 18px;
  margin-top: 4px;
}

/* Source d'un import IA : rappel posé en tête du formulaire de validation pour
   rouvrir la page d'origine et confronter ce que l'IA a extrait. Pense-bête et
   pas bandeau — il ne doit pas repousser le premier champ vers le bas. */

.rf-source {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px 12px;
  margin-bottom: 16px;
  padding: 9px 14px;
  background: var(--color-info-light);
  border: 1px solid var(--color-info-border);
  border-radius: var(--radius-input);
}

.rf-source__label {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  flex-shrink: 0;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-info-text);
}

/* L'adresse occupe la place restante et se tronque plutôt que de casser la
   ligne ; sous 240px disponibles elle passe à la ligne (base flex) au lieu de
   se réduire à trois caractères. Soulignée d'emblée pour se lire comme un lien
   — le survol en assombrit l'encre via la règle globale des liens texte. */
.rf-source__link {
  flex: 1 1 240px;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 12px;
  color: var(--color-info-text);
  text-decoration: underline;
}

/* === Visionneuse de la source (import photo) ===

   Valider un import photo, c'est recopier une page : elle doit rester lisible
   pendant toute la saisie, pas s'ouvrir à chaque doute. Elle occupe donc une
   colonne collée au défilement, à droite du formulaire — qui garde sa mesure de
   820 px et sa position, repli ou pas : rien ne se déplace sous les yeux quand on
   replie la source.

   Deux régimes, un seul balisage. En dessous de 1200 px les deux colonnes ne
   tiennent plus (miroir de WIDE_SCREEN dans source_viewer_controller.js) : la
   visionneuse redevient une superposition, l'aside faisant office de voile. */

/* Largeur plancher de la colonne : en deçà, une page photographiée n'offre plus
   assez de prise au premier coup d'œil. Au-delà, elle est fluide — voir la grille
   du régime deux colonnes, qui lui donne tout ce que le formulaire ne prend pas. */
.rf-split {
  --source-viewer-min-width: 380px;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 24px;
  /* Indispensable au sticky de la colonne : étirée sur la hauteur de la ligne,
     elle n'aurait aucune marge de manœuvre pour se décoller. */
  align-items: start;
}

/* Borné, jamais centré : le formulaire reste aligné sur le titre du hero et ne
   bouge pas d'un pixel quand la source se replie. */
.rf-split__main {
  min-width: 0;
  max-width: 820px;
}

/* .rf-body est un item flex de .main-content : ses marges automatiques y absorbent
   l'espace libre, ce qui le dimensionne sur son contenu au lieu de l'étirer. Or une
   grille n'y contribue pas la largeur que le formulaire seul y contribue — sans
   cette largeur explicite, la colonne du formulaire se réduirait à ses champs les
   plus larges. C'est la contrepartie de la grille, donc réservé aux pages qui en
   ont une : les autres formulaires gardent le dimensionnement d'origine. */
.rf-body--split {
  width: 100%;
}

/* Les deux états par défaut sont portés par le CSS et non par le contrôleur —
   colonne ouverte sur grand écran, languette seule en dessous : au chargement,
   rien ne clignote le temps que Stimulus s'attache. Le contrôleur ne pose une
   classe que pour l'écart au défaut de la largeur courante, d'où deux noms
   symétriques : .is-source-open (ouverte alors qu'elle serait masquée),
   .is-source-hidden (repliée alors qu'elle serait ouverte). */

/* Superposition (régime par défaut) : l'aside est le voile, la plaque blanche se
   centre dedans. Masquée tant que la languette n'a pas été actionnée. */
.rf-viewer {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 9990;
  padding: 12px;
  background: rgba(28, 25, 22, 0.55);
}

.is-source-open .rf-viewer {
  display: block;
}

.rf-viewer__inner {
  display: flex;
  flex-direction: column;
  height: 100%;
  max-width: 720px;
  margin: 0 auto;
  overflow: hidden;
  background: var(--color-bg-white);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-lg);
}

/* En-tête : l'intitulé dans le registre ambré des sections du formulaire — la
   source est une pièce du même écran, pas un module étranger. */
.rf-viewer__head {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 4px 6px 4px 14px;
  background: var(--color-bg-secondary);
  border-bottom: 1px solid var(--color-border-light);
}

.rf-viewer__label {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  /* Pousse les deux actions contre le bord droit. */
  margin-right: auto;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-accent-text);
}

/* Le cadre porte les gestes, l'image ne fait que suivre : d'où le débordement
   masqué ici et la transformation là. Fond beige soutenu — une page de livre
   photographiée est claire, elle a besoin d'un cadre pour se détacher. */
.rf-viewer__frame {
  flex: 1;
  min-height: 0;
  overflow: hidden;
  background: var(--color-bg-tertiary);
  cursor: zoom-in;
  /* Le glisser déplace l'image ; sans cela le navigateur y verrait un défilement
     ou un glisser-déposer natif. */
  touch-action: none;
}

.rf-viewer__frame.is-zoomed {
  cursor: grab;
}

.rf-viewer__frame.is-panning {
  cursor: grabbing;
}

.rf-viewer__img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  /* Le zoom part du centre : c'est ce repère que suppose le calcul de la
     translation dans le contrôleur. */
  transform-origin: center center;
  user-select: none;
  -webkit-user-drag: none;
}

/* Contrôles de zoom dans l'en-tête, et non flottant sur le bas de l'image : la
   colonne fait la hauteur de l'écran mais démarre sous le hero, donc son pied
   passe sous la ligne de flottaison tant qu'on n'a pas défilé — une commande qui
   ne se voit qu'après avoir défilé ne se découvre pas. Groupés sur un aplat
   blanc, ils se distinguent des deux actions du panneau (plein écran, masquer). */
.rf-viewer__zoom {
  display: flex;
  align-items: center;
  gap: 2px;
  margin-right: 2px;
  padding: 2px;
  background: var(--color-bg-white);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-button);
}

/* Le niveau de zoom est aussi le bouton qui le remet à 100 % : chiffres à largeur
   fixe pour que la barre ne tremble pas pendant qu'on tourne la molette. */
.rf-viewer__level {
  min-width: 58px;
  height: var(--min-touch-target);
  background: transparent;
  border: 0;
  font-size: var(--font-size-sm);
  font-variant-numeric: tabular-nums;
  color: var(--color-ink-3);
  cursor: pointer;
  transition: color var(--transition-fast);
}

.rf-viewer__level:hover {
  color: var(--color-ink);
}

/* Languette de rappel, collée au bord droit : la seule trace de la source quand
   elle est repliée, et la porte d'entrée sur les écrans étroits. Le chevron dit
   d'où le panneau revient. Même étage que les autres éléments flottants de page
   (bouton « Filtres » du catalogue), donc sous le header. */
.rf-viewer-handle {
  position: fixed;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  z-index: 150;
  display: flex;
  align-items: center;
  gap: 4px;
  height: var(--min-touch-target);
  padding: 0 10px;
  background: var(--color-bg-white);
  border: 1px solid var(--color-border);
  border-right: 0;
  border-radius: var(--radius-card) 0 0 var(--radius-card);
  box-shadow: -2px 0 8px rgba(28, 25, 22, 0.09);
  color: var(--color-ink-3);
  cursor: pointer;
  transition: color var(--transition-fast), border-color var(--transition-fast);
}

.rf-viewer-handle:hover {
  color: var(--color-accent-text);
  border-color: var(--color-accent-deep);
}

.is-source-open .rf-viewer-handle {
  display: none;
}

/* Deux colonnes : la place existe, la source devient une colonne du document au
   lieu d'une superposition. C'est ici, et ici seulement, que le formulaire de
   validation s'élargit — hero compris, pour que le titre reste aligné sur lui. */
@media (min-width: 1200px) {
  /* Large mesure, réservée à cette page : le formulaire garde ses 820 px et tout
     le reste va à la source, qui est ici l'autre moitié du travail. Le plafond
     n'est donc pas celui d'un texte à lire mais celui de l'image à confronter —
     au-delà, on afficherait un import de 1600 px à moins de deux fois sa
     définition, et les deux colonnes s'écarteraient trop pour un aller-retour du
     regard. Hero comprise : le titre suit la mesure du formulaire, comme sur les
     autres pages. */
  .rf-hero-inner--split,
  .rf-body--split {
    max-width: 1680px;
  }

  /* Le formulaire prend sa mesure de lecture, la source prend le reste : c'est
     elle qui profite de la largeur de l'écran, puisque c'est elle qu'on déchiffre.
     Les deux pistes savent se réduire — la source jusqu'à son plancher, puis le
     formulaire — pour que rien ne déborde entre 1200 et 1280 px. */
  .rf-split:not(.is-source-hidden) {
    grid-template-columns: minmax(0, 820px) minmax(var(--source-viewer-min-width), 1fr);
  }

  .rf-split:not(.is-source-hidden) .rf-viewer {
    display: block;
    position: sticky;
    /* inset remis à auto avant tout : le régime superposé l'avait cloué aux
       quatre bords, et un `bottom` résiduel fait dériver un élément sticky. */
    inset: auto;
    /* Ancrée sous le header sticky, elle tient la hauteur de l'écran : la source
       reste entière sous les yeux, des mesures jusqu'aux tags. */
    top: calc(var(--header-height) + 16px);
    height: calc(100vh - var(--header-height) - 32px);
    padding: 0;
    background: none;
    z-index: auto;
  }

  .rf-split:not(.is-source-hidden) .rf-viewer-handle {
    display: none;
  }

  /* Plaque à même la colonne : plus de voile à surplomber, l'ombre s'allège. */
  .rf-viewer__inner {
    max-width: none;
    box-shadow: var(--shadow-md);
  }
}

/* Abandon d'un import IA : action destructive tenue à distance des actions du
   formulaire, sous une séparation nette — on ne supprime pas un import d'un
   clic voisin de « Publier ». */
.rf-discard {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  margin-top: 24px;
  padding-top: 16px;
  border-top: 1px solid var(--color-border-light);
}

.rf-discard__hint {
  margin: 0;
  font-size: 0.85rem;
  color: var(--color-ink-3);
}

.rf-discard__btn {
  padding-left: 0;
  padding-right: 0;
  color: var(--color-danger);
  white-space: nowrap;
}

.rf-discard__btn:hover {
  color: var(--color-danger-hover);
  text-decoration: underline;
}

/* ============================================
   FORMULAIRE RECETTE — RESPONSIVE
   ============================================ */

/* Mobile (max-width: 767px) */
@media (max-width: 767px) {
  .rf-hero {
    padding: 14px 16px 16px;
  }

  .rf-hero-title {
    font-size: 1.25rem;
  }

  /* width : .rf-body est un item flex à marges automatiques, donc dimensionné
     sur son contenu (cf. .rf-body--split). Sur un téléphone, le contenu le plus
     large du formulaire — l'adresse de la source, un nom de recette long —
     réclame plus que l'écran : la page entière devenait alors plus large que le
     téléphone et défilait latéralement, décalant jusqu'au logo du header. Bornée
     à la largeur disponible, elle force ce contenu à tenir dans l'écran (le lien
     de source s'y abrège déjà). */
  .rf-body {
    width: 100%;
    padding: 14px 16px 28px;
  }

  .rf-section {
    padding: 12px 0;
  }

  .rf-section-label {
    margin-bottom: 10px;
  }

  /* Une ligne par champ : à cette largeur, deux champs côte à côte laissent
     trop peu de place au libellé comme à la valeur. */
  .rf-form .form-row {
    gap: 10px;
  }

  .rf-form .form-row .form-group {
    flex: 1 1 100%;
    min-width: 0;
  }

  /* La phrase des valeurs chiffrées tient rarement sur une ligne à cette
     largeur : elle se replie groupe par groupe, et la ponctuation qui séparait
     « personnes » de « Préparation » n'a alors plus rien à séparer. */
  .rf-measure-sep {
    display: none;
  }

  /* Repliée, la rangée devient une ligne par valeur : l'intitulé à gauche, le
     cadre calé à droite par la marge automatique qui suit l'intitulé. Les deux
     durées partagent ainsi la même largeur et le même bord droit — l'œil
     descend une seule verticale au lieu de suivre la longueur des intitulés. */
  .rf-measure {
    flex: 1 1 100%;
  }

  .rf-measures .form-label {
    margin-right: auto;
  }

  /* Le cadre ne s'étire plus jusqu'au bord : à pleine largeur, une valeur de
     trois chiffres au plus flotte au milieu du vide. Les largeurs restent un
     peu plus généreuses qu'en grand écran, la hauteur passant à hauteur de
     doigt et les boutons − / + s'élargissant avec elle. */
  .rf-measure .rf-unit-field {
    min-height: var(--min-touch-target);
  }

  .rf-stepper {
    width: 136px;
  }

  .rf-duration {
    width: 120px;
  }

  .rf-stepper-btn {
    flex-basis: 42px;
    font-size: 17px;
  }

  /* La colonne d'intitulés de 140px ne laisse plus assez de place aux options
     à cette largeur : l'intitulé reprend sa ligne entière, la rangée d'options
     s'étale dessous. */
  .rf-form .rf-choice > .form-label {
    flex: 1 0 100%;
  }

  /* Une ligne par champ, sur toute la largeur de l'écran : le cadre s'étire et
     ses options s'y partagent la place à parts égales. */
  .rf-segmented {
    width: 100%;
    flex-wrap: nowrap;
  }

  /* min-width: 0 lève le plancher de 44px ET la largeur minimale implicite d'un
     élément flex — sans quoi les cinq moments déborderaient de l'écran au lieu
     de se répartir. La hauteur, elle, reste à hauteur de doigt. */
  .rf-segment {
    flex: 1 1 0;
    min-width: 0;
    min-height: 38px;
    padding: 6px 4px;
    font-size: var(--font-size-xs);
  }

  /* Filet de sécurité sur les écrans les plus étroits : un libellé qui ne tient
     pas dans sa part s'abrège au lieu de pousser la piste hors de l'écran. */
  .rf-segment-label {
    overflow: hidden;
    text-overflow: ellipsis;
  }

  /* Les capsules restent à leur largeur de contenu (elles se replient au fil du
     texte) mais gagnent la hauteur de doigt des autres contrôles du formulaire. */
  .tag-checkbox {
    min-height: 38px;
    padding: 8px 16px;
  }

  /* .preparations-actions passe déjà en colonne via components.css */
  .rf-form .preparations-actions .btn,
  .rf-actions .btn {
    width: 100%;
  }

  .rf-actions {
    flex-direction: column;
    align-items: stretch;
    gap: 10px;
    padding-top: 16px;
  }

  .rf-discard {
    flex-direction: column;
    align-items: stretch;
    gap: 10px;
  }
}

/* Tablette (768px – 1024px) */
@media (min-width: 768px) and (max-width: 1024px) {
  .rf-hero {
    padding: 16px 20px 18px;
  }

  .rf-hero-title {
    font-size: 22px;
  }

  .rf-body {
    padding: 16px 20px 32px;
  }
}

/* ============================================
   IMPORT IA — Panneau de revue des ingrédients
   ============================================ */

/* === Badge brouillon === */

.badge--draft {
  background: var(--color-accent-light);
  color: var(--color-accent-text);
  border: 1px solid var(--color-accent-border);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.05em;
  padding: 3px 8px;
  border-radius: 20px;
  vertical-align: middle;
  margin-left: 10px;
}

.badge--source {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  background: var(--color-info-light);
  color: var(--color-info-text);
  border: 1px solid var(--color-info-border);
  font-size: 11px;
  font-weight: 600;
  padding: 3px 8px;
  border-radius: 20px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* Badge de source cliquable (import par lien) : il ouvre la page d'origine.
   Le survol renforce la bordure et souligne le libellé — la pastille garde sa
   forme et sa couleur. Sélecteur doublé (.badge.badge--source-link) pour passer
   devant la règle globale a:not([class*="btn"]):hover, qui repeindrait le
   libellé en encre sombre. */

.badge.badge--source-link:hover,
.badge.badge--source-link:focus-visible {
  color: var(--color-info-text);
  border-color: var(--color-info);
  text-decoration: underline;
}

/* === Section publication === */

.rf-publish-section {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-top: 24px;
  padding: 20px 24px;
  background: var(--color-success-light);
  border: 1px solid #A3E635;
  border-radius: var(--radius-input);
}

.rf-publish-hint {
  font-size: var(--font-size-sm);
  color: var(--color-success-text);
  margin: 0;
}

@media (max-width: 640px) {
  .rf-publish-section {
    flex-direction: column;
    align-items: flex-start;
  }
}

/* === Panneau IA ===
   Le panneau se lit en colonnes, pas en boutons. Chaque ligne est une phrase :
   ce que l'IA a lu → l'ingrédient du catalogue visé → l'action. Les quatre
   colonnes sont communes à toutes les lignes et à leurs blocs imbriqués
   (suggestions floues, repli), qui reprennent la même sous-grille : la colonne
   d'action tombe donc toujours à la même abscisse, quel que soit l'état de la
   ligne. C'est cet alignement — et non des boîtes autour des boutons — qui
   regroupe l'information et rend la liste scannable.

   Deux variables suffisent à le tenir : la largeur de la colonne d'action et
   celle de la flèche. Aucune valeur en dur ailleurs, sinon l'alignement se
   défait au premier ajustement. */

.ai-panel {
  --ai-action-col: 148px;
  --ai-arrow-col: 16px;
  --ai-gap: 16px;

  margin-bottom: 24px;
  background: var(--color-bg-white);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-card);
  overflow: hidden;
}

/* En-tête : le titre, ce qu'il reste à faire, et la consigne. */

.ai-panel__header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px var(--ai-gap);
  padding: 16px 20px 14px;
  background: var(--color-bg-secondary);
  border-bottom: 1px solid var(--color-border);
}

.ai-panel__title {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  font-size: var(--font-size-lg);
  font-weight: 600;
  color: var(--color-ink);
}

/* L'ambre signe l'origine IA du panneau — le seul accent coloré de la charte,
   posé sur une icône décorative et non sur une commande. */
.ai-panel__title-icon {
  color: var(--color-accent-text);
}

.ai-panel__count {
  font-size: var(--font-size-sm);
  font-weight: 600;
  color: var(--color-ink-2);
  background: var(--color-bg-white);
  border: 1px solid var(--color-border);
  padding: 1px 9px;
  border-radius: 20px;
}

/* Les deux compteurs d'état, poussés à droite du titre : combien de lignes se
   règlent d'un clic, combien attendent une décision. Seul le second est ambré —
   c'est le seul endroit du panneau où le regard doit s'arrêter, et c'est la
   même couleur que le filet des lignes concernées. */
.ai-panel__states {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-left: auto;
}

.ai-panel__state {
  font-size: var(--font-size-xs);
  font-weight: 600;
  color: var(--color-ink-2);
  background: var(--color-bg-white);
  border: 1px solid var(--color-border);
  padding: 3px 10px;
  border-radius: 20px;
}

.ai-panel__state--todo {
  color: var(--color-accent-text);
  background: var(--color-accent-light);
  border-color: var(--color-accent-border);
}

.ai-panel__hint {
  flex-basis: 100%;
  margin: 0;
  font-size: var(--font-size-sm);
  color: var(--color-ink-3);
}

/* Intitulés de colonnes : ils nomment ce que la ligne compare avant qu'on en
   lise une seule. Ils partagent le gabarit de grille des lignes, sans quoi ils
   désigneraient des colonnes qui n'existent pas. */
.ai-panel__columns {
  display: grid;
  grid-template-columns: minmax(0, 1fr) var(--ai-arrow-col) minmax(0, 1fr) var(--ai-action-col);
  gap: var(--ai-gap);
  padding: 9px 20px;
  background: var(--color-bg-main);
  border-bottom: 1px solid var(--color-border);
  font-size: var(--font-size-xs);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-ink-3);
}

/* Placé explicitement : la colonne de la flèche n'a pas d'intitulé, et un
   élément vide pour la sauter serait un élément de plus à maintenir. */
.ai-panel__column-target {
  grid-column: 3;
}

/* === Lignes d'ingrédients IA === */

/* Correction : display:grid côté CSS auteur écrase l'attribut HTML hidden
   sur les éléments d'agent utilisateur. On force display:none ici.
   fuzzyOptions y manquait : « Aucun de ces résultats » masquait bien les
   suggestions côté JS, mais leur display les rendait quand même — la ligne
   affichait alors ses suggestions ET son repli, deux jeux d'actions
   concurrents sur une même décision. */
[data-ai-panel-target="fuzzyOptions"][hidden],
[data-ai-panel-target="noMatchFallback"][hidden],
[data-ai-panel-target="search"][hidden] {
  display: none !important;
}

.ai-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) var(--ai-arrow-col) minmax(0, 1fr) var(--ai-action-col);
  align-items: center;
  gap: 10px var(--ai-gap);
  padding: 12px 20px;
  border-bottom: 1px solid var(--color-border-light);
  min-height: 62px;
  transition: background var(--transition-fast);
}

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

/* Le survol n'a rien à dire sur une ligne déjà ajoutée, dont le fond porte
   justement l'information — et il l'emporterait en spécificité. */
.ai-row:not(.ai-row--done):hover {
  background: var(--color-bg-main);
}

/* Filet ambre : cette ligne attend une décision (suggestion à départager ou
   ingrédient absent du catalogue). Les lignes que l'IA a résolues restent
   muettes — l'ambre ne sert qu'à diriger le regard vers ce qui reste à faire.
   En ombre interne plutôt qu'en bordure : la ligne garde sa hauteur et son
   padding quel que soit son état. */
.ai-row--decide {
  box-shadow: inset 3px 0 0 var(--color-accent-deep);
}

/* Ligne ajoutée au formulaire. Déclarée après --decide, à spécificité égale :
   le vert remplace l'ambre sur une ligne qui portait les deux. */
.ai-row--done {
  background: var(--color-success-light);
  box-shadow: inset 3px 0 0 var(--color-success);
}

/* Colonne 1 : ce que l'IA a lu dans la recette */

.ai-row__detected {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 6px;
  min-width: 0;
}

/* Le nom détecté se replie plutôt qu'il ne se tronque : c'est lui qu'on compare
   à la proposition du catalogue, une ellipse en pleine comparaison ferait
   perdre le seul mot qui tranche. */
.ai-row__name {
  font-size: var(--font-size-base);
  font-weight: 600;
  color: var(--color-ink);
  overflow-wrap: anywhere;
}

/* La quantité en pastille, pour qu'elle ne se lise pas comme la fin du nom :
   « courgettes 600 g » n'est pas un ingrédient. */
.ai-row__qty {
  flex-shrink: 0;
  font-size: var(--font-size-sm);
  font-weight: 500;
  color: var(--color-ink-2);
  background: var(--color-bg-tertiary);
  padding: 2px 9px;
  border-radius: 20px;
  white-space: nowrap;
}

/* Colonne 2 : le sens de lecture de la ligne, rien de plus */

.ai-row__arrow {
  display: flex;
  color: var(--color-ink-4);
}

/* Colonnes 3 et 4 : la cible et l'action.
   Réunies sous un même parent parce que le contrôleur Stimulus y substitue le
   badge « Ajouté » d'un seul remplacement. Sa sous-grille reprend exactement
   les deux dernières colonnes de la ligne. */

.ai-row__right {
  grid-column: 3 / -1;
  display: grid;
  grid-template-columns: minmax(0, 1fr) var(--ai-action-col);
  align-items: center;
  gap: 8px var(--ai-gap);
}

/* Un seul bouton plein par décision, à la largeur de sa colonne : d'une ligne à
   l'autre ils forment une colonne franche au lieu d'une mosaïque de tailles. */
.ai-row__right .btn {
  width: 100%;
}

/* L'icône du bouton d'action se tait tant que le libellé est là : en desktop la
   colonne est large, « Ajouter » se lit tout seul et un pictogramme de plus ne
   ferait qu'ajouter du bruit. Elle prend le relais sous 640 px, où la colonne
   se réduit à une cible carrée (cf. la media query en fin de section). */
.ai-row__action-icon {
  display: none;
}

/* La cible et son échappatoire. « Choisir un autre », « Aucun de ces
   résultats » et « Créer » changent l'ingrédient visé, pas l'action à mener :
   ils vivent donc sous la cible et jamais dans la colonne des boutons. */
.ai-row__target {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  min-width: 0;
}

.ai-row__match,
.ai-row__fuzzy-name {
  font-size: var(--font-size-base);
  color: var(--color-ink);
  overflow-wrap: anywhere;
}

/* Unité de base de l'ingrédient du catalogue, collée à son nom (match exact,
   suggestion floue, résultat de recherche). C'est elle qu'on compare à l'unité
   détectée avant de confirmer : « 2 tranches » face à « Jambon blanc (g) »
   prévient les 2 g de jambon que la reprise brute du nombre produirait. */
.ai-row__unit {
  font-size: var(--font-size-xs);
  color: var(--color-ink-3);
  white-space: nowrap;
}

/* Aucune conversion possible vers cette unité : la quantité détectée partirait
   telle quelle. L'unité passe en alerte et porte l'explication en title. */
.ai-row__unit--mismatch {
  color: var(--color-warning-text);
  background: var(--color-warning-light);
  padding: 1px 6px;
  border-radius: 20px;
  cursor: help;
}

/* Conversion obtenue par une densité que l'IA a estimée : la quantité est juste
   à cette estimation près. Ce n'est pas une alerte — le nombre est exploitable —
   mais ça se signale, en info et non en avertissement, avec la même explication
   en title. Le tilde dit « à peu près » sans occuper de place. */
.ai-row__unit--estimated {
  color: var(--color-info-text);
  background: var(--color-info-light);
  padding: 1px 6px;
  border-radius: 20px;
  cursor: help;
}

.ai-row__unit--estimated::before {
  content: "≈ ";
}

/* Suggestions à départager, et actions de repli : deux blocs qui occupent les
   deux colonnes de droite et y refont la même sous-grille. Chaque suggestion
   est une sous-ligne « nom + bouton » — l'alignement remplace la mini-carte
   qui entourait autrefois chaque bouton. */

.ai-row__fuzzy,
.ai-row__no-match-fallback {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: minmax(0, 1fr) var(--ai-action-col);
  align-items: center;
  gap: 8px var(--ai-gap);
}

.ai-row__fuzzy-label {
  grid-column: 1 / -1;
  font-size: var(--font-size-xs);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-ink-3);
}

.ai-row__no-match {
  font-size: var(--font-size-base);
  color: var(--color-ink-3);
}

/* Les échappatoires en texte : une seule et même mécanique pour les trois.
   Ce ne sont pas des boutons — les avoir traitées comme tels, dans trois
   variantes différentes, est ce qui rendait la colonne de droite illisible. */
.ai-row__alt-btn {
  justify-self: start;
  padding: 2px 0;
  background: none;
  border: none;
  font-family: inherit;
  font-size: var(--font-size-sm);
  color: var(--color-ink-3);
  text-decoration: underline;
  text-decoration-color: var(--color-border-strong);
  text-underline-offset: 2px;
  text-align: left;
  cursor: pointer;
  transition: color var(--transition-fast);
}

.ai-row__alt-btn:hover {
  color: var(--color-ink);
  text-decoration-color: currentColor;
}

/* Recherche manuelle dans le catalogue : dépliée sous la ligne, sur toute
   sa largeur (la ligne est une grille à quatre colonnes). */

.ai-row__search {
  grid-column: 1 / -1;
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 4px;
  padding-top: 12px;
  border-top: 1px dashed var(--color-border);
}

/* Champ + croix de fermeture sur une même ligne */

.ai-row__search-head {
  display: flex;
  align-items: center;
  gap: 8px;
}

/* 16 px et pas moins : sous cette taille, iOS zoome sur le champ à la mise au
   point et laisse la page décalée. */
.ai-row__search-input {
  flex: 1;
  min-width: 0;
  font-size: var(--font-size-base);
}

.ai-row__search-close {
  flex-shrink: 0;
}

.ai-row__search-results {
  display: grid;
  gap: 4px;
  max-height: 220px;
  overflow-y: auto;
}

/* Chaque résultat : une cible pleine largeur, alignée à gauche comme une liste */

.ai-row__search-item {
  text-align: left;
  padding: 10px 12px;
  font-family: inherit;
  font-size: var(--font-size-base);
  color: var(--color-ink);
  background: var(--color-bg-white);
  border: 1px solid var(--color-border-light);
  border-radius: var(--radius-button);
  cursor: pointer;
  transition: background var(--transition-fast), border-color var(--transition-fast);
}

.ai-row__search-item:hover {
  background: var(--color-bg-secondary);
  border-color: var(--color-border-strong);
}

.ai-row__search-empty {
  margin: 0;
  padding: 6px 2px;
  font-size: var(--font-size-sm);
  font-style: italic;
  color: var(--color-ink-3);
}

/* Badge ✓ Ajouté — posé par le contrôleur à la place de la cible et du bouton.
   Fond blanc et non vert : la ligne entière ayant viré au vert, un badge vert
   clair s'y dissoudrait. */

.ai-row__done-badge {
  grid-column: 1 / -1;
  justify-self: start;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--font-size-sm);
  font-weight: 600;
  color: var(--color-success-text);
  background: var(--color-bg-white);
  border: 1px solid var(--color-success-border);
  padding: 5px 14px;
  border-radius: 20px;
}

/* Ligne posée avec une quantité que la conversion n'a pas su traduire : elle
   est bien ajoutée (d'où le ✓), mais le nombre reste à revoir dans le
   formulaire — la ligne disparaît ensuite, le badge est le dernier rappel. */
.ai-row__done-badge--warn {
  color: var(--color-warning-text);
  border-color: var(--color-warning-border);
  cursor: help;
}

/* Quantité obtenue par une densité estimée : la ligne est réglée, mais le nombre
   n'est juste qu'à l'estimation près. Même réserve que le badge d'unité, même
   registre d'information — et non d'avertissement. */
.ai-row__done-badge--estimated {
  color: var(--color-info-text);
  border-color: var(--color-info-border);
  cursor: help;
}

/* === Panneau IA — mobile ===
   Sous 640 px la ligne se replie : le détecté passe au-dessus de la cible, les
   intitulés de colonnes et la flèche n'ont plus rien à désigner. Mais le
   couple cible ↔ action, lui, ne se déplie pas — c'est le seul appariement qui
   compte au doigt, et l'empiler donnait une liste où chaque décision occupait
   140 px pour deux mots utiles.

   Trois corrections, dans cet ordre d'importance :
     1. la colonne d'action tombe à une cible carrée de 44 px et le bouton n'y
        garde que son icône — neuf « Ajouter » pleine largeur formaient un mur
        de noir qui écrasait les noms d'ingrédients, seuls à devoir se lire ;
     2. les échappatoires deviennent des pastilles bordées : en lien souligné de
        14 px coincé sous un bouton pleine largeur, « Choisir un autre » n'était
        ni repérable ni atteignable ;
     3. un « ↳ » relie les deux noms, que la flèche ne relie plus.

   Une seule variable porte le point 1 : la colonne d'action. Les trois
   sous-grilles (cible + action, suggestions, repli) la reprennent d'elles-mêmes
   et n'ont donc rien à redéclarer ici — c'est ce qui les avait fait dériver
   vers l'empilement pleine largeur. */

@media (max-width: 640px) {
  .ai-panel {
    --ai-action-col: var(--min-touch-target);
    --ai-gap: 10px;
  }

  .ai-panel__columns {
    display: none;
  }

  /* Marges latérales resserrées : 20 px de chaque côté, c'est un dixième de
     l'écran perdu pour le nom de l'ingrédient. */
  .ai-panel__header {
    padding: 14px 14px 12px;
  }

  /* Les compteurs d'état reviennent sous le titre, alignés à gauche comme le
     reste de la colonne — poussés à droite, ils flottaient seuls sur leur ligne. */
  .ai-panel__states {
    margin-left: 0;
  }

  .ai-row {
    grid-template-columns: minmax(0, 1fr);
    gap: 8px;
    padding: 12px 14px;
  }

  .ai-row__arrow {
    display: none;
  }

  .ai-row__right {
    grid-column: 1 / -1;
  }

  /* Ce que l'IA a lu, puis ce que le catalogue propose. Sans ce repère les deux
     noms s'empilent sans dire lequel remplace lequel. La seconde déclaration
     retire le glyphe du texte annoncé par les lecteurs d'écran ; les moteurs qui
     ignorent cette syntaxe gardent la première, donc le repère visuel. */
  .ai-row__match::before,
  .ai-row__no-match::before {
    content: "↳ ";
    content: "↳ " / "";
    color: var(--color-ink-4);
  }

  /* Le libellé cède la place à l'icône. Il reste dans le DOM par principe, mais
     ce n'est pas lui qui porte le nom accessible : chaque bouton a son
     aria-label, qui nomme l'ingrédient (cf. recipes/_ai_action_button). */
  .ai-row__action-icon {
    display: block;
  }

  .ai-row__action-label {
    display: none;
  }

  .ai-row__right .btn {
    min-height: var(--min-touch-target);
    padding: 0;
  }

  /* Échappatoires : un vrai contrôle, à la hauteur de .btn-md, et non un lien
     souligné de 14 px. Le fond blanc les distingue de la ligne sans les faire
     concurrencer l'action principale, qui reste seule en anthracite. */
  .ai-row__alt-btn {
    min-height: 38px;
    padding: 0 12px;
    color: var(--color-ink-2);
    background: var(--color-bg-white);
    border: var(--border-thin);
    border-radius: var(--radius-button);
    text-decoration: none;
  }

  .ai-row__alt-btn:hover {
    border-color: var(--color-border-strong);
  }

  /* La pastille ne colle plus au nom : 2 px suffisaient à un lien, pas à un
     contrôle bordé. */
  .ai-row__target {
    gap: 6px;
  }

  /* Les résultats de recherche méritent la même cible tactile que le reste. */
  .ai-row__search-item {
    min-height: var(--min-touch-target);
    padding: 12px;
  }
}

/* ============================================
   PAGE BROUILLONS IA — /recipe_drafts
   ============================================ */

/* Barre du haut : compteur + bouton Importer */

.draft-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 20px;
}

.draft-toolbar__count {
  font-size: 13px;
  color: var(--color-ink-3);
  margin: 0;
}

/* Côté droit de la barre : tri puis import. Le wrap évite que les deux se
   chevauchent sur les écrans étroits. */

/* align-items: stretch (et non center) : le sélecteur de tri prend la hauteur
   de la ligne, donc celle du bouton d'import voisin — le plus haut des deux.
   Aucune hauteur codée en dur à retoucher si le bouton change de taille. */

.draft-toolbar__actions {
  display: flex;
  align-items: stretch;
  flex-wrap: wrap;
  gap: 10px 14px;
}

/* Tri de la liste — formulaire sans bouton d'envoi (filter-form le soumet au
   changement du sélecteur), donc libellé et sélecteur suffisent. Il relaie la
   hauteur de la ligne à ses deux enfants. */

.draft-sort {
  display: flex;
  align-items: stretch;
  gap: 8px;
  margin: 0;
}

/* Étiré comme le sélecteur : son texte se recentre lui-même. */
.draft-sort__label {
  display: flex;
  align-items: center;
  font-size: 12px;
  color: var(--color-ink-3);
  white-space: nowrap;
}

/* .filter-select passe en pleine largeur sous 768px — c'est la règle des
   filtres du catalogue d'ingrédients, qui s'empilent en colonne. Ici le
   sélecteur reste en ligne avec son libellé : il se dimensionne sur son
   contenu, et c'est la barre d'outils qui gère l'empilement. */
@media (max-width: 767px) {
  .draft-sort .filter-select {
    width: auto;
    min-width: 0;
  }
}

/* État vide */

.draft-empty {
  text-align: center;
  padding: 64px 24px;
}

.draft-empty__text {
  font-size: 15px;
  font-weight: 500;
  color: var(--color-ink-2);
  margin: 0 0 6px 0;
}

.draft-empty__hint {
  font-size: 13px;
  color: var(--color-ink-3);
  max-width: 400px;
  margin: 0 auto;
}

/* Liste de cards */

.draft-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.draft-card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  padding: 16px 24px;
  background: var(--color-bg-white);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-card);
  transition: border-color 0.15s, box-shadow 0.15s;
}

.draft-card:hover {
  border-color: var(--color-border-strong);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}

/* Partie gauche : nom + méta */

.draft-card__main {
  flex: 1;
  min-width: 0;
}

/* Nom sur 2 lignes maximum : un titre long respire au lieu d'être coupé net,
   l'ellipsis du line-clamp ne servant que de garde-fou au-delà. */

.draft-card__name {
  font-size: 15px;
  font-weight: 600;
  color: var(--color-ink);
  margin: 0 0 5px 0;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
}

.draft-card__meta {
  display: flex;
  align-items: center;
  gap: 6px;
}

.draft-card__sep {
  color: var(--color-ink-4);
  font-size: 12px;
}

.draft-card__date {
  font-size: 12px;
  color: var(--color-ink-3);
}

/* Partie droite : actions.
   Grille à colonnes égales plutôt que flex : les deux boutons font la même
   largeur quel que soit leur libellé (« Compléter » ou « Valider » selon la
   complétude du brouillon), donc la colonne d'actions ne bouge plus d'une carte
   à l'autre. La largeur commune se cale sur le plus long des deux libellés en
   desktop, et sur la moitié de la carte en mobile (.draft-card__actions passe
   alors en width: 100%). */

.draft-card__actions {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}

/* button_to génère un <form> — on neutralise ses marges */
.draft-card__actions form {
  margin: 0;
}

/* C'est le <form> de button_to qui est la colonne de la grille : son bouton
   doit l'occuper entièrement pour faire la même largeur que le lien voisin. */
.draft-card__actions .btn {
  width: 100%;
}

/* Mini-photo (ou placeholder si aucune photo importée) */

.draft-card__thumb {
  flex-shrink: 0;
}

.draft-card__thumb-img {
  display: block;
  width: 56px;
  height: 56px;
  object-fit: cover;
  border-radius: var(--radius-card-compact);
  border: 1px solid var(--color-border);
}

.draft-card__thumb-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  border-radius: var(--radius-card-compact);
  border: 1px dashed var(--color-border-strong);
  background: var(--color-bg-secondary);
  color: var(--color-ink-4);
}

/* Priorisation visuelle : liseré ambre à compléter, vert prêt à valider.
   box-shadow inset ⇒ pas de décalage de mise en page et respect du border-radius. */

.draft-card--todo {
  box-shadow: inset 3px 0 0 var(--color-warning);
}

.draft-card--ready {
  box-shadow: inset 3px 0 0 var(--color-success);
}

.draft-card--todo:hover {
  box-shadow: inset 3px 0 0 var(--color-warning), 0 2px 8px rgba(0, 0, 0, 0.06);
}

.draft-card--ready:hover {
  box-shadow: inset 3px 0 0 var(--color-success), 0 2px 8px rgba(0, 0, 0, 0.06);
}

/* Statut de complétude */

.draft-status {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  margin: 8px 0 0 0;
  font-size: 12px;
  font-weight: 600;
}

.draft-status--ready {
  color: var(--color-success-text);
}

.draft-status__label {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  color: var(--color-warning-text);
}

/* Liste des champs manquants (chips) */

.draft-missing {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.draft-missing__item {
  font-size: 11px;
  font-weight: 600;
  padding: 2px 8px;
  border-radius: 20px;
  background: var(--color-warning-light);
  color: var(--color-warning-text);
  border: 1px solid var(--color-warning-border);
}

/* Mobile */

@media (max-width: 640px) {
  .draft-toolbar {
    flex-direction: column;
    align-items: flex-start;
    gap: 12px;
  }

  /* La barre passe en colonne : tri et import se partagent la ligne du bas. */
  .draft-toolbar__actions {
    width: 100%;
    justify-content: space-between;
  }

  .draft-card {
    flex-direction: column;
    align-items: flex-start;
    gap: 12px;
  }

  .draft-card__actions {
    width: 100%;
  }
}

/* ============================================
   IMPORT IA — Page recipe_imports/new
   ============================================ */

.import-wrapper {
  max-width: 560px;
  margin: 0 auto;
  padding: 0 0 3rem;
}

/* === Onglets source === */

.import-tabs {
  display: flex;
  gap: 0;
  border: 1.5px solid var(--color-border);
  border-radius: var(--radius-input);
  overflow: hidden;
  margin-bottom: 2rem;
}

.import-tab {
  flex: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 10px 16px;
  background: var(--color-bg-white);
  border: none;
  cursor: pointer;
  font-size: 14px;
  font-weight: 500;
  color: var(--color-ink-3);
  transition: background 0.15s, color 0.15s;
  letter-spacing: 0.01em;
}

.import-tab + .import-tab {
  border-left: 1.5px solid var(--color-border);
}

.import-tab:hover {
  background: var(--color-bg-secondary);
  color: var(--color-ink);
}

.import-tab--active {
  background: var(--color-primary);
  color: var(--color-bg-white);
  font-weight: 600;
}

.import-tab--active:hover {
  background: var(--color-primary);
  color: var(--color-bg-white);
}

/* === Sections URL / Photo === */

.import-section {
  margin-bottom: 1.5rem;
}

.import-hint {
  margin-top: 8px;
  font-size: 13px;
  color: var(--color-ink-3);
  line-height: 1.5;
}

.import-hint strong {
  color: var(--color-ink-2);
}

/* Indice de geste (dépôt, collage) : un cran plus discret que l'aide format. */
.import-hint--gesture {
  margin-top: 4px;
  font-size: var(--font-size-xs);
  color: var(--color-ink-4);
}

/* === Zone d'upload photo === */

.import-file-zone {
  position: relative;
  border: 2px dashed var(--color-border);
  border-radius: var(--radius-input);
  background: var(--color-bg-secondary);
  transition: border-color 0.2s, background 0.2s;
  cursor: pointer;
}

/* Le survol d'un fichier glissé (classe posée par import_source_controller)
   reprend la mise en avant du survol souris : pendant un drag, :hover ne
   s'applique pas. */
.import-file-zone:hover,
.import-file-zone:focus-within,
.import-file-zone.is-dragging {
  border-color: var(--color-primary);
  background: var(--color-bg-white);
}

.import-file-input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
  z-index: 1;
}

.import-file-label {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 2.5rem 1rem;
  cursor: pointer;
}

.import-file-label__icon {
  font-size: 2rem;
  line-height: 1;
}

.import-file-label__text {
  font-size: 14px;
  font-weight: 500;
  color: var(--color-ink-2);
}

/* === Bouton et notice === */

.import-actions {
  margin-top: 2rem;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.import-submit-btn {
  width: 100%;
  justify-content: center;
}

.import-notice {
  font-size: 13px;
  color: var(--color-ink-3);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  margin: 0;
}

/* === Aperçu de la photo sélectionnée === */

.import-file-preview {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 12px;
  border: 1.5px solid var(--color-border);
  border-radius: var(--radius-input);
  background: var(--color-bg-white);
}

.import-file-preview__img {
  width: 72px;
  height: 72px;
  object-fit: cover;
  border-radius: var(--radius-card-compact);
  flex-shrink: 0;
}

.import-file-preview__info {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
  min-width: 0;
}

.import-file-preview__name {
  font-size: 13px;
  font-weight: 500;
  color: var(--color-ink-2);
  word-break: break-word;
}

/* Poids du fichier réellement envoyé : une information de service, discrète. */
.import-file-preview__size {
  font-size: var(--font-size-xs);
  color: var(--color-ink-4);
}

.import-file-preview__change {
  background: none;
  border: none;
  padding: 0;
  font-size: 13px;
  font-weight: 600;
  color: var(--color-primary);
  cursor: pointer;
  text-decoration: underline;
}

.import-file-preview__change:hover {
  color: var(--color-primary-hover);
}

/* === Réassurance et erreur === */

.import-reassure {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  padding: 10px 14px;
  font-size: 13px;
  color: var(--color-success-text);
  background: var(--color-success-light);
  border-radius: var(--radius-input);
}

.import-reassure .svg-icon {
  flex-shrink: 0;
}

.import-error {
  margin: 0;
  padding: 10px 14px;
  font-size: 13px;
  font-weight: 500;
  color: var(--color-danger-text);
  background: var(--color-danger-light);
  border: 1px solid var(--color-danger-border);
  border-radius: var(--radius-input);
}

/* === État de chargement du bouton === */

.import-submit-btn {
  gap: 8px;
}

.import-submit-btn:disabled {
  opacity: 0.85;
  cursor: progress;
}

.import-submit-spinner {
  width: 16px;
  height: 16px;
  border: 2px solid rgba(255, 255, 255, 0.45);
  border-top-color: var(--color-bg-white);
  border-radius: 50%;
  animation: import-spin 0.7s linear infinite;
  flex-shrink: 0;
}

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

/* === Page d'attente d'un import === */

/* .rf-body est en fit-content : sans width 100%, ce bloc rétrécirait la page. */
.import-waiting {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 1.25rem;
  padding: 3rem 0;
}

/* Même animation que le spinner du bouton, mais posé sur fond clair : les
   couleurs viennent donc de la bordure et de la teinte principale. */
.import-waiting__spinner {
  width: 40px;
  height: 40px;
  border: 3px solid var(--color-border);
  border-top-color: var(--color-primary);
  border-radius: 50%;
  animation: import-spin 0.9s linear infinite;
}

.import-waiting__step {
  font-size: 17px;
  font-weight: 600;
  color: var(--color-ink);
  margin: 0;
}

.import-waiting__hint {
  font-size: 14px;
  color: var(--color-ink-3);
  max-width: 42ch;
  margin: 0;
}

/* Une rotation continue peut gêner : on la ralentit sans la supprimer, le
   mouvement restant le seul signe que le travail est en cours. */
@media (prefers-reduced-motion: reduce) {
  .import-waiting__spinner {
    animation-duration: 2.4s;
  }
}

/* Mobile */

@media (max-width: 640px) {
  .import-wrapper {
    padding: 0 0 2rem;
  }

  .import-tab {
    font-size: 13px;
    padding: 10px 10px;
  }
}
/* ============================================
   TAGS - Styles spécifiques
   ============================================
   Gestion des tags avec édition inline
   ============================================ */

/* === Container principal === */
.tags-container {
  max-width: 1400px;
  min-width: 30%;
  margin: 0 auto;
  padding: 2rem 1rem;
}

.tags-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 1.25rem;
}

/* La taille suit l'échelle fluide de la règle de base h1 (global.css). */
.tags-header h1 {
  font-weight: 600;
  color: var(--color-ink);
  margin: 0;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}

/* === Barre de création rapide d'un tag === */
.tag-create {
  background: var(--color-bg-white);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-md);
  padding: 1rem 1.5rem;
  margin-bottom: 2rem;
}

.tag-create-row {
  display: flex;
  gap: 0.75rem;
  align-items: center;
  flex-wrap: wrap;
}

.tag-create-field {
  flex: 1 1 220px;
  min-width: 0;
}

/* Le champ occupe toute la largeur de sa colonne (le conteneur n'est pas flex) */
.tag-create .tag-inline-input {
  width: 100%;
}

.tag-create-select {
  flex: 0 1 200px;
  padding: 0.625rem 0.875rem;
  border: var(--border-thin);
  border-radius: var(--radius-input);
  font-size: var(--font-size-base);
  color: var(--color-ink);
  background: var(--color-bg-white);
  cursor: pointer;
  transition: var(--transition-normal);
}

.tag-create-select:focus {
  outline: none;
  border-color: var(--color-primary);
  box-shadow: 0 0 0 2px var(--color-primary-light);
}

/* Alignement icône + libellé hérité de .btn ; on ne pilote que le flex. */
.tag-create-submit {
  flex: 0 0 auto;
}

/* Champ en erreur (nom dupliqué, trop court, etc.) */
.tag-create-field.has-error .tag-inline-input {
  border-color: var(--color-danger);
}

.tag-create .field-errors {
  margin-top: 0.5rem;
}

/* === Liste des groupes (accordéons) === */
.tags-container turbo-frame {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

/* === Groupe de tags = accordéon (un par type) === */
.tag-group-header {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  width: 100%;
  background: none;
  border: none;
  border-left: 3px solid var(--color-primary);
  border-bottom: var(--border-thin);
  cursor: pointer;
  padding: 0.5rem 0.5rem 0.5rem 0.875rem;
  margin-bottom: 0.5rem;
  text-align: left;
  transition: opacity var(--transition-fast);
}

.tag-group-header:hover {
  opacity: 0.75;
}

.tag-group-title {
  font-size: var(--font-size-base);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-ink);
}

/* Compteur de tags du groupe (pastille) */
.tag-group-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.5rem;
  height: 1.5rem;
  padding: 0 0.5rem;
  border-radius: 999px;
  background: var(--color-bg-secondary);
  color: var(--color-ink-3);
  font-size: var(--font-size-sm);
  font-weight: 600;
}

.tag-group-chevron {
  margin-left: auto;
  display: flex;
  align-items: center;
  color: var(--color-ink-4);
}

.tag-group-chevron::after {
  content: "›";
  display: inline-block;
  transform: rotate(90deg);
  transition: transform var(--transition-fast);
  font-size: 1.25rem;
  line-height: 1;
}

/* Chevron replié — piloté par l'état ARIA générique posé par le controller */
.tag-group-header[aria-expanded="false"] .tag-group-chevron::after {
  transform: rotate(0deg);
}

/* === Corps de l'accordéon : liste des tags === */
.tag-group-body {
  list-style: none;
  margin: 0;
  padding: 0 0 0.75rem 0.875rem;
  display: flex;
  flex-direction: column;
  gap: 0.375rem;
}

.tag-group-body[hidden] {
  display: none;
}

.tag-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.5rem 0.75rem;
  background: var(--color-bg-white);
  border: var(--border-thin);
  border-radius: var(--radius-card-compact);
}

.tag-row.editing {
  border-color: var(--color-primary);
  background: var(--color-bg-secondary);
}

.tag-row-name {
  font-weight: 600;
  color: var(--color-ink);
}

.tag-row-usage {
  color: var(--color-ink-3);
  font-size: var(--font-size-sm);
}

.text-muted {
  color: var(--color-ink-3);
  font-style: italic;
  font-size: var(--font-size-sm);
}

/* Actions : boutons icône à droite, masqués pendant l'édition inline */
.tag-row-actions {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  margin-left: auto;
  flex-shrink: 0;
}

.tag-row.editing .tag-row-actions {
  display: none;
}

/* === Édition inline === */

.tag-display {
  display: flex;
  align-items: baseline;
  gap: 0.625rem;
  flex: 1;
  min-width: 0;
}

.tag-edit-form {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex: 1;
  min-width: 0;
}

.tag-inline-input {
  flex: 1;
  min-width: 0;
  padding: 0.5rem 0.75rem;
  border: var(--border-thin);
  border-radius: var(--radius-input);
  font-size: var(--font-size-base);
  font-weight: 600;
  color: var(--color-ink);
  background: var(--color-bg-white);
  transition: var(--transition-normal);
}

.tag-inline-input:focus {
  outline: none;
  border-color: var(--color-primary);
  box-shadow: 0 0 0 2px var(--color-primary-light);
}

.btn-icon-save,
.btn-icon-cancel {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border: none;
  border-radius: var(--radius-button);
  font-size: var(--font-size-lg);
  font-weight: bold;
  cursor: pointer;
  transition: var(--transition-normal);
  box-shadow: var(--shadow-sm);
}

/* Action principale (enregistrer) = anthracite, cohérent avec .btn-primary.
  L'ambre est réservé au seul CTA accent de l'app. */
.btn-icon-save {
  background: var(--color-primary);
  color: var(--color-bg-white);
}

.btn-icon-save:hover {
  background: var(--color-primary-hover);
  transform: translateY(-1px);
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.15);
}

.btn-icon-cancel {
  background: var(--color-border-light);
  color: var(--color-ink-3);
}

.btn-icon-cancel:hover {
  /* Lavis d'encre à 8 % plutôt qu'un cran opaque de la rampe : les pas entre
     surfaces voisines valent ~0,025 en luminance, soit ~1,09:1 — trop fin pour
     signaler un état. Le lavis donne 1,17:1 contre la surface qu'il recouvre,
     ici --color-bg-secondary porté par .tag-row.editing, seul contexte où ce
     bouton est rendu. */
  background: var(--color-primary-light);
  transform: translateY(-1px);
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.15);
}

/* === Formulaire d'édition === */

.tag-form-container {
  max-width: 600px;
  margin: 0 auto;
  background: var(--color-bg-white);
  padding: 2rem;
  border-radius: var(--radius-input);
  box-shadow: var(--shadow-sm);
}

.tag-form .form-group {
  margin-bottom: 1.5rem;
}

.tag-form .form-label {
  display: block;
  font-weight: 600;
  color: var(--color-ink);
  margin-bottom: 0.5rem;
}

.tag-form .form-input {
  width: 100%;
  padding: 0.75rem;
  border: 2px solid var(--color-border);
  border-radius: 4px;
  font-size: var(--font-size-base);
  transition: border-color 0.2s;
}

.tag-form .form-input:focus {
  outline: none;
  border-color: var(--color-primary);
}

.tag-form .form-actions {
  display: flex;
  gap: 1rem;
  margin-top: 2rem;
}

/* === Responsive === */

@media (max-width: 767px) {
  .tags-container {
    padding: 1rem 0.75rem;
  }

  .tags-header {
    flex-direction: column;
    gap: 1rem;
    align-items: stretch;
  }

  .tags-header h1 {
    justify-content: center;
  }

  /* Barre de création : champs empilés pleine largeur sur mobile */
  .tag-create-row {
    flex-direction: column;
    align-items: stretch;
  }

  .tag-create-select,
  .tag-create-submit {
    flex: 1 1 auto;
  }

  .tag-create-submit {
    justify-content: center;
  }

  /* Évite le zoom sur iOS lors du focus des champs texte */
  .tag-inline-input {
    font-size: 16px;
  }

  /* Barre de création : padding horizontal réduit sur mobile */
  .tag-create {
    padding: 1rem;
  }

  /* En-tête d'accordéon : hauteur d'ouverture/fermeture confortable au pouce */
  .tag-group-header {
    padding-top: 0.75rem;
    padding-bottom: 0.75rem;
  }

  /* Boutons Valider/Annuler de l'édition inline : cible 44px */
  .btn-icon-save,
  .btn-icon-cancel {
    width: var(--min-touch-target);
    height: var(--min-touch-target);
  }

  /* Select de création à hauteur tactile */
  .tag-create-select {
    min-height: var(--min-touch-target);
  }

  .tag-form-container {
    padding: 1rem;
  }

  .tag-form .form-actions {
    flex-direction: column;
  }
}
/* ============================================
   USERS - Gestion admin des utilisateurs
   ============================================ */

.users-container {
  width: min(1180px, calc(100% - 2rem));
  margin: 0 auto;
  padding: var(--spacing-2xl) 0;
}

.users-container--narrow {
  max-width: 900px;
}

.users-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: var(--spacing-lg);
  margin-bottom: var(--spacing-xl);
}

.users-kicker {
  display: inline-block;
  margin-bottom: var(--spacing-xs);
  color: var(--color-ink-3);
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.16em;
}

/* La taille suit l'échelle fluide de la règle de base h1 (global.css). */
.users-title,
.users-form-title h1 {
  margin: 0;
  color: var(--color-ink);
  line-height: 1.15;
}

.users-subtitle,
.users-form-title p {
  margin: var(--spacing-sm) 0 0;
  color: var(--color-ink-3);
}

.users-count {
  display: inline-flex;
  align-items: center;
  min-height: 36px;
  padding: 0 var(--spacing-md);
  border: var(--border-thin);
  border-radius: var(--radius-badge);
  background: var(--color-bg-white);
  color: var(--color-ink-2);
  font-size: var(--font-size-sm);
  font-weight: 600;
}

.users-table-wrap,
.users-form-card {
  background: var(--color-bg-white);
  border: var(--border-thin);
  border-radius: var(--radius-card);
  overflow: hidden;
}

.users-table {
  width: 100%;
  border-collapse: collapse;
}

.users-table th,
.users-table td {
  padding: var(--spacing-md);
  border-bottom: var(--border-thin);
  text-align: left;
  vertical-align: middle;
}

.users-table th {
  color: var(--color-ink-3);
  background: var(--color-bg-secondary);
  font-size: var(--font-size-xs);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.users-table tbody tr:last-child td {
  border-bottom: 0;
}

.users-identity-cell,
.users-form-header {
  display: flex;
  align-items: center;
  gap: var(--spacing-md);
}

.users-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  flex: 0 0 44px;
  border-radius: var(--radius-card-compact);
  background: var(--color-primary);
  color: var(--color-bg-white);
  font-size: var(--font-size-sm);
  font-weight: 700;
}

.users-avatar--large {
  width: 56px;
  height: 56px;
  flex-basis: 56px;
  font-size: var(--font-size-lg);
}

.users-identity,
.users-preferences {
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
}

.users-identity strong {
  color: var(--color-ink);
}

.users-identity span,
.users-identity small,
.users-preferences small,
.users-table time {
  color: var(--color-ink-3);
  font-size: var(--font-size-sm);
}

.users-warning {
  color: var(--color-warning-text);
  font-size: var(--font-size-xs);
  font-weight: 600;
}

.users-menu-count {
  color: var(--color-ink);
  font-weight: 600;
}

.badge-dark {
  background: var(--color-primary);
  color: var(--color-bg-white);
}

.users-actions__inner {
  display: flex;
  gap: var(--spacing-xs);
}

.users-actions__inner form {
  display: inline-flex;
}

.icon-btn-disabled {
  opacity: 0.35;
  cursor: not-allowed;
}

.users-form-card {
  padding: var(--spacing-xl);
}

.users-form-header {
  margin-bottom: var(--spacing-xl);
  padding-bottom: var(--spacing-lg);
  border-bottom: var(--border-thin);
}

.users-form-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--spacing-lg);
}

.users-form-flags {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--spacing-md);
  margin-top: var(--spacing-xl);
}

.users-checkbox {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
  min-height: var(--min-touch-target);
  padding: var(--spacing-md);
  border: var(--border-thin);
  border-radius: var(--radius-card-compact);
  color: var(--color-ink-2);
  background: var(--color-bg-main);
  cursor: pointer;
}

.users-checkbox input {
  width: 18px;
  height: 18px;
  accent-color: var(--color-primary);
}

.users-form-actions {
  display: flex;
  gap: var(--spacing-md);
  margin-top: var(--spacing-xl);
}

@media (max-width: 767px) {
  .users-container {
    width: min(100% - 1rem, 1180px);
    padding: var(--spacing-lg) 0;
  }

  .users-header,
  .users-form-header,
  .users-form-actions {
    align-items: stretch;
    flex-direction: column;
  }

  /* L'en-tête passé en colonne étirerait le compteur sur toute la largeur :
     il doit rester une pastille calée à gauche. */
  .users-header__meta {
    align-self: flex-start;
  }

  /* === Le tableau se replie en une pile de cartes ===
     Le cadre quitte le conteneur pour aller sur chaque ligne, comme sur la
     table des ingrédients. */
  .users-table-wrap {
    overflow: visible;
    border: 0;
    background: transparent;
  }

  .users-table,
  .users-table tbody,
  .users-table td {
    display: block;
  }

  .users-table thead {
    display: none;
  }

  /* Ligne en flex et non en grille : trois lignes visuelles (identité + rôle,
     préférences, méta) qui doivent se dimensionner indépendamment. En grille,
     le libellé « Inscription » et la pastille de rôle partageraient la même
     colonne, dont la largeur — celle de la date — amputait l'identité. */
  .users-table tr {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--spacing-sm) var(--spacing-md);
    margin-bottom: var(--spacing-md);
    padding: var(--spacing-md);
    border: var(--border-thin);
    border-radius: var(--radius-card);
    background: var(--color-bg-white);
    box-shadow: var(--shadow-sm);
  }

  .users-table tr:last-child {
    margin-bottom: 0;
  }

  .users-table td {
    padding: 0;
    border-bottom: 0;
  }

  /* Ligne 1 — identité à gauche, rôle poussé à droite. */
  .users-table .users-identity-cell {
    display: flex;
    /* Base 0 : sans cela, un nom long pousserait la pastille de rôle sur une
       ligne à elle seule au lieu de se replier lui-même. */
    flex: 1 1 0;
    min-width: 0;
    align-items: flex-start;
    gap: var(--spacing-sm);
  }

  .users-identity {
    min-width: 0;
  }

  /* L'adresse e-mail est le seul contenu insécable de la carte : sans césure
     elle déborderait de sa colonne. */
  .users-identity span {
    overflow-wrap: anywhere;
  }

  .users-role-cell {
    flex: 0 0 auto;
    margin-left: auto;
  }

  /* Ligne 2 — préférences au fil du texte plutôt qu'empilées. */
  .users-prefs-cell,
  .users-actions {
    width: 100%;
  }

  .users-preferences {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--spacing-xs) var(--spacing-sm);
  }

  /* Ligne 3 — hors de leur colonne, les chiffres nus reprennent l'intitulé
     que le thead ne porte plus. */
  .users-menus-cell,
  .users-date-cell {
    font-size: var(--font-size-sm);
  }

  .users-menus-cell::before,
  .users-date-cell::before {
    content: attr(data-label) " : ";
    color: var(--color-ink-3);
  }

  .users-date-cell {
    margin-left: auto;
  }

  /* Ligne 4 — actions détachées du contenu par un filet. */
  .users-table .users-actions {
    padding-top: var(--spacing-sm);
    border-top: var(--border-thin);
  }

  .users-actions__inner {
    justify-content: flex-end;
    gap: var(--spacing-sm);
  }

  /* Le gap de la barre porte seul l'espacement : la marge propre au bouton
     décalerait la dernière icône du bord de la carte. */
  .users-actions__inner .icon-btn {
    margin: 0;
  }

  .users-form-card {
    padding: var(--spacing-lg);
  }

  .users-form-grid,
  .users-form-flags {
    grid-template-columns: 1fr;
  }
}
/* ============================================
   AUTHENTICATION PAGES - MODERN DESIGN SYSTEM
   ============================================
   Utilise les variables CSS globales définies dans variables.css
   ============================================ */

/* ============================================
   AUTHENTICATION CONTAINER
   ============================================ */

.auth-container {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--color-bg-main);
  padding: 2rem 1rem;
  position: relative;
  overflow: hidden;
}

.auth-container::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-image: 
    radial-gradient(circle at 20% 50%, rgba(28, 25, 22, 0.03) 0%, transparent 50%),
    radial-gradient(circle at 80% 80%, rgba(28, 25, 22, 0.02) 0%, transparent 50%);
}

@keyframes gradientShift {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.8; }
}

.auth-card {
  background: var(--color-bg-white);
  border-radius: var(--radius-card);
  box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
  max-width: 480px;
  width: 100%;
  padding: 3rem 2.5rem;
  position: relative;
  z-index: 1;
  animation: slideUp 0.4s ease-out;
}

@keyframes slideUp {
  from {
    opacity: 0;
    transform: translateY(20px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* ============================================
   AUTHENTICATION HEADER
   ============================================ */

.auth-header {
  text-align: center;
}

.user-form-header {
  margin-bottom: 2rem;
}

.auth-title {
  font-size: 2rem;
  font-weight: 700;
  color: var(--color-ink);
  margin-bottom: 0.5rem;
  letter-spacing: -0.025em;
}

.auth-subtitle {
  font-size: 1rem;
  color: var(--color-ink-3);
  margin: 0;
  font-weight: 400;
}

/* ============================================
   FORM STRUCTURE
   ============================================ */

.auth-form {
  width: 100%;
}

.form-fields {
  margin-bottom: 1.5rem;
}

/* .form-row : définition canonique dans components.css (grid 2 colonnes).
   Seul l'espacement propre à l'auth est surchargé ici (les .form-group internes
   portent déjà leur marge basse). */
.auth-form .form-row {
  margin-bottom: 0;
}

.form-group {
  margin-bottom: 1.5rem;
  position: relative;
}

.form-group-half {
  margin-bottom: 1.5rem;
}

/* Remove extra margin from SimpleForm wrapper */
.input-wrapper {
  margin-bottom: 0;
}

/* .form-label et .form-label abbr[title] : définition canonique unique dans
   components.css (identique à l'ancienne version auth). Ne pas redupliquer ici. */

/* ============================================
   FORM INPUTS
   ============================================ */

.form-input {
  width: 100%;
  padding: 0.75rem 1rem;
  font-size: 1rem;
  line-height: 1.5;
  color: var(--color-ink);
  background-color: var(--color-bg-white);
  border: var(--border-thin);
  border-radius: var(--radius-input);
  transition: var(--transition-normal);
  outline: none;
  box-sizing: border-box;
}

.form-input::placeholder {
  color: var(--color-ink-4);
}

/* ============================================
   ŒIL D'AFFICHAGE DU MOT DE PASSE
   ============================================ */

/* Le conteneur est posé par le contrôleur `password-visibility` autour du seul
   champ : c'est ce qui permet d'ancrer le bouton sur le champ, et non sur le
   groupe, dont la hauteur dépend du libellé et de l'aide. */
.password-field__control {
  position: relative;
  display: flex;
  align-items: center;
}

/* Le texte s'arrête avant le bouton : sans cette réserve, un mot de passe long
   passerait dessous. */
.password-field__control .form-input {
  padding-right: 2.75rem;
}

.password-toggle {
  position: absolute;
  right: 0.5rem;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0.375rem;
  color: var(--color-ink-4);
  background: none;
  border: none;
  border-radius: var(--radius-sm, 6px);
  cursor: pointer;
  transition: var(--transition-normal);
}

.password-toggle:hover {
  color: var(--color-ink-2);
}

/* Une seule des deux icônes est visible à la fois : l'œil quand le mot de passe
   est masqué (« cliquer pour voir »), l'œil barré quand il est lisible. */
.password-toggle__icon--hide,
.password-field--visible .password-toggle__icon--show {
  display: none;
}

.password-field--visible .password-toggle__icon--hide {
  display: block;
}

.form-input:focus {
  border-color: var(--color-primary);
  box-shadow: 0 0 0 2px var(--color-primary-light);
}

.form-input:hover:not(:focus) {
  border-color: var(--color-border-light);
}

/* Input with error */
.form-group.error .form-input,
.input-wrapper.error .form-input {
  border-color: var(--color-danger);
  background-color: var(--color-danger-light);
}

.form-group.error .form-input:focus,
.input-wrapper.error .form-input:focus {
  border-color: var(--color-danger);
  box-shadow: 0 0 0 2px rgba(220, 38, 38, 0.1);
}

/* ============================================
   FORM HINTS & ERRORS
   ============================================ */

.hint {
  display: block;
  font-size: 0.8125rem;
  color: var(--color-ink-3);
  margin-top: 0.375rem;
}

.error {
  display: block;
  font-size: 0.8125rem;
  color: var(--color-danger);
  margin-top: 0.375rem;
  font-weight: 500;
}

/* Error notification */
.error_notification {
  background-color: var(--color-danger-light);
  border-left: 4px solid var(--color-danger);
  padding: 1rem 1.25rem;
  margin-bottom: 1.5rem;
  border-radius: var(--radius-card-compact);
}

.error_notification p {
  color: var(--color-danger-text);
  font-size: 0.875rem;
  margin: 0;
  font-weight: 500;
}

/* ============================================
   BUTTONS
   ============================================ */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.75rem 1.5rem;
  font-size: 1rem;
  font-weight: 600;
  line-height: 1.5;
  border-radius: var(--radius-button);
  border: none;
  cursor: pointer;
  transition: var(--transition-normal);
  text-decoration: none;
  outline: none;
  box-sizing: border-box;
}

.btn-primary {
  background: var(--color-primary);
  color: var(--color-bg-white);
  box-shadow: var(--shadow-sm);
}

.btn-primary:hover {
  background: var(--color-primary-hover);
  box-shadow: var(--shadow-md);
  transform: translateY(-1px);
}

.btn-primary:active {
  transform: translateY(0);
  box-shadow: var(--shadow-sm);
}

.btn-large {
  width: 100%;
  padding: 1rem 1.5rem;
  font-size: 1.0625rem;
}

.btn-oauth {
  width: 100%;
  margin-top: 0.75rem;
  background-color: var(--color-bg-white);
  color: var(--color-ink-2);
  border: 2px solid var(--color-border);
}

.btn-oauth:hover {
  background-color: var(--color-bg-main);
  border-color: var(--color-border-light);
}

/* ============================================
   FORM ACTIONS
   ============================================ */

.form-actions {
  margin-top: 2rem;
}

/* ============================================
   AUTH FOOTER & LINKS
   ============================================ */

.auth-footer {
  margin-top: 2rem;
  padding-top: 2rem;
  border-top: 1px solid var(--color-border);
}

.auth-links {
  text-align: center;
}

.auth-link-text {
  font-size: 0.9375rem;
  color: var(--color-ink-3);
  margin: 0.75rem 0;
}

.auth-link {
  color: var(--color-ink);
  text-decoration: none;
  font-weight: 600;
  transition: var(--transition-normal);
  margin-left: 0.25rem;
}

.auth-link:hover {
  color: var(--color-ink-2);
  text-decoration: underline;
}

.auth-link-secondary {
  color: var(--color-ink-3);
  text-decoration: none;
  font-weight: 500;
  transition: var(--transition-normal);
}

.auth-link-secondary:hover {
  color: var(--color-ink);
  text-decoration: underline;
}

/* ============================================
   SIMPLE FORM SPECIFIC OVERRIDES
   ============================================ */

/* Reset SimpleForm default styles */
.simple_form .input {
  margin-bottom: 0;
}

.simple_form label {
  margin-bottom: 0.5rem;
}

.simple_form .hint {
  display: block;
  margin-top: 0.375rem;
}

/* Ensure proper input sizing */
.simple_form input[type="text"],
.simple_form input[type="email"],
.simple_form input[type="password"] {
  width: 100%;
  box-sizing: border-box;
}

/* Boolean checkbox styling */
.simple_form .boolean {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 1rem 0;
}

.simple_form .boolean input[type="checkbox"] {
  width: 1.125rem;
  height: 1.125rem;
  border-radius: var(--radius-input);
  border: 2px solid var(--color-border);
  cursor: pointer;
}

.simple_form .boolean label {
  margin: 0;
  font-weight: 400;
  cursor: pointer;
}

/* ============================================
   RESPONSIVE DESIGN
   ============================================ */

@media (max-width: 640px) {
  .auth-card {
    padding: 2rem 1.5rem;
  }

  .auth-title {
    font-size: 1.625rem;
  }

  .auth-subtitle {
    font-size: 0.9375rem;
  }

  .form-row {
    grid-template-columns: 1fr;
    gap: 0;
  }

  .form-group-half {
    margin-bottom: 1.5rem;
  }
}

@media (max-width: 480px) {
  .auth-container {
    padding: 1rem 0.75rem;
  }

  .auth-card {
    padding: 1.75rem 1.25rem;
  }

  .auth-title {
    font-size: 1.5rem;
  }

  .form-input {
    padding: 0.625rem 0.875rem;
    /* 16px minimum : en-dessous, iOS zoome automatiquement au focus du champ. */
    font-size: 1rem;
  }

  .btn-large {
    padding: 0.875rem 1.25rem;
  }
}

/* Accessibilité (focus visible, reduced-motion) : règles globales centralisées
   dans global.css — ne pas dupliquer ici. */
/* ============================================
   MENUS - Styles UC1 / UC2
   ============================================
   Index, new/edit, show brouillon (draft_view),
   cartes de repas (meal-card), formulaires de génération.
   Design Slate Craft Premium — no box-shadow on cards, bordures fines 1px.
   ============================================ */

/* ─────────────────────────────────────────────────────────
   MENUS INDEX (mi-*) — design mockup hero card / historique
   ───────────────────────────────────────────────────────── */

.mi-page-wrap {
  width: 100%;
  max-width: 1400px;
  margin: 0 auto;
  padding: 36px 0 60px;
}

.mi-page-top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 32px;
}

.mi-page-title {
  font-family: var(--font-serif);
  font-size: 22px;
  font-weight: 400;
  color: var(--color-ink);
  margin: 0;
}

.mi-btn-new {
  background: var(--color-primary);
  color: var(--color-bg-white);
  border: none;
  border-radius: var(--radius-button);
  padding: 9px 18px;
  font-size: var(--font-size-sm);
  font-weight: 500;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  letter-spacing: 0.02em;
  white-space: nowrap;
  text-decoration: none;
  transition: background var(--transition-fast);
}

.mi-btn-new:hover {
  background: var(--color-primary-hover);
  color: var(--color-bg-white);
  text-decoration: none;
}

.mi-btn-new svg {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
}

/* ── Draft guard (modale avant création si brouillon existant) ── */

/* Wrapper inline pour ne pas rompre le layout flexbox de .mi-page-top */
.mi-draft-guard {
  display: inline-flex;
  align-items: center;
  position: relative;
}

/* Aperçu du brouillon dans la modale */
.mn-draft-guard-preview {
  background: var(--color-bg-main);
  border: var(--border-thin);
  border-radius: var(--radius-button);
  padding: 10px 14px;
  margin: 10px 0 16px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.mn-draft-guard-name {
  font-size: var(--font-size-base);
  font-weight: 500;
  color: var(--color-ink);
}

.mn-draft-guard-meta {
  font-size: var(--font-size-xs);
  color: var(--color-ink-3);
}

/* En-tête de la modale avec croix de fermeture */
.mn-modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.mn-modal-close {
  background: none;
  border: none;
  cursor: pointer;
  font-size: 20px;
  line-height: 1;
  color: var(--color-ink-3);
  padding: 0 0 0 8px;
  transition: color var(--transition-fast);
  flex-shrink: 0;
}

.mn-modal-close:hover {
  color: var(--color-ink);
}

/* Boutons d'action pleine largeur en 2 colonnes */
.mn-draft-guard-actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin-top: 0.5rem;
}

.mn-draft-guard-actions .btn {
  width: 100%;
  justify-content: center;
}

@media (max-width: 767px) {
  .mn-draft-guard-actions {
    grid-template-columns: 1fr;
  }
}

.mn-replace-warning {
  display: grid;
  gap: 4px;
  margin: 0 0 18px;
  padding: 12px 14px;
  background: var(--color-bg-white);
  border: var(--border-thin);
  border-left: 3px solid var(--color-primary);
  border-radius: var(--radius-card);
  color: var(--color-ink-2);
  font-size: var(--font-size-sm);
}

.mn-replace-warning strong {
  color: var(--color-ink);
  font-weight: 500;
}

/* ── Section labels ────────────────────────────────────── */

.mi-section {
  margin-bottom: 24px;
}

.mi-section-label {
  font-size: var(--font-size-xs);
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-ink-3);
  margin-bottom: 10px;
  display: flex;
  align-items: center;
  gap: 8px;
}

.mi-section-label::after {
  content: '';
  flex: 1;
  height: var(--border-width);
  background: var(--color-border);
}

.mi-section-count {
  font-size: var(--font-size-xs);
  color: var(--color-ink-3);
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
}

/* ── Brouillon card ────────────────────────────────────── */

.mi-draft-card {
  background: var(--color-bg-white);
  border: var(--border-width) solid var(--color-accent-deep);
  border-radius: var(--radius-button);
  overflow: hidden;
  margin-bottom: 8px;
}

.mi-draft-card-header {
  padding: 13px 16px;
  display: flex;
  align-items: center;
  gap: 12px;
  cursor: pointer;
  user-select: none;
}

.mi-draft-card-left {
  flex: 1;
  min-width: 0;
}

.mi-draft-card-right {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}

.mi-draft-card-top {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 4px;
}

.mi-draft-badge {
  background: var(--color-accent-light);
  color: var(--color-accent-text);
  font-size: var(--font-size-xs);
  font-weight: 500;
  padding: 2px 7px;
  border-radius: var(--radius-badge);
  flex-shrink: 0;
}

.mi-draft-card-name {
  font-size: var(--font-size-base);
  font-weight: 500;
  color: var(--color-ink);
}

.mi-draft-card-meta {
  font-size: var(--font-size-xs);
  color: var(--color-ink-3);
  display: flex;
  gap: 8px;
}

/* Draft expanded content (hidden by default) */
.mi-draft-expanded {
  display: none;
  border-top: var(--border-width) solid var(--color-accent-deep);
}

.mi-draft-expanded.open {
  display: block;
}

.mi-draft-card-actions {
  padding: 8px 16px;
  display: flex;
  gap: 6px;
  background: var(--color-bg-secondary);
  border-top: var(--border-thin);
  flex-wrap: wrap;
}

/* ── Boutons compacts (brouillon + historique) ──────────── */

.mi-btn-sm {
  font-size: var(--font-size-xs);
  padding: 5px 10px;
  border-radius: var(--radius-button);
  border: var(--border-thin);
  background: var(--color-bg-white);
  color: var(--color-ink-2);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  text-decoration: none;
  transition: background var(--transition-fast), border-color var(--transition-fast),
    box-shadow var(--transition-fast), color var(--transition-fast);
}

/* Survol : fond blanc conservé, feedback par bordure marquée + ombre discrète. */
.mi-btn-sm:hover {
  background: var(--color-bg-white);
  color: var(--color-ink);
  border-color: var(--color-border-strong);
  box-shadow: var(--shadow-sm);
  text-decoration: none;
}

.mi-btn-sm svg {
  width: 11px;
  height: 11px;
}

/* Bouton suppression (cartes « à valider » & historique) : poubelle pleine noire
   (sans contour), dans un bouton blanc aligné sur les boutons voisins .mi-btn-sm.
   padding: 2px → seuls 2px entourent l'icône, qui est agrandie en conséquence. */
.mi-btn-sm-danger {
  padding: 2px;
  border-radius: var(--radius-button);
  border: var(--border-thin);
  background: var(--color-bg-white);
  color: var(--color-ink);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-left: auto;
  text-decoration: none;
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}

.mi-btn-sm-danger:hover {
  border-color: var(--color-border-strong);
  box-shadow: var(--shadow-sm);
}

.mi-btn-sm-danger svg {
  width: 22px;
  height: 22px;
}

/* button_to enveloppe le bouton dans un <form>, seul enfant flex de la rangée
   d'actions (étiré en hauteur par align-items: stretch). On centre le bouton
   compact dans cette hauteur pour l'aligner sur les boutons voisins. */
.mi-draft-card-actions form.button_to,
.mi-history-card-actions form.button_to {
  display: flex;
  align-items: center;
  margin: 0;
}

/* ── Hero card (menu actif) ────────────────────────────── */

.mi-hero-card {
  background: var(--color-primary);
  border-radius: var(--radius-button);
  overflow: hidden;
  margin-bottom: 32px;
}

.mi-hero-card-top {
  padding: 20px 24px 16px;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}

.mi-hero-card-info {
  flex: 1;
  min-width: 0;
}

.mi-hero-card-badges {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 6px;
  flex-wrap: wrap;
}

/* flex: 1 → absorbe l'espace libre pour pousser les actions et la poubelle
   contre le bord droit (la barre a 3 enfants : badges, actions, suppression). */
.mi-hero-card-badges-left {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1 1 auto;
  min-width: 0;
}

.mi-hero-card-badges-actions {
  display: flex;
  align-items: center;
  gap: 6px;
}

.mi-badge-active {
  background: var(--color-accent-light);
  color: var(--color-accent-text);
  font-size: var(--font-size-xs);
  font-weight: 500;
  padding: 3px 8px;
  border-radius: var(--radius-badge);
  letter-spacing: 0.04em;
}

.mi-hero-date {
  font-size: var(--font-size-xs);
  color: var(--color-ink-4);
}

.mi-hero-menu-name {
  font-family: var(--font-serif);
  font-size: 20px;
  font-weight: 400;
  color: var(--color-bg-white);
  line-height: 1.2;
  margin: 0 0 4px;
}

.mi-hero-meta {
  font-size: var(--font-size-xs);
  color: var(--color-ink-4);
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  align-items: center;
}

.mi-hero-meta-sep {
  color: var(--color-ink-2);
}

/* Stats */
.mi-hero-stats {
  display: flex;
  gap: 0;
  border: var(--border-width) solid var(--color-ink-2);
  border-radius: var(--radius-button);
  overflow: hidden;
  flex-shrink: 0;
  align-self: flex-start;
  margin-top: 4px;
}

.mi-hero-stat {
  padding: 8px 14px;
  border-right: var(--border-width) solid var(--color-ink-2);
  text-align: center;
}

.mi-hero-stat:last-child {
  border-right: none;
}

.mi-hero-stat-val {
  font-size: var(--font-size-base);
  font-weight: 500;
  color: var(--color-bg-white);
  line-height: 1;
}

.mi-hero-stat-lbl {
  font-size: var(--font-size-xs);
  color: var(--color-ink-4);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  margin-top: 2px;
}

/* ── Mini recipe cards (dans le hero) ──────────────────── */

.mi-recipe-cards-section {
  border-top: var(--border-width) solid var(--color-surface-dark);
  padding: 16px 24px;
}

.mi-recipe-cards-label {
  font-size: var(--font-size-xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-ink-4);
  margin-bottom: 10px;
}

.mi-recipe-cards-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 8px;
}

.mi-recipe-card-mini {
  background: var(--color-surface-dark);
  border: var(--border-width) solid var(--color-surface-dark-border);
  border-radius: var(--radius-button);
  overflow: hidden;
  cursor: pointer;
  transition: border-color var(--transition-fast);
  text-decoration: none;
  display: block;
}

.mi-recipe-card-mini:hover {
  border-color: var(--color-surface-dark-border-hover);
  text-decoration: none;
}

.mi-recipe-card-mini-img {
  height: 60px;
  background: var(--color-bg-main);
  display: flex;
  align-items: center;
  justify-content: center;
  border-bottom: var(--border-width) solid var(--color-surface-dark-border);
  overflow: hidden;
}

.mi-recipe-card-mini-photo {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.mi-recipe-placeholder-icon {
  color: var(--color-surface-dark-border-hover);
}

.mi-recipe-card-mini-body {
  padding: 7px 8px;
}

.mi-recipe-card-mini-name {
  font-size: var(--font-size-xs);
  font-weight: 500;
  color: var(--color-ink-on-dark);
  line-height: 1.3;
  margin-bottom: 3px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.mi-recipe-card-mini-meta {
  font-size: var(--font-size-xs);
  color: var(--color-ink-4);
}

/* ── Hero actions ──────────────────────────────────────── */

.mi-hero-actions {
  border-top: var(--border-width) solid var(--color-surface-dark);
  padding: 14px 24px;
  display: flex;
  gap: 8px;
  align-items: center;
  flex-wrap: wrap;
}

/* Action principale sur la carte « menu actif » (fond sombre anthracite).
   L'anthracite plein serait invisible ici : on utilise donc le primaire inversé
   (fond clair, texte anthracite). L'ambre reste réservé au seul CTA accent. */
.mi-btn-hero-primary {
  background: var(--color-bg-white);
  color: var(--color-primary);
  font-size: var(--font-size-sm);
  font-weight: 500;
  padding: 8px 16px;
  border-radius: var(--radius-button);
  border: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  text-decoration: none;
  transition: background var(--transition-fast);
}

.mi-btn-hero-primary:hover {
  background: var(--color-secondary-hover);
  color: var(--color-primary);
  text-decoration: none;
}

.mi-btn-hero-primary svg {
  width: 13px;
  height: 13px;
}

.mi-btn-hero-ghost {
  background: transparent;
  color: var(--color-ink-4);
  font-size: var(--font-size-sm);
  padding: 8px 12px;
  border-radius: var(--radius-button);
  border: var(--border-width) solid var(--color-ink-2);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  text-decoration: none;
  transition: border-color var(--transition-fast), color var(--transition-fast);
}

.mi-btn-hero-ghost:hover {
  border-color: var(--color-surface-dark-border-hover);
  color: var(--color-ink-on-dark);
  text-decoration: none;
}

.mi-btn-hero-ghost svg {
  width: 13px;
  height: 13px;
}

/* Suppression du menu actif : icône poubelle pleine seule (blanche), sans cadre.
   button_to enveloppe le bouton dans un <form> : c'est ce form qui est l'enfant
   flex de la barre. On l'étire (align-self: stretch) sur la hauteur des boutons
   voisins (« Voir le menu »…) et le bouton, seul enfant flex du form, la remplit.
   Survol en rouge danger pour signaler l'action destructive. */
.mi-hero-card-badges>form.button_to {
  display: flex;
  align-self: stretch;
  margin: 0;
}

.mi-btn-hero-danger {
  background: transparent;
  color: var(--color-bg-white);
  padding: 2px 10px;
  border-radius: var(--radius-button);
  border: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
  transition: color var(--transition-fast);
}

.mi-btn-hero-danger:hover {
  color: var(--color-danger-on-dark);
  text-decoration: none;
}

/* height: 100% → l'icône épouse la hauteur du bouton (donc des boutons voisins) ;
   width: auto conserve le ratio 1:1 du viewBox. La taille passée à svg_icon ne
   sert que de repli si le pourcentage ne se résout pas. */
.mi-btn-hero-danger svg {
  height: 100%;
  width: auto;
}

/* ── History list + cards ──────────────────────────────── */

.mi-history-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 12px;
}

.mi-history-card {
  background: var(--color-bg-white);
  border: var(--border-thin);
  border-radius: var(--radius-button);
  overflow: hidden;
  transition: border-color var(--transition-fast);
  width: 100%;
  min-width: 0;
}

.mi-history-card:hover {
  border-color: var(--color-border-strong);
}

.mi-history-card-header {
  padding: 13px 16px;
  display: flex;
  align-items: center;
  gap: 12px;
  cursor: pointer;
  user-select: none;
}

.mi-history-card-left {
  flex: 1;
  min-width: 0;
}

.mi-history-card-name {
  font-size: var(--font-size-base);
  font-weight: 500;
  color: var(--color-ink);
  margin-bottom: 3px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.mi-history-card-meta {
  font-size: var(--font-size-xs);
  color: var(--color-ink-3);
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

.mi-history-card-right {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}

.mi-badge-done {
  background: var(--color-success-light);
  color: var(--color-success-text);
  font-size: var(--font-size-xs);
  padding: 2px 7px;
  border-radius: var(--radius-badge);
  font-weight: 500;
}

.mi-expand-icon {
  width: 16px;
  height: 16px;
  color: var(--color-ink-4);
  transition: transform 0.2s;
  flex-shrink: 0;
}

.mi-expand-icon.open {
  transform: rotate(180deg);
}

/* Expanded content (hidden by default) */
.mi-history-expanded {
  display: none;
  border-top: var(--border-thin);
}

.mi-history-expanded.open {
  display: block;
}

/* Recipe thumbnails in history */
.mi-history-recipes {
  padding: 12px 16px;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
  gap: 6px;
  background: var(--color-bg-secondary);
  overflow: hidden;
}

.mi-hist-recipe-card {
  background: var(--color-bg-white);
  border: var(--border-thin);
  border-radius: var(--radius-button);
  overflow: hidden;
}

.mi-hist-recipe-img {
  height: 64px;
  background: var(--color-border);
  display: flex;
  align-items: center;
  justify-content: center;
  border-bottom: var(--border-thin);
  overflow: hidden;
}

.mi-hist-recipe-photo {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.mi-hist-recipe-body {
  padding: 5px 7px;
}

.mi-hist-recipe-name {
  font-size: var(--font-size-xs);
  font-weight: 500;
  color: var(--color-ink);
  line-height: 1.3;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.mi-hist-recipe-time {
  font-size: var(--font-size-xs);
  color: var(--color-ink-3);
  margin-top: 1px;
}

/* History card actions */
.mi-history-card-actions {
  padding: 8px 16px;
  display: flex;
  gap: 6px;
  background: var(--color-bg-secondary);
  border-top: var(--border-thin);
  flex-wrap: wrap;
}

/* ── Older menus toggle ────────────────────────────────── */

.mi-older-menus {
  display: none;
  flex-direction: column;
  gap: 8px;
}

.mi-older-menus.open {
  display: flex;
}

.mi-toggle-old {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: var(--font-size-xs);
  color: var(--color-ink-3);
  cursor: pointer;
  padding: 8px 0;
  border: none;
  background: none;
  width: 100%;
  transition: color var(--transition-fast);
}

.mi-toggle-old:hover {
  color: var(--color-ink);
}

.mi-toggle-old svg {
  width: 14px;
  height: 14px;
  transition: transform 0.2s;
  flex-shrink: 0;
}

.mi-toggle-old-icon.open {
  transform: rotate(180deg);
}

/* ── Empty state ───────────────────────────────────────── */

.mi-empty {
  text-align: center;
  padding: 4rem 2rem;
  background: var(--color-bg-white);
  border-radius: var(--radius-card);
  border: var(--border-thin);
}

.mi-empty-icon {
  margin-bottom: 1rem;
  color: var(--color-ink-4);
}

.mi-empty-title {
  font-size: var(--font-size-2xl);
  font-weight: 600;
  color: var(--color-ink);
  margin-bottom: 0.5rem;
}

.mi-empty-text {
  color: var(--color-ink-3);
  margin-bottom: 1.5rem;
}

/* ── Responsive index ──────────────────────────────────── */

@media (max-width: 640px) {
  .mi-page-wrap {
    padding: 20px 16px 48px;
    width: auto;
  }

  .mi-hero-stats {
    display: none;
  }

  .mi-hero-card-top {
    flex-direction: column;
  }

  /* Barre d'en-tête du menu actif sur 2 lignes :
     1. badge « Actif » + date de création, avec la poubelle callée à droite ;
     2. les 2 actions de consultation, à parts égales.
     Grille explicite plutôt qu'un simple retour à la ligne flex : le passage
     à la ligne est ainsi garanti quelle que soit la longueur des libellés. */
  .mi-hero-card-badges {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: 8px;
    align-items: center;
  }

  .mi-hero-card-badges-left {
    grid-row: 1;
    grid-column: 1;
    /* Sur écran très étroit, badge et date passent l'un sous l'autre plutôt que
       de déborder sous la poubelle. */
    flex-wrap: wrap;
  }

  /* Suppression : remontée sur la ligne 1, collée à droite. La cible tactile
     reste à 44px (action destructive) mais l'icône est réduite pour rester
     discrète à côté de la date. */
  .mi-hero-card-badges>form.button_to {
    grid-row: 1;
    grid-column: 2;
    align-self: center;
    justify-self: end;
  }

  .mi-btn-hero-danger svg {
    width: 18px;
    height: 18px;
  }

  /* Ligne 2 pleine largeur, 2 colonnes égales → boutons de largeur identique,
     et même hauteur même si un libellé passe sur 2 lignes (stretch par défaut). */
  .mi-hero-card-badges-actions {
    grid-row: 2;
    grid-column: 1 / -1;
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 8px;
  }

  /* Padding latéral réduit : le libellé dispose de toute la demi-largeur. */
  .mi-btn-hero-primary {
    padding: 8px 6px;
    justify-content: center;
    text-align: center;
    min-width: 0;
  }

  .mi-btn-hero-primary svg {
    flex-shrink: 0;
  }

  .mi-recipe-cards-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .mi-history-recipes {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }

  .mi-draft-card-header {
    gap: 8px;
  }

  .mi-draft-card-actions {
    width: 100%;
  }

  .mi-page-top {
    flex-direction: column;
    gap: 12px;
  }
}

/* ─────────────────────────────────────────────────────────
   Legacy index classes preserved for other usages
   ───────────────────────────────────────────────────────── */

/* ── Hero new/edit ─────────────────────────────────────── */
/* NOTE: .mn-back-bar et .mn-back-link supprimés — utilise .context-bar */

.mn-hero {
  background: var(--color-primary);
  padding: 2rem 1.5rem;
}

.mn-hero-inner {
  max-width: 900px;
  margin: 0 auto;
}

.mn-kicker {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-bottom: 0.75rem;
  font-size: var(--font-size-xs);
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.5);
}

.mn-kicker-line {
  display: block;
  width: 24px;
  height: 1px;
  background: rgba(255, 255, 255, 0.3);
}

.mn-hero-title {
  font-size: var(--font-size-3xl);
  font-weight: 700;
  color: var(--color-bg-white);
  margin: 0;
}

/* ── Body new/edit (formulaires) ───────────────────────── */
.mn-body {
  max-width: 900px;
  margin: 0 auto;
  padding: 2rem 1.5rem;
}

.mn-form {
  background: var(--color-bg-white);
  border-radius: var(--radius-card);
  border: var(--border-thin);
  overflow: hidden;
}

.mn-form-section {
  padding: 1.5rem 2rem;
}

.mn-section-label {
  font-size: var(--font-size-xs);
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-ink-3);
  margin-bottom: 1.25rem;
  padding-bottom: 0.5rem;
  border-bottom: var(--border-thin);
}

.mn-form-actions {
  padding: 1.25rem 2rem;
  border-top: var(--border-thin);
  background: var(--color-bg-secondary);
  display: flex;
  gap: 0.75rem;
  align-items: center;
}

/* ── Show — en-tête ────────────────────────────────────── */
.menu-show {
  max-width: 1100px;
  margin: 0 auto;
  padding: 2rem 1.5rem;
}

.menu-show-header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1rem;
  margin-bottom: 2rem;
  padding-bottom: 1.25rem;
  border-bottom: var(--border-thin);
}

/* NOTE: .menu-show-breadcrumb et .menu-breadcrumb-* supprimés — utilise .context-bar */

.menu-show-meta {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
  font-size: var(--font-size-xs);
  color: var(--color-ink-3);
}

.menu-meta-diet,
.menu-meta-people {
  background: var(--color-bg-secondary);
  border: var(--border-thin);
  border-radius: var(--radius-badge);
  padding: 0.2em 0.65em;
  font-size: 0.75rem;
  font-weight: 500;
  color: var(--color-ink-2);
}

.menu-show-actions {
  display: flex;
  gap: 0.5rem;
}

/* ── Readonly recipe card (dashboards de la home) ──────── */
/* Sélecteur doublé à dessein : .mc-recipe-card pose plus bas dans ce fichier un
   raccourci `border-left` qui, à spécificité égale, l'emporterait par simple
   ordre de déclaration. */
.mc-recipe-card.mc-recipe-card--readonly {
  cursor: pointer;
  text-decoration: none;
  /* Ces cartes n'annotent aucun jour (pas de data-day) : elles reprennent une
     bordure uniforme plutôt que la barre latérale neutre de 5px, qui n'y
     signifierait rien. */
  border-left-width: var(--border-width);
  /* Le nom est le dernier bloc de la carte (pas de méta) : la carte reprend à
     son compte le retrait bas que portait la ligne « X pers. ». */
  padding-bottom: 7px;
}

.mc-recipe-card--readonly:hover {
  text-decoration: none;
}

/* « X pers. » figé dans la barre d'actions de la carte du menu actif
   (menus/_menu_recipe_card). */
.mc-card-meta-people {
  font-size: var(--font-size-xs);
  color: var(--color-ink-3);
  font-weight: 500;
}

/* ── Liste meal-cards ──────────────────────────────────── */
.meal-cards-list {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  margin-bottom: 1.5rem;
}

/* ── Meal card ─────────────────────────────────────────── */
.meal-card {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  padding: 1rem 1.25rem;
  background: var(--color-bg-white);
  border-radius: var(--radius-card);
  border: var(--border-thin);
  flex-wrap: wrap;
}

.meal-card-info {
  flex: 1;
  min-width: 0;
}

.meal-card-title {
  font-size: var(--font-size-base);
  font-weight: 600;
  color: var(--color-ink);
  margin: 0 0 0.4rem;
}

.meal-card-recipe-link {
  color: inherit;
  text-decoration: none;
  transition: color var(--transition-fast);
}

.meal-card-recipe-link:hover {
  color: var(--color-primary-hover);
}

.meal-card-badges {
  display: flex;
  gap: 0.4rem;
  flex-wrap: wrap;
}

/* ── Vue menu archivé (_archived_view) ─────────────────── */
.menu-archived-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 1rem;
  padding: 1rem 1.25rem;
  margin-bottom: 1.5rem;
  background: var(--color-bg-secondary);
  border: var(--border-thin);
  border-radius: var(--radius-card);
}

.menu-archived-banner-text {
  margin: 0;
  color: var(--color-ink-2);
  font-size: var(--font-size-sm);
}

.menu-archived-title {
  font-size: var(--font-size-lg);
  font-weight: 600;
  color: var(--color-ink);
  margin: 0 0 1rem;
}

.menu-archived-empty {
  padding: 2rem;
  text-align: center;
  color: var(--color-ink-3);
  background: var(--color-bg-secondary);
  border: var(--border-width) dashed var(--color-border-strong);
  border-radius: var(--radius-card);
}

.menu-archived-empty p {
  margin: 0;
}

/* Bloc « X pers. » à droite d'une meal-card (lecture seule) */
.meal-card-people {
  flex-shrink: 0;
}

.people-display {
  display: inline-block;
  background: var(--color-bg-secondary);
  border: var(--border-thin);
  border-radius: var(--radius-badge);
  padding: 0.2em 0.65em;
  font-size: var(--font-size-xs);
  font-weight: 500;
  color: var(--color-ink-2);
}

/* Sur mobile, le bouton de réactivation prend toute la largeur du bandeau. */
@media (max-width: 767px) {
  .menu-archived-banner .btn {
    width: 100%;
  }
}

/* ── Badge ambre d'information (badge « de saison » meal-card + badge
      « quantité augmentée » de la liste de courses) ──────── */
.badge-amber {
  background: var(--color-accent-light);
  color: var(--color-accent-text);
  border: var(--border-width) solid var(--color-accent);
  border-radius: var(--radius-badge);
  font-size: 0.75rem;
  font-weight: 500;
  padding: 0.2em 0.6em;
}

/* Tailles partagées .btn-sm / .btn-lg / .btn-icon : voir components.css
   (section BOUTONS — source de vérité du système). */

/* ── Responsive ────────────────────────────────────────── */
@media (max-width: 767px) {
  .menu-show-header {
    flex-direction: column;
    align-items: flex-start;
  }

  .menu-show-actions {
    width: 100%;
    justify-content: flex-start;
  }

  .meal-card {
    flex-direction: column;
    align-items: flex-start;
  }

  .mn-form-section {
    padding: 1.25rem 1rem;
  }

  .mn-form-actions {
    padding: 1rem;
    flex-direction: column;
    align-items: stretch;
  }

  .mn-form-actions .btn {
    width: 100%;
    justify-content: center;
  }
}

/* ── Formulaire de génération (design card-based) ──────── */

.mn-page-wrap {
  max-width: 640px;
  margin: 0 auto;
  padding: 40px 28px 60px;
}

.mn-page-header {
  margin-bottom: 32px;
}

.mn-page-kicker {
  font-size: var(--font-size-xs);
  letter-spacing: 0.12em;
  color: var(--color-accent-hover);
  text-transform: uppercase;
  margin-bottom: 8px;
  display: flex;
  align-items: center;
  gap: 8px;
}

.mn-page-kicker-sep {
  width: 16px;
  height: var(--border-width);
  background: var(--color-accent-deep);
  opacity: 0.6;
}

.mn-page-title {
  font-family: var(--font-serif);
  font-size: 24px;
  font-weight: 400;
  color: var(--color-ink);
  line-height: 1.2;
  margin: 0 0 6px;
}

.mn-page-sub {
  font-size: var(--font-size-base);
  color: var(--color-ink-3);
  line-height: 1.5;
  margin: 0;
}

/* ── Form cards ──────────────────────────────────────────── */

.mn-form-card {
  background: var(--color-bg-white);
  border: var(--border-thin);
  border-radius: var(--radius-button);
  overflow: hidden;
  margin-bottom: 12px;
}

.mn-form-card-header {
  padding: 14px 20px 0px;
  display: flex;
  align-items: center;
  gap: 8px;
}

.mn-form-card-num {
  width: 20px;
  height: 20px;
  border-radius: var(--radius-button);
  background: var(--color-primary);
  color: var(--color-bg-white);
  font-size: var(--font-size-xs);
  font-weight: 500;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

.mn-form-card-title {
  font-size: var(--font-size-sm);
  font-weight: 500;
  color: var(--color-ink);
  letter-spacing: 0.01em;
}

.mn-form-card-body {
  padding: 20px;
}

.mn-form-card-body--compact {
  padding: 16px 20px;
}

.mn-field-hint {
  font-size: var(--font-size-xs);
  color: var(--color-ink-3);
  margin-top: 8px;
  line-height: 1.4;
}

.mn-field-optional {
  font-size: var(--font-size-xs);
  color: var(--color-ink-3);
  font-style: italic;
  margin-left: 5px;
}

/* ── Diet grid ───────────────────────────────────────────── */

.mn-diet-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
}

.mn-diet-option {
  border: var(--border-thin);
  border-radius: var(--radius-button);
  padding: 12px 14px;
  cursor: pointer;
  transition: border-color var(--transition-fast), background var(--transition-fast);
  display: flex;
  align-items: flex-start;
  gap: 10px;
  background: var(--color-bg-white);
}

.mn-diet-option:hover {
  border-color: var(--color-brand);
  background: var(--color-brand-light);
}

/* État sélectionné : piloté par le radio natif, donc toujours exact — au clic
   sur le label comme au clavier, sans classe à poser ni à retirer. */
.mn-diet-option:has(.mn-diet-radio-input:checked) {
  border-color: var(--color-brand);
  background: var(--color-brand-light);
}

/* Masquer le radio natif */
.mn-diet-radio-input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
  pointer-events: none;
}

.mn-diet-radio {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 1.5px solid var(--color-border-strong);
  background: var(--color-bg-white);
  flex-shrink: 0;
  margin-top: 1px;
  position: relative;
  transition: border-color var(--transition-fast), background var(--transition-fast);
}

.mn-diet-radio::after {
  content: '';
  display: none;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--color-bg-white);
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

.mn-diet-option:has(.mn-diet-radio-input:checked) .mn-diet-radio {
  background: var(--color-brand);
  border-color: var(--color-brand);
}

.mn-diet-option:has(.mn-diet-radio-input:checked) .mn-diet-radio::after {
  display: block;
}

.mn-diet-text {
  display: flex;
  flex-direction: column;
}

.mn-diet-name {
  font-size: var(--font-size-base);
  font-weight: 500;
  color: var(--color-ink);
  margin-bottom: 2px;
}

.mn-diet-desc {
  font-size: var(--font-size-xs);
  color: var(--color-ink-3);
  line-height: 1.4;
}

/* ── People counter ──────────────────────────────────────── */

/* ── Unified slider (people + meals) ─────────────────────── */

.mn-slider-display {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 4px;
}

.mn-slider-icon {
  width: 32px;
  height: 32px;
  border-radius: var(--radius-card);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

.mn-slider-icon--people {
  background: var(--color-category-icon-bg);
  color: var(--color-category-icon);
}

.mn-slider-big {
  font-size: 26px;
  font-weight: 500;
  color: var(--color-ink);
  line-height: 1;
}

.mn-slider-label {
  font-size: var(--font-size-sm);
  color: var(--color-ink-3);
}

.mn-slider {
  width: 100%;
  -webkit-appearance: none;
  appearance: none;
  height: 4px;
  background: var(--color-bg-tertiary);
  border-radius: 2px;
  outline: none;
  cursor: pointer;
}

.mn-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--color-primary);
  cursor: pointer;
  border: 2px solid var(--color-bg-white);
  box-shadow: 0 0 0 1.5px var(--color-primary);
}

.mn-slider::-moz-range-thumb {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--color-primary);
  cursor: pointer;
  border: 2px solid var(--color-bg-white);
}

/* ── Répartition des repas (5 steppers, UC7) ─────────────── */

/* Une ligne par moment de la journée : libellé à gauche, stepper à droite.
   Le stepper lui-même réutilise le composant .mc-stepper des réglages du menu
   (variante compacte, sans unité) — même objet d'interface, mêmes dimensions.
   Trois consommateurs, un seul habillage : le formulaire de génération, les
   réglages du foyer (shared/_meal_counts_fields) et le panneau de réglages du
   brouillon (menus/_settings_panel), qui y règle sa répartition en direct. */
.mn-meal-counts {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.mn-meal-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.mn-meal-label {
  font-size: var(--font-size-sm);
  color: var(--color-ink-2);
}

/* Option « même petit-déjeuner toute la semaine » : discrète, elle n'apparaît
   qu'à partir de 2 petits-déjeuners (attribut hidden piloté par Stimulus). */
.mn-meal-option {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 4px;
  font-size: var(--font-size-xs);
  color: var(--color-ink-3);
  line-height: 1.4;
  cursor: pointer;
}

.mn-meal-option[hidden] {
  display: none;
}

.mn-meal-option-checkbox {
  width: 15px;
  height: 15px;
  accent-color: var(--color-primary);
  flex-shrink: 0;
  cursor: pointer;
}

/* ── Text input ──────────────────────────────────────────── */

.mn-text-input {
  width: 100%;
  border: var(--border-thin);
  border-radius: var(--radius-button);
  padding: 9px 12px;
  font-size: var(--font-size-base);
  color: var(--color-ink);
  background: var(--color-bg-white);
  font-family: inherit;
  transition: border-color var(--transition-fast);
}

.mn-text-input:focus {
  outline: none;
  border-color: var(--color-primary);
}

.mn-text-input::placeholder {
  color: var(--color-ink-4);
}

/* ── Summary bar ─────────────────────────────────────────── */

.mn-summary-bar {
  background: var(--color-bg-secondary);
  border: var(--border-thin);
  border-radius: var(--radius-button);
  padding: 12px 16px;
  margin-bottom: 20px;
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  align-items: center;
}

.mn-summary-label {
  font-size: var(--font-size-xs);
  color: var(--color-ink-3);
  margin-right: 4px;
}

.mn-summary-chip {
  font-size: var(--font-size-xs);
  padding: 3px 9px;
  background: var(--color-bg-white);
  border: var(--border-thin);
  border-radius: var(--radius-button);
  color: var(--color-ink-2);
}

.mn-summary-chip--highlighted {
  background: var(--color-accent-light);
  border-color: var(--color-accent-deep);
  color: var(--color-accent-text);
}

.mn-summary-sep {
  color: var(--color-border-strong);
  font-size: var(--font-size-xs);
}

/* ── Submit zone ─────────────────────────────────────────── */

.mn-submit-zone {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.mn-btn-generate {
  background: var(--color-primary);
  color: var(--color-bg-white);
  border: none;
  border-radius: var(--radius-button);
  padding: 13px 24px;
  font-size: var(--font-size-sm);
  font-weight: 500;
  cursor: pointer;
  width: 100%;
  letter-spacing: 0.02em;
  transition: background var(--transition-fast);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}

.mn-btn-generate:hover {
  background: var(--color-primary-hover);
}

.mn-btn-generate:disabled {
  background: var(--color-bg-tertiary);
  color: var(--color-ink-4);
  cursor: not-allowed;
}

.mn-btn-generate svg {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
}

.mn-submit-note {
  font-size: var(--font-size-xs);
  color: var(--color-ink-3);
  text-align: center;
  line-height: 1.5;
  margin: 0;
}

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

.mn-spinning {
  animation: mn-spin 1s linear infinite;
}

/* ── Two-column layout (étapes 2+3) ──────────────────────── */

.mn-two-col {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
  margin-bottom: 12px;
}

.mn-two-col .mn-form-card {
  margin-bottom: 0;
}

/* ── Diet icons (backgrounds thématiques) ────────────────── */

/* Comme pour les cartes de repas, le neutre par défaut est porté par l'élément
   lui-même : un seul point de peinture, et tout régime sans pastille dédiée
   (--default compris) est couvert sans règle supplémentaire. */
.mn-diet-icon {
  --diet-tint: var(--color-bg-tertiary);

  width: 36px;
  height: 36px;
  border-radius: var(--radius-card);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  background: var(--diet-tint);
}

.mn-diet-icon--omni  { --diet-tint: var(--diet-omni-tint); }
.mn-diet-icon--vege  { --diet-tint: var(--diet-vege-tint); }
.mn-diet-icon--vegan { --diet-tint: var(--diet-vegan-tint); }
.mn-diet-icon--pesc  { --diet-tint: var(--diet-pesc-tint); }

/* Le glyphe générique est tracé en currentColor, contrairement aux quatre
   pictogrammes de régime qui portent leurs propres couleurs de remplissage. */
.mn-diet-icon--default {
  color: var(--color-ink-3);
}

/* ── Slider marks (graduation) ───────────────────────────── */

.mn-slider-marks {
  display: flex;
  justify-content: space-between;
}

.mn-slider-mark {
  font-size: var(--font-size-xs);
  color: var(--color-ink-3);
}

/* ── Responsive formulaire de génération ──────────────── */

@media (max-width: 600px) {
  .mn-page-wrap {
    padding: 24px 16px 48px;
  }

  .mn-page-title {
    font-size: 20px;
  }

  .mn-diet-grid {
    grid-template-columns: 1fr;
  }

  .mn-two-col {
    grid-template-columns: 1fr;
  }
}

/* ============================================================
   MENU CUSTOMIZE — Card grid (mc-*)
   Maquette : easymeal_menu_customize.html — Slate Craft Premium
   ============================================================ */

/* ── Page layout ───────────────────────────────────────── */
.mc-page-outer {
  display: flex;
  justify-content: center;
  padding: 28px 28px 56px;
}

.mc-page-wrap {
  width: 75%;
  min-width: 480px;
  max-width: 1400px;
}

/* Utilisé par grocery.html.haml (titre "Liste de courses") — les vues
   brouillon/actif portent désormais leur titre dans .mc-head-title. */
.mc-page-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 20px;
  gap: 12px;
}

.mc-page-title {
  font-family: var(--font-serif);
  font-size: 20px;
  font-weight: 400;
  color: var(--color-ink);
}

/* ── En-tête de page menu (mc-head-*) ──────────────────────
   Nom du menu en titre, puis une ligne de méta qui porte la pastille de statut
   et les caractéristiques du menu à gauche, les actions secondaires à droite.
   Rendu par menus/_page_head — brouillon et menu actif. L'action principale de
   la page est le CTA de bas de page (.mc-validate-zone). */
.mc-head {
  margin-bottom: 28px;
}

/* En desktop, la vue brouillon place sa disposition en deux colonnes juste sous
   l'en-tête : .mc-split porte déjà son propre écart (margin-top), la marge de
   l'en-tête s'y ajouterait. Elle reste en place sous le breakpoint, où
   .mc-split perd ce margin-top, et dans la vue menu actif (grille de repas
   directement sous l'en-tête, sans marge propre). */
@media (min-width: 1025px) {
  .mc-head:has(+ .mc-split) {
    margin-bottom: 0;
  }
}

/* Pastille de statut de la page */
.mc-kicker {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 9px;
  border-radius: var(--radius-badge);
  font-size: var(--font-size-xs);
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.mc-kicker::before {
  content: '';
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: currentColor;
  flex-shrink: 0;
}

.mc-kicker--draft {
  background: var(--color-accent-light);
  color: var(--color-accent-text);
}

.mc-kicker--active {
  background: var(--color-success-light);
  color: var(--color-success-text);
}

/* ── Titre : nom du menu ───────────────────────────────── */
.mc-head-title-row {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}

.mc-head-title,
.mc-head-title-input {
  font-family: var(--font-serif);
  font-size: 26px;
  font-weight: 400;
  line-height: 1.2;
  color: var(--color-ink);
  margin: 0;
}

/* Le nom du brouillon s'édite sur place : le champ se fond dans le titre et
   ne se révèle qu'au survol / focus pour ne pas alourdir l'en-tête. */
.mc-head-title-input {
  /* Largeur naturelle donnée par l'attribut size (calé sur la longueur du nom),
     bornée pour ne jamais déborder ; field-sizing affine au caractère près sur
     les navigateurs qui le supportent. */
  flex: 0 1 auto;
  min-width: 275px;
  max-width: 100%;
  field-sizing: content;
  padding: 1px 2px;
  background: transparent;
  border: none;
  border-bottom: 1.5px solid transparent;
  border-radius: 0;
  transition: border-color var(--transition-fast);
}

.mc-head-title-input:hover {
  border-bottom-color: var(--color-border-strong);
}

.mc-head-title-input:focus {
  outline: none;
  border-bottom-color: var(--color-primary);
}

.mc-head-title-input::placeholder {
  color: var(--color-ink-4);
}

/* Crayon : affordance d'édition du nom (le champ reste éditable au clic direct) */
.mc-head-title-edit {
  width: 28px;
  height: 28px;
  flex-shrink: 0;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--color-bg-white);
  border: var(--border-thin);
  border-radius: var(--radius-icon-btn);
  color: var(--color-ink-3);
  cursor: pointer;
  transition: color var(--transition-fast), border-color var(--transition-fast),
    box-shadow var(--transition-fast);
}

.mc-head-title-edit:hover {
  color: var(--color-ink);
  border-color: var(--color-border-strong);
  box-shadow: var(--shadow-sm);
}

/* ── Ligne méta : statut + caractéristiques, actions à droite ──
   Deux groupes écartés par space-between. En mobile, le groupe d'actions passe
   à la ligne (flex-wrap) et se retrouve aligné à gauche, sous les méta. */
.mc-head-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 8px 12px;
  margin-top: 10px;
  font-size: var(--font-size-xs);
  color: var(--color-ink-3);
}

.mc-head-meta-facts {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  min-width: 0;
}

/* La pastille ouvre la ligne : un peu d'air pour la détacher du texte de méta,
   qui n'a pas de séparateur avant lui. */
.mc-head-meta-facts .mc-kicker {
  margin-right: 4px;
}

.mc-head-meta-sep {
  color: var(--color-border-strong);
}

.mc-head-meta-actions {
  display: flex;
  align-items: center;
  gap: 2px;
  flex-shrink: 0;
}

/* button_to enveloppe chaque bouton dans un <form> : il ne doit pas rompre
   l'alignement de la rangée d'actions. */
.mc-head-meta-actions form.button_to {
  display: inline-flex;
  margin: 0;
}

/* Lien et non bouton encadré : ces actions ne doivent pas appeler le clic.
   Hauteur contenue pour ne pas épaissir la ligne de méta. */
.mc-head-action {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 8px;
  background: none;
  border: none;
  border-radius: var(--radius-button);
  font-size: var(--font-size-base);
  color: var(--color-ink-3);
  cursor: pointer;
  text-decoration: none;
  transition: background var(--transition-fast), color var(--transition-fast);
}

.mc-head-action:hover {
  background: var(--color-bg-secondary);
  color: var(--color-ink);
}

/* Destructif : neutre au repos, rouge au survol / focus — la conséquence est
   signalée juste avant le clic. */
.mc-head-action--danger:hover,
.mc-head-action--danger:focus-visible {
  background: var(--color-danger-light);
  color: var(--color-danger-text);
}

/* ── Panneau de réglages (brouillon) ───────────────────────
   Carte affichée en colonne latérale à côté de la grille de repas (voir
   .mc-split ci-dessous) : nombre de personnes et régime alimentaire, les
   deux réglages qui s'appliquent à tout le menu. Toujours empilée verticalement
   (desktop et mobile) — pas de bascule flex-direction à gérer en responsive. */
.mc-settings-panel {
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding: 20px;
  background: var(--color-bg-white);
  border: var(--border-thin);
  border-radius: var(--radius-card);
}

.mc-settings-panel-title {
  font-family: var(--font-serif);
  font-size: 17px;
  font-weight: 400;
  color: var(--color-ink);
  margin: 0;
}

/* Le titre est aussi le déclencheur de l'accordéon mobile (controller
   mobile-accordion). En desktop le JS le passe à disabled : il n'est ni
   cliquable ni focusable, et rend exactement comme un titre simple. */
.mc-settings-panel-toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  width: 100%;
  padding: 0;
  background: none;
  border: 0;
  font: inherit;
  color: inherit;
  text-align: left;
}

/* Le chevron n'a de sens que là où la carte se replie (mobile) */
.mc-settings-panel-chevron {
  display: none;
  color: var(--color-ink-3);
  transition: transform var(--transition-fast);
}

.mc-settings-panel-toggle[aria-expanded="false"] .mc-settings-panel-chevron {
  transform: rotate(-90deg);
}

/* Corps repliable : reprend l'empilement vertical de la carte */
.mc-settings-panel-body {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.mc-setting {
  display: flex;
  align-items: center;
  gap: 10px;
}

/* Réglage à plusieurs lignes (répartition des repas) : sa pastille se cale en
   haut, au niveau du libellé, plutôt qu'au milieu de la colonne de steppers. */
.mc-setting--stacked {
  align-items: flex-start;
}

/* Pastille d'icône — même vocabulaire que les cartes du formulaire de génération */
.mc-setting-icon {
  width: 32px;
  height: 32px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-card);
}

.mc-setting-icon--people {
  background: var(--color-category-icon-bg);
  color: var(--color-category-icon);
}

.mc-setting-icon--diet {
  background: var(--diet-vege-tint);
  color: var(--color-success);
}

/* Répartition des repas : l'ambre de la charte, seul accent coloré, distingue
   ce réglage des deux autres pastilles sans introduire de nouvelle teinte. */
.mc-setting-icon--meals {
  background: var(--color-accent-light);
  color: var(--color-accent-text);
}

/* flex: 1 pour que les trois réglages (régime, personnes, répartition)
   partagent la même largeur de champ malgré leurs contenus différents — c'est
   ce qui permet à leurs contrôles de se terminer sur le même bord droit. */
.mc-setting-body {
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}

.mc-setting-label {
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-ink-3);
}

/* Séparateur horizontal entre les blocs du panneau (note de portée, puis chaque
   réglage) */
.mc-settings-panel-sep {
  height: var(--border-width);
  background: var(--color-border);
}

/* Rappel de la portée des réglages, juste sous le titre : la consigne se lit
   avant les réglages qu'elle qualifie, et non après. */
.mc-settings-panel-note {
  display: flex;
  align-items: flex-start;
  gap: 6px;
  margin: 0;
  font-size: var(--font-size-xs);
  line-height: 1.5;
  color: var(--color-ink-3);
}

/* flex-shrink + margin-top : l'icône reste alignée sur la 1ère ligne du texte
   (et non centrée sur sa hauteur totale) quand la note tient sur 2 lignes. */
.mc-settings-panel-note svg {
  flex-shrink: 0;
  margin-top: 2px;
  color: var(--color-ink-4);
}

/* En desktop, la carte de réglages est côte à côte avec la grille de repas :
   elle s'aligne sur la hauteur d'une carte de recette (--mc-card-height,
   mesurée et publiée par menu_customize_controller) pour que les deux colonnes
   se terminent au même trait. min-height et non height : le contenu du panneau
   ne peut jamais être rogné si la carte de référence est plus courte. */
@media (min-width: 1025px) {
  .mc-settings-panel {
    min-height: var(--mc-card-height);
  }

  /* Le supplément de hauteur est réparti entre les blocs de réglages plutôt que
     laissé en blanc au bas de la carte. */
  .mc-settings-panel-body {
    flex: 1;
    justify-content: space-between;
  }
}

/* ── Disposition brouillon : réglages en colonne latérale ──
   Libère la largeur au-dessus de la grille de repas — le titre du menu n'est
   plus repoussé vers le bas par une barre de réglages pleine largeur. */
.mc-split {
  display: flex;
  align-items: flex-start;
  gap: 24px;
  margin-top: 24px;
}

/* 280px : la largeur qu'il faut pour que les lignes de répartition tiennent
   d'un trait — libellé du moment, puis stepper compact — sans grignoter de
   colonne à la grille de repas (les cartes font 190px minimum). */
.mc-split-aside {
  flex: 0 0 280px;
}

.mc-split-main {
  flex: 1;
  min-width: 0;
}

/* Sous le --breakpoint-tablet (1024px), la carte de réglages n'a plus la
   place de cohabiter avec la grille : elle repasse au-dessus, pleine largeur.
   (valeur dupliquée en dur : les media features n'acceptent pas var()) */
@media (max-width: 1024px) {

  /* Empilée, la carte de réglages pousse déjà la grille de repas vers le bas :
     l'écart avec l'en-tête (.mc-head, margin-bottom) suffit. */
  .mc-split {
    flex-direction: column;
    margin-top: 0;
  }

  .mc-split-aside {
    flex-basis: auto;
    width: 100%;
  }

  /* Accordéon : le titre plie / déplie la carte pour que la grille de repas
     remonte dans la page (voir mobile_accordion_controller.js). */
  .mc-settings-panel-chevron {
    display: inline-block;
  }

  /* Le déclencheur occupe toute la largeur de la carte : cible tactile pleine.
     L'affordance (curseur) n'est posée qu'ici, là où le titre est cliquable. */
  .mc-settings-panel-toggle {
    min-height: var(--min-touch-target);
    cursor: pointer;
  }

  .mobile-accordion--collapsed .mc-settings-panel-body {
    display: none;
  }

  /* Replié, le titre n'introduit plus rien : son trait de séparation n'a plus
     d'objet et la carte se réduit à une simple barre. */
  .mobile-accordion--collapsed .mc-settings-panel-title {
    padding-bottom: 0;
    border-bottom: 0;
  }

  /* Réduite à sa barre de titre, la carte n'a plus de contenu à espacer
     verticalement : la hauteur de la barre vient du déclencheur (cible
     tactile), le padding ne garde que ses marges latérales. */
  .mobile-accordion--collapsed .mc-settings-panel {
    padding: 0 1rem;
  }

  /* Replier la carte sert à faire remonter la grille de repas : l'écart sous
     l'en-tête y participe. Il est rétabli au dépliage, où la carte redevient un
     bloc de contenu à part entière (voir .mc-split, margin-top ci-dessus). */
  .mc-head:has(+ .mc-split .mobile-accordion--collapsed) {
    margin-bottom: 0;
  }
}

/* ── Manques du brouillon (UC7) ─────────────────────────── */
/* Une ligne compacte au-dessus de la grille : ce n'est pas une erreur mais un
   état du menu, d'où le ton ambre de la charte plutôt qu'un bandeau rouge.
   Le display: flex ci-dessous écrase le display: none que le navigateur pose
   sur [hidden] : sans repli explicite, l'élément vide resterait visible. */
.mc-missing-alert {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 0 0 12px;
  padding: 7px 10px;
  background: var(--color-warning-light);
  border-radius: var(--radius-button);
  font-size: var(--font-size-sm);
  line-height: 1.4;
  color: var(--color-warning-text);
}

.mc-missing-alert[hidden] {
  display: none;
}

.mc-missing-alert svg {
  flex-shrink: 0;
}

/* ── Card grid ─────────────────────────────────────────── */
/* auto-fill plutôt qu'un repeat figé : la grille s'adapte à la largeur
   disponible, que la colonne soit pleine largeur (menu actif) ou réduite par
   le panneau de réglages latéral (.mc-split-main, brouillon). minmax(190px)
   agrandit les cartes en desktop par rapport au gabarit mobile (160px,
   ci-dessous) — le mobile garde son propre minmax et n'est pas affecté. */
.mc-card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: 14px;
  margin-bottom: 24px;
}

/* ── Palette des jours (UC7) ─────────────────────────────
   SOURCE UNIQUE de vérité des couleurs de jour : partout ailleurs, uniquement
   var(--day-accent) & co. Indices = Date#wday (0 = dimanche … 6 = samedi),
   l'entier de MenuRecipe#day_of_week que la carte porte en data-day.

   Teintes générées en OKLCH à luminosité et chroma constants — seule la teinte
   tourne, du froid (lundi) au chaud (dimanche). Aucun jour ne « saute » donc
   plus que les autres, et le texte de la pastille tient partout entre 6,9:1 et
   7,6:1 sur son fond. Converties en sRGB : le projet est en CSS vanilla, et une
   déclaration oklch() invalide ferait retomber les sept jours sur le même gris.

   La charte reste anthracite + ambre : la couleur ne lave plus la carte entière,
   elle se concentre sur la bordure latérale et la pastille du sélecteur de jour.
   Elle ne porte jamais l'information seule — le jour reste écrit en toutes
   lettres dans ce sélecteur, juste à côté.

   Quatre rôles : --day-accent (bordure latérale de la carte, seul aplat vif),
   --day-tint (fond de pastille), --day-deep (texte sur la pastille),
   --day-edge (bordure de la pastille, et de la carte au survol). */

/* Jour non renseigné : les valeurs neutres sont portées par la carte elle-même.
   Couvre du même coup l'absence d'attribut (day_of_week nil, ou repassé à « — » :
   menu-customize#setDay supprime alors data-day) et un data-day="" — inutile de
   sélectionner les deux. */
.mc-recipe-card {
  --day-tint: var(--color-bg-secondary);
  --day-edge: var(--color-border-strong);
  --day-accent: var(--color-border-strong);
  --day-deep: var(--color-ink-2);
}

/*                                   tint                edge                accent              deep    */
.mc-recipe-card[data-day="1"] { --day-tint: #E7F5FF; --day-edge: #C5DEFF; --day-accent: #719AD6; --day-deep: #2D5083; } /* lundi    */
.mc-recipe-card[data-day="2"] { --day-tint: #DEF9FF; --day-edge: #B0E7F3; --day-accent: #3CA8BC; --day-deep: #005C6E; } /* mardi    */
.mc-recipe-card[data-day="3"] { --day-tint: #E2FAEE; --day-edge: #B9E9D3; --day-accent: #55AC89; --day-deep: #006042; } /* mercredi */
.mc-recipe-card[data-day="4"] { --day-tint: #F1F7E0; --day-edge: #D8E2B7; --day-accent: #93A157; --day-deep: #4B560A; } /* jeudi    */
.mc-recipe-card[data-day="5"] { --day-tint: #FFF1DE; --day-edge: #F5D7B3; --day-accent: #BE8E4F; --day-deep: #6F4500; } /* vendredi */
.mc-recipe-card[data-day="6"] { --day-tint: #FFEDE7; --day-edge: #FFCFC5; --day-accent: #CE8171; --day-deep: #7B392D; } /* samedi   */
.mc-recipe-card[data-day="0"] { --day-tint: #FFECF5; --day-edge: #FCCDE0; --day-accent: #C87E9F; --day-deep: #763755; } /* dimanche */

/* ── Palette des régimes & de la pastille de catégorie ───
   Mêmes raisons que les couleurs de jour ci-dessus : tous leurs points de
   consommation sont dans ce fichier, elles n'ont donc rien à faire dans la
   palette globale. Même convention de nommage aussi — « tint » désigne le fond
   d'une pastille — plutôt qu'un second vocabulaire à quelques lignes d'écart.

   Teintes générées en OKLCH à luminosité et chroma constants (L 0.955 / C 0.030),
   seule la teinte tourne : aucun régime ne « saute » plus que les autres, ce que
   les anciennes valeurs ne garantissaient pas. Converties en sRGB, pour la raison
   déjà donnée plus haut — une déclaration oklch() invalide ferait retomber tous
   les régimes sur le même gris. */
:root {
  --diet-omni-tint:  #FFE9E6;  /* oklch(0.955 0.030  25) */
  --diet-vege-tint:  #EBF4DE;  /* oklch(0.955 0.030 125) */
  --diet-vegan-tint: #DFF7EB;  /* oklch(0.955 0.030 165) */
  --diet-pesc-tint:  #E0F3FF;  /* oklch(0.955 0.030 245) */

  /* Pastille du compteur de personnes. Nommée par son rôle et non par sa
     teinte : le jour où elle passe au bleu, un token « violet » deviendrait un
     mensonge impossible à corriger sans rename global. */
  --color-category-icon: #7C3AED;
  --color-category-icon-bg: #EDE9FE;
}

/* ── Recipe card ───────────────────────────────────────── */
.mc-recipe-card {
  /* Colonne flex pour que la barre d'actions se cale en bas (margin-top: auto).
     Les cartes d'une même ligne ayant déjà la même hauteur (elles sont les items
     de la grille, cf. display: contents du turbo-frame), leurs barres d'actions
     s'alignent alors aussi, que le titre tienne sur une ou deux lignes. */
  display: flex;
  flex-direction: column;
  background: var(--color-bg-white);
  border: var(--border-thin);
  /* Toute la couleur du jour tient dans cette barre. */
  border-left: 5px solid var(--day-accent);
  border-radius: var(--radius-button);
  overflow: hidden;
  cursor: grab;
  user-select: none;
  transition: border-color var(--transition-fast), opacity 0.2s, transform var(--transition-fast);
}

.mc-recipe-card:hover {
  border-color: var(--day-edge);
  /* La barre du jour ne bouge pas au survol : c'est un repère, pas un état. */
  border-left-color: var(--day-accent);
}

/* Turbo frame wrapper ne doit pas casser le grid */
.mc-card-grid turbo-frame {
  display: contents;
}

/* Drag states */
.mc-recipe-card.mc-dragging {
  opacity: 0.4;
  cursor: grabbing;
  transform: scale(0.97);
  border-color: var(--color-ink-4);
}

/* Cible de dépôt : on repeint les bordures sans toucher à leurs épaisseurs — un
   raccourci `border` ramènerait la barre latérale de 5px à 1px et décalerait le
   contenu de la carte à chaque survol pendant le glissement. */
.mc-recipe-card.mc-drag-over {
  border-color: var(--color-accent-deep);
  border-left-color: var(--color-accent-deep);
  background: var(--color-accent-light);
}

/* ── Photo carrée ──────────────────────────────────────── */
/* Enveloppe de la photo (UC7) : repère de positionnement du bouton Dupliquer,
   qui se superpose à la photo sans pouvoir vivre dans son lien (<form> dans
   <a>). Sans ce parent, il se réglerait sur toute la hauteur de la carte. La
   poignée de glissement et le badge temps, eux, vivent DANS le lien et se
   règlent sur .mc-card-photo. */
.mc-card-media {
  position: relative;
}

.mc-card-photo {
  width: 100%;
  aspect-ratio: 1 / 0.8;
  background: var(--color-bg-secondary);
  overflow: hidden;
  position: relative;
  display: block;
}

.mc-card-photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  pointer-events: none;
}

/* Drag handle */
.mc-drag-hint {
  position: absolute;
  top: 5px;
  left: 5px;
  opacity: 0;
  transition: opacity var(--transition-fast);
  background: rgba(28, 25, 22, 0.65);
  border-radius: 2px;
  padding: 3px 4px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.mc-recipe-card:hover .mc-drag-hint {
  opacity: 1;
}

.mc-drag-hint svg {
  width: 10px;
  height: 10px;
  color: var(--color-bg-white);
}

/* ⏱️ Badge temps (UC7) : pastille sur la photo, toujours visible (contrairement
   au drag-hint qui n'apparaît qu'au survol) — même habillage sombre translucide. */
.mc-card-time-badge {
  position: absolute;
  top: 5px;
  right: 5px;
  background: rgba(28, 25, 22, 0.65);
  color: var(--color-bg-white);
  border-radius: var(--radius-badge);
  padding: 2px 6px;
  display: flex;
  align-items: center;
  gap: 3px;
  font-size: var(--font-size-xs);
  font-weight: 500;
  line-height: 1.3;
}

.mc-card-time-badge svg {
  width: 10px;
  height: 10px;
  flex-shrink: 0;
}

/* ⧉ Dupliquer (UC7) : répéter un repas dans la semaine. Coin bas-droit de la
   photo — le haut est pris par la poignée (gauche) et le badge temps (droite).
   Toujours visible comme le badge, et non au survol comme la poignée : le
   glisser-déposer n'existe qu'à la souris, dupliquer sert aussi au doigt.
   Même habillage sombre translucide que les deux autres surimpressions. */
.mc-card-dup {
  position: absolute;
  bottom: 5px;
  right: 5px;
  border: none;
  /* 7px + 14px d'icône = 28px, la taille des boutons de la barre d'actions :
     même confort au doigt, sans mesure propre à ce bouton. */
  padding: 7px;
  border-radius: var(--radius-badge);
  background: rgba(28, 25, 22, 0.65);
  color: var(--color-bg-white);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background var(--transition-fast);
}

.mc-card-dup svg {
  width: 14px;
  height: 14px;
}

.mc-card-dup:hover {
  background: rgba(28, 25, 22, 0.9);
}

.mc-card-dup:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 1px;
}

/* ── Nom de la recette ─────────────────────────────────────
   Wrapper partagé avec la carte en lecture seule (menu actif) : il porte le
   retrait du bloc, le nom porte son propre écrêtage à 2 lignes. */
.mc-card-name-wrap {
  padding: 7px 8px 0;
}

.mc-card-name {
  font-size: var(--font-size-xs);
  font-weight: 500;
  color: var(--color-ink);
  line-height: 1.35;
  display: -webkit-box;
  line-clamp: 2;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  min-height: 28px;
  /* Équilibre la coupure entre les deux lignes plutôt que de remplir la première
     au maximum : évite le mot orphelin sur la seconde ligne et les coupures en
     milieu de syntagme (« Tartelette au chèvre / sur »). Sans effet là où le
     navigateur ne le gère pas encore. */
  text-wrap: pretty;
}

/* Photo et nom : liens vers la recette (menu_card_recipe_link) — même habillage
   qu'un bloc inerte, sans soulignement ; seul le nom se souligne au survol
   pour annoncer le clic. */
a.mc-card-photo,
a.mc-card-name-wrap {
  display: block;
  color: inherit;
  text-decoration: none;
}

a.mc-card-name-wrap:hover .mc-card-name {
  text-decoration: underline;
}

/* ── Rangée moment + jour (UC7) ─────────────────────────────
   Les deux contrôles se partagent la ligne à parts égales : flex: 1 1 0 sur
   les enfants directs (formulaire d'un sélecteur, ou étiquette figée du menu
   actif) leur donne la même largeur quel que soit le libellé, et min-width: 0
   les autorise à se comprimer sous la largeur naturelle du <select>.
   Habillage mc-mini-select commun. */
.mc-card-controls {
  display: flex;
  gap: 5px;
  padding: 6px 8px 0;
}

.mc-card-control {
  flex: 1 1 0;
  min-width: 0;
}

/* Corps compact conservé même en desktop (où les autres mini-selects grandissent) :
   deux sélecteurs pour une largeur de carte, « Petit-déj » doit tenir en entier. */
.mc-card-controls .mc-mini-select {
  width: 100%;
  font-size: var(--font-size-xs);
}

/* ── Barre d'actions (3 éléments) ──────────────────────── */
.mc-card-actions {
  /* Cale la barre en bas de la carte (colonne flex), quelle que soit la hauteur
     du titre au-dessus : les barres d'une même ligne s'alignent entre elles. */
  margin-top: auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 6px 7px 7px;
  gap: 5px;
}

/* Les formulaires dans la barre d'actions disparaissent du layout */
.mc-card-actions form,
.mc-act-form {
  display: contents;
}

/* Bouton d'action icône (remplacer / supprimer) */
.mc-act-btn {
  width: 28px;
  height: 28px;
  border-radius: var(--radius-button);
  border: var(--border-thin);
  background: var(--color-bg-white);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  transition: all 0.12s;
  padding: 0;
}

.mc-act-btn svg {
  width: 17px;
  height: 17px;
  color: var(--color-ink-3);
  flex-shrink: 0;
}

.mc-act-btn:hover:not(:disabled) {
  background: var(--color-bg-white);
  border-color: var(--color-border-strong);
  box-shadow: var(--shadow-sm);
}

.mc-act-btn:hover:not(:disabled) svg {
  color: var(--color-ink);
}

/* Focus clavier explicite (les libellés aria sont annoncés en parallèle) */
.mc-act-btn:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 1px;
  border-color: var(--color-primary);
}

.mc-act-btn:focus-visible svg {
  color: var(--color-ink);
}

/* Aux bornes de section, ⬆️/⬇️ sont désactivés plutôt que masqués (UC7) : même
   traitement que le stepper de personnes (.mc-stepper-btn:disabled). */
.mc-act-btn:disabled {
  opacity: 0.3;
  cursor: not-allowed;
}

/* Variante danger (supprimer) */
.mc-del:hover {
  background: var(--color-danger-light);
  border-color: var(--color-danger-border);
}

.mc-del:hover svg {
  color: var(--color-danger);
}

/* ⬆️/⬇️ réordonnancement dans la section (UC7) : le drag & drop HTML5 suffit
   là où la souris est disponible, ces boutons ne servent qu'au tactile.
   Une seule icône (chevron-down) pour les deux sens, la variante « monter »
   pivote à 180° — même idiome que .mi-expand-icon.open. */
.mc-move-btn {
  display: none;
}

.mc-move-btn--up svg {
  transform: rotate(180deg);
}

/* Mini-selects de carte (personnes / type / jour, UC7) : même habillage —
   bordure, flèche, hauteur — largeur propre à chaque variante ci-dessous. */
.mc-mini-select {
  height: 28px;
  border: var(--border-thin);
  border-radius: var(--radius-button);
  background: var(--color-bg-white);
  color: var(--color-ink);
  font-size: var(--font-size-xs);
  font-weight: 500;
  padding: 0 10px 0 4px;
  cursor: pointer;
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg width='8' height='5' viewBox='0 0 8 5' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1l3 3 3-3' stroke='%23A8A29E' stroke-width='1.2' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 5px center;
}

.mc-mini-select:hover {
  border-color: var(--color-border-strong);
}

.mc-mini-select:focus {
  outline: none;
  border-color: var(--color-ink);
}

.mc-mini-select:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 1px;
  border-color: var(--color-primary);
}

/* Moment figé d'un menu validé : même gabarit que le mini-select avec lequel
   il partage la rangée (mêmes hauteurs à chaque breakpoint), mais aucune
   affordance d'interaction — ni flèche, ni curseur. Fond blanc et encre
   soutenue héritées de .mc-mini-select : c'est une étiquette en lecture seule,
   pas un contrôle désactivé — le gris clair aurait dit l'inverse. */
.mc-mini-select--static {
  display: flex;
  align-items: center;
  gap: 4px;
  background-image: none;
  color: var(--color-ink-2);
  cursor: default;
}

/* Sélecteur de jour : seul mini-select à porter la couleur du jour, puisque
   c'est lui qui la pilote. Déclaré après .mc-mini-select — même spécificité,
   c'est l'ordre qui tranche. */
.mc-day-select {
  background-color: var(--day-tint);
  border-color: var(--day-edge);
  color: var(--day-deep);
}

.mc-day-select:hover {
  border-color: var(--day-accent);
}

/* Select nombre de personnes : inline dans la rangée d'actions, largeur réduite. */
.mc-ppl-select {
  min-width: 52px;
  flex: 1;
  max-width: 64px;
}

/* ── Bouton « Ajouter un repas » ────────────────────────── */
/* Dernier item de la grille, toujours à la suite du dernier repas : il ouvre le
   catalogue, où les filtres choisissent le moment voulu. Il occupe la hauteur
   d'une carte, mesurée et publiée en variable par menu_customize_controller. */
.mc-add-btn {
  /* Sans carte de référence sur la page (menu vide) la variable n'est pas
     publiée : la déclaration est invalide, la hauteur retombe sur le contenu. */
  height: var(--mc-card-height);
  background: var(--color-bg-white);
  border: var(--border-width) dashed var(--color-border-strong);
  border-radius: var(--radius-button);
  cursor: pointer;
  transition: border-color var(--transition-fast), background var(--transition-fast);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 5px;
  width: 100%;
  text-decoration: none;
  padding: 14px 6px;
}

.mc-add-btn:hover {
  border-color: var(--color-ink-4);
  background: var(--color-bg-secondary);
}

.mc-add-btn svg {
  width: 15px;
  height: 15px;
  color: var(--color-ink-4);
  flex-shrink: 0;
}

.mc-add-btn span {
  font-size: var(--font-size-xs);
  color: var(--color-ink-3);
  text-align: center;
  line-height: 1.3;
  padding: 0 6px;
}

.mc-add-btn:hover svg,
.mc-add-btn:hover span {
  color: var(--color-ink-2);
}

/* ── Empty grid ────────────────────────────────────────── */
.mc-empty-grid {
  grid-column: 1 / -1;
  text-align: center;
  padding: 2rem;
  color: var(--color-ink-3);
  background: var(--color-bg-secondary);
  border-radius: var(--radius-card);
  border: var(--border-width) dashed var(--color-border-strong);
}

.mc-empty-text {
  font-weight: 500;
  color: var(--color-ink-2);
}

/* L'invitation au catalogue de l'état vide respire sous son texte */
.mc-empty-grid .btn {
  margin-top: 12px;
}

/* ── Bouton de validation ──────────────────────────────── */
.mc-validate-zone {
  text-align: center;
}

/* Sticky en bas quand le menu contient au moins un repas : le CTA reste
   visible pendant le défilement de la grille. Bande de fond + bordure
   haute pour détacher la zone du contenu qui défile dessous. */
.mc-validate-zone--sticky {
  position: sticky;
  bottom: 0;
  z-index: 10;
  padding: 14px 0;
  margin-top: 4px;
  background: var(--color-bg-main);
}

/* Le form de button_to doit rester inline */
.mc-validate-zone form.button_to,
.mc-validate-zone form {
  display: inline;
}

.mc-btn-validate {
  background: var(--color-primary);
  color: var(--color-bg-white);
  border: none;
  border-radius: var(--radius-button);
  padding: 13px 32px;
  font-size: var(--font-size-sm);
  font-weight: 500;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  letter-spacing: 0.02em;
  transition: background var(--transition-fast);
}

.mc-btn-validate:hover {
  background: var(--color-primary-hover);
}

.mc-btn-validate:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.mc-btn-validate svg {
  width: 15px;
  height: 15px;
  flex-shrink: 0;
}

/* ── Agrandissement desktop des cartes de recette (mc-*) ──
   La grille (minmax 190px ci-dessus) agrandit déjà la photo carrée ; on
   remonte ici les éléments internes (nom, barre d'actions) dans la
   même proportion pour que la carte agrandie ne paraisse pas déséquilibrée.
   Scindé du reste des styles mc-* pour laisser le mobile (2 colonnes,
   gabarit compact ci-dessous) strictement inchangé. */
@media (min-width: 768px) {
  .mc-card-name-wrap {
    padding: 10px 10px 0;
  }

  .mc-card-name {
    font-size: var(--font-size-sm);
    min-height: 38px;
  }

  .mc-recipe-card.mc-recipe-card--readonly {
    padding-bottom: 10px;
  }

  .mc-card-controls {
    padding: 6px 10px 0;
    gap: 6px;
  }

  .mc-card-actions {
    padding: 8px 10px 10px;
    gap: 8px;
  }

  .mc-act-btn {
    width: 32px;
    height: 32px;
  }

  .mc-act-btn svg {
    width: 19px;
    height: 19px;
  }

  /* Suit l'agrandissement des cartes, comme les boutons de la barre d'actions */
  .mc-card-dup svg {
    width: 17px;
    height: 17px;
  }

  .mc-mini-select {
    height: 32px;
    font-size: var(--font-size-sm);
    padding: 0 14px 0 8px;
    background-position: right 7px center;
  }

  .mc-ppl-select {
    min-width: 68px;
    max-width: 92px;
  }
}

/* ── Responsive (mc-*) ─────────────────────────────────── */
@media (max-width: 767px) {

  .mc-head-title,
  .mc-head-title-input {
    font-size: 1.1rem;
  }

  .mc-page-outer {
    padding: 16px 16px 48px;
  }

  /* Moins d'espace sous le titre sur mobile : la liste de courses doit
     démarrer plus haut, l'écran étant plus petit qu'en desktop. */
  .mc-page-header {
    margin-bottom: 10px;
  }

  .mc-page-wrap {
    width: 100%;
    min-width: 0;
  }

  /* Cartes assez larges (≥160px) pour que la barre d'actions tienne sans être
     coupée : le 110px précédent générait 3 colonnes sur les téléphones 390–430px
     et tronquait le bouton Supprimer (clippé par overflow:hidden de la carte). */
  .mc-card-grid {
    grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
    gap: 8px;
  }

  /* Barre d'actions au doigt : cibles 44px + retour à la ligne de sécurité, pour
     ne jamais tronquer un bouton même sur les écrans très étroits (≤360px). */
  .mc-card-actions {
    flex-wrap: wrap;
    justify-content: center;
  }

  .mc-act-btn {
    width: var(--min-touch-target);
    height: var(--min-touch-target);
  }

  /* ⬆️/⬇️ ne servent qu'au tactile : le drag & drop HTML5 couvre le desktop. */
  .mc-move-btn {
    display: flex;
  }

  .mc-mini-select {
    height: var(--min-touch-target);
    font-size: 12px;
    /* évite aussi le zoom iOS au focus */
  }

  /* Réglages du menu au doigt : stepper et sélecteur de régime à hauteur de cible */
  .mc-stepper,
  .mc-diet-toggle {
    height: var(--min-touch-target);
  }

  .mc-stepper-btn {
    width: 40px;
    height: 40px;
    font-size: 18px;
  }

  .mc-btn-validate {
    width: 100%;
    justify-content: center;
  }

  /* Actions d'en-tête au doigt. On ne masque jamais une action — « Modifier ce
     menu » (revert_to_draft) n'existe QUE là : la masquer rendrait impossible
     de repasser un menu actif en brouillon sur mobile. */
  .mc-head-action {
    min-height: var(--min-touch-target);
    padding: 8px 12px;
    font-size: var(--font-size-base);
  }

  /* Le bouton le plus à gauche du groupe s'aligne sur le titre : son padding
     horizontal est compensé pour que le texte tombe pile au bord. */
  .mc-head-meta-actions {
    margin-left: -12px;
  }
}

/* ============================================
   Polish mobile — cibles tactiles résiduelles (lot 4)
   ============================================ */
@media (max-width: 767px) {

  /* Index : CTA de création + croix de fermeture de la modale */
  .mi-btn-new {
    min-height: var(--min-touch-target);
    padding: 11px 18px;
  }

  .mn-modal-close {
    min-width: var(--min-touch-target);
    min-height: var(--min-touch-target);
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }

  /* Boutons des cartes brouillon & historique : cible + espacement au doigt */
  .mi-btn-sm,
  .mi-btn-sm-danger {
    min-height: var(--min-touch-target);
    padding: 8px 12px;
    font-size: var(--font-size-sm);
  }

  /* Suppression : icône seule → cible tactile carrée en conservant les 2px autour. */
  .mi-btn-sm-danger {
    padding: 2px;
    min-width: var(--min-touch-target);
  }

  .mi-draft-card-actions,
  .mi-history-card-actions {
    gap: 8px;
  }

  /* Carte « à valider » : les 3 actions restent sur une seule ligne, poubelle
     comprise. Les 2 boutons libellés se compriment jusqu'à leur mot le plus
     long (le libellé passe alors sur 2 lignes) — on ne descend pas en dessous,
     donc aucun texte ne déborde de son bouton. */
  .mi-draft-card-actions {
    flex-wrap: nowrap;
  }

  /* Padding latéral réduit : le libellé récupère la place ainsi gagnée. */
  .mi-draft-card-actions .mi-btn-sm {
    padding: 8px 6px;
    justify-content: center;
    text-align: center;
  }

  /* L'icône n'est jamais écrasée par la compression du libellé voisin. */
  .mi-draft-card-actions .mi-btn-sm svg {
    flex-shrink: 0;
  }

  /* button_to enveloppe la poubelle dans un <form> : c'est lui l'enfant flex.
     Il garde sa largeur pleine (cible tactile de 44px intacte). */
  .mi-draft-card-actions form.button_to {
    flex-shrink: 0;
  }

  /* Actions du hero « menu actif » */
  .mi-btn-hero-primary,
  .mi-btn-hero-ghost,
  .mi-btn-hero-danger {
    height: var(--min-touch-target);
  }

  /* Icône seule : garantir aussi une cible tactile assez large. */
  .mi-btn-hero-danger {
    min-width: var(--min-touch-target);
  }

  /* Bouton d'ajout de repas (brouillon) */
  .mc-add-btn {
    min-height: var(--min-touch-target);
  }

  /* Sliders (nb personnes / nb repas) : préhension plus large au doigt */
  .mn-slider {
    height: 6px;
  }

  .mn-slider::-webkit-slider-thumb {
    width: 26px;
    height: 26px;
  }

  .mn-slider::-moz-range-thumb {
    width: 26px;
    height: 26px;
  }

  /* CTA « Valider » collant : ne pas friser le home-indicator iOS */
  .mc-validate-zone--sticky {
    padding-bottom: calc(14px + env(safe-area-inset-bottom));
  }
}

/* ============================================
   CONTRÔLES DES RÉGLAGES DU MENU (mc-diet-*, mc-stepper-*)
   Sélecteur de régime et stepper « personnes » du panneau .mc-settings-panel
   (vue brouillon). Design Slate Craft Premium.
   ============================================ */

/* Déclencheur du sélecteur de régime — même vocabulaire visuel que les selects
   de l'app (fond blanc, bordure fine, chevron à droite). */
.mc-diet-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-width: 150px;
  height: 32px;
  padding: 0 10px;
  background: var(--color-bg-white);
  border: var(--border-thin);
  border-radius: var(--radius-button);
  font-size: var(--font-size-sm);
  font-weight: 500;
  color: var(--color-ink);
  cursor: pointer;
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}

.mc-diet-toggle:hover {
  border-color: var(--color-border-strong);
  box-shadow: var(--shadow-sm);
}

.mc-diet-toggle:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 1px;
  border-color: var(--color-primary);
}

.mc-diet-chevron {
  opacity: 0.55;
  transition: transform 0.15s;
  flex-shrink: 0;
}

.mc-diet-toggle[aria-expanded="true"] .mc-diet-chevron {
  transform: rotate(180deg);
}

/* Dropdown du sélecteur de régime — le picker occupe toute la largeur du champ
   (héritée de .mc-setting-body) et pousse le déclencheur sur le bord droit,
   aligné avec les autres réglages du panneau. */
.mc-diet-picker {
  position: relative;
  display: flex;
  justify-content: flex-end;
}

.mc-diet-options {
  position: absolute;
  top: calc(100% + 4px);
  right: 0;
  background: var(--color-bg-white);
  border: 0.5px solid var(--color-border);
  border-radius: 4px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
  min-width: 160px;
  z-index: 200;
  padding: 4px 0;
}

.mc-diet-opt,
.mc-diet-opt--current {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 9px 14px;
  font-size: 12.5px;
  color: var(--color-ink-2);
  background: none;
  border: none;
  width: 100%;
  text-align: left;
  cursor: pointer;
  transition: background var(--transition-fast);
  white-space: nowrap;
}

.mc-diet-opt:hover {
  background: var(--color-bg-secondary);
}

.mc-diet-opt--current {
  font-weight: 600;
  color: var(--color-ink);
  cursor: default;
}

.mc-diet-opt-form {
  display: block;
}

/* Stepper nombre de personnes — occupe toute la largeur du champ et pousse le
   stepper sur le bord droit, aligné avec les autres réglages du panneau. */
.mc-stepper-form {
  display: inline-flex;
  align-items: center;
  justify-content: flex-end;
}

.mc-stepper {
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  min-width: 150px;
  height: 32px;
  padding: 0 4px;
  background: var(--color-bg-white);
  border: var(--border-thin);
  border-radius: var(--radius-button);
}

/* Variante sans unité, pour les listes de steppers où le libellé est déjà
   porté par la ligne (répartition des repas du formulaire de menu et des
   réglages du foyer). */
.mc-stepper--compact {
  min-width: 96px;
}

/* Les deux boutons ont la même largeur : avec justify-content: space-between,
   ce groupe (valeur + unité) se retrouve exactement centré dans la largeur
   étendue du stepper (alignée sur .mc-diet-toggle). */
.mc-stepper-value {
  display: flex;
  align-items: center;
  gap: 2px;
}

.mc-stepper-btn {
  background: none;
  border: none;
  color: var(--color-ink-2);
  font-size: 16px;
  line-height: 1;
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  border-radius: var(--radius-badge);
  padding: 0;
  transition: background var(--transition-fast);
  flex-shrink: 0;
}

.mc-stepper-btn:hover:not(:disabled) {
  background: var(--color-bg-tertiary);
}

.mc-stepper-btn:disabled {
  opacity: 0.3;
  cursor: not-allowed;
}

.mc-stepper-val {
  font-size: var(--font-size-sm);
  font-weight: 600;
  color: var(--color-ink);
  min-width: 16px;
  text-align: center;
}

.mc-stepper-label {
  font-size: var(--font-size-xs);
  color: var(--color-ink-3);
  letter-spacing: 0.02em;
  padding-right: 2px;
}

/* ============================================
   MÉMORISER LES PRÉFÉRENCES (mn-save-defaults-*)
   ============================================ */

.mn-save-defaults {
  padding: 14px 20px;
  background: var(--color-bg-secondary);
  border-radius: 4px;
  margin: 0 0 8px;
}

.mn-save-defaults-label {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  cursor: pointer;
  font-size: 13px;
  color: var(--color-ink-2);
}

.mn-save-defaults-checkbox {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  margin-top: 2px;
  accent-color: var(--color-primary);
  cursor: pointer;
}

.mn-save-defaults-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.mn-save-defaults-title {
  font-weight: 500;
  color: var(--color-ink);
}

.mn-save-defaults-hint {
  font-size: 12px;
  color: var(--color-ink-3);
  line-height: 1.4;
}

/* ============================================
   PRÉFÉRENCES PROFIL (pref-*)
   ============================================ */

.pref-page-wrap {
  max-width: 680px;
  margin: 0 auto;
  padding: 32px 24px 80px;
}

/* Encadrement carte sur desktop */
@media (min-width: 768px) {
  .pref-page-wrap {
    margin-top: 25px;
    padding: 40px 48px 56px;
    background: var(--color-bg-white);
    border: 0.5px solid var(--color-border);
    border-radius: 6px;
  }
}

.pref-header {
  margin-bottom: 27px;
}

.pref-title {
  font-family: var(--font-serif);
  font-size: 26px;
  font-weight: 400;
  color: var(--color-ink);
  margin: 0 0 8px;
}

.pref-subtitle {
  font-size: 14px;
  color: var(--color-ink-3);
  line-height: 1.5;
  margin: 0;
}

.pref-form {
  display: flex;
  flex-direction: column;
  gap: 32px;
}

.pref-section {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.pref-section-title {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-ink-3);
  margin: 0;
  padding-bottom: 10px;
  border-bottom: 0.5px solid var(--color-border);
}

/* Grille régime : 2 colonnes desktop, 1 sur mobile */
.pref-diet-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}

@media (max-width: 480px) {
  .pref-diet-grid {
    grid-template-columns: 1fr;
  }
}

.pref-diet-option {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 16px;
  border: 0.5px solid var(--color-border);
  border-radius: 4px;
  cursor: pointer;
  transition: border-color var(--transition-fast), background var(--transition-fast);
  background: var(--color-bg-white);
  position: relative;
}

.pref-diet-option:hover {
  border-color: var(--color-ink-4);
  background: var(--color-bg-secondary);
}

.pref-diet-radio {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
  pointer-events: none;
}

.pref-diet-body {
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex: 1;
  min-width: 0;
}

.pref-diet-name {
  font-size: 13px;
  font-weight: 500;
  color: var(--color-ink);
}

.pref-diet-desc {
  font-size: 11.5px;
  color: var(--color-ink-3);
}

.pref-diet-check {
  color: var(--color-ink);
  opacity: 0;
  transition: opacity var(--transition-fast);
  flex-shrink: 0;
}

/* État sélectionné : piloté par le radio natif, donc toujours exact — au clic
   sur le label comme au clavier, sans classe à poser ni à retirer. */
.pref-diet-option:has(.pref-diet-radio:checked) {
  border-color: var(--color-primary);
  background: var(--color-bg-secondary);
}

.pref-diet-option:has(.pref-diet-radio:checked) .pref-diet-check {
  opacity: 1;
}

/* Champ numérique (personnes / repas) */
.pref-number-row {
  display: flex;
  align-items: center;
  gap: 12px;
}

.pref-number-input {
  width: 80px;
  padding: 8px 12px;
  border: 0.5px solid var(--color-border);
  border-radius: 4px;
  font-size: 15px;
  font-weight: 500;
  color: var(--color-ink);
  background: var(--color-bg-white);
  text-align: center;
}

.pref-number-input:focus {
  outline: 2px solid var(--color-primary);
  outline-offset: 1px;
  border-color: transparent;
}

.pref-number-label {
  font-size: 13px;
  color: var(--color-ink-3);
}

.pref-actions {
  display: flex;
  gap: 12px;
  align-items: center;
  padding-top: 8px;
}

@media (max-width: 480px) {
  .pref-actions {
    flex-direction: column;
    align-items: stretch;
  }

  .pref-actions .btn {
    text-align: center;
    justify-content: center;
  }
}
/* ============================================
   GROCERY ITEMS - Styles UC3 (liste de courses)
   ============================================
   Vue active du menu : liste groupée par rayon,
   toggle coché, ajout manuel.
   Design Slate Craft Premium.
   ============================================ */

/* ── Contrôles liste (tout fermer/ouvrir) ───────────────── */
.grocery-list-controls {
  display: flex;
  justify-content: flex-end;
  margin-bottom: 0.75rem;
}

.btn-text-sm {
  background: none;
  border: none;
  cursor: pointer;
  font-size: var(--font-size-sm);
  color: var(--color-ink-3);
  padding: 0.25rem 0;
  text-decoration: underline dotted;
  line-height: 1;
}

.btn-text-sm:hover {
  color: var(--color-ink);
}

/* ── Sections par rayon (accordéon) ─────────────────────── */
/* La couleur du rayon vient de la palette partagée (--category-color,
   components.css), posée sur le data-category que porte déjà la section. */
.grocery-section {
  margin-bottom: 1.75rem;
}

.grocery-section-header {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  background: none;
  border: none;
  border-bottom: var(--border-thin);
  border-left: 2.5px solid var(--category-color);
  cursor: pointer;
  padding: 0 0 0.375rem 0.625rem;
  margin: 0 0 0.5rem;
  text-align: left;
  transition: opacity var(--transition-fast);
}

.grocery-section-header:hover {
  opacity: 0.75;
}

.grocery-section-title {
  font-size: var(--font-size-base);
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  /* Texte en ink (AA) : l'identité couleur du rayon reste portée par le liseré
     gauche et la pastille de compteur (--category-color en décor). */
  color: var(--color-ink);
}

.grocery-section-count {
  font-size: var(--font-size-base);
  color: var(--color-ink);
  background: color-mix(in srgb, var(--category-color) 12%, var(--color-bg-secondary));
  border-radius: 999px;
  padding: 0.1em 0.45em;
  font-weight: 500;
}

.grocery-section-chevron {
  margin-left: auto;
  display: flex;
  align-items: center;
  color: var(--color-ink-4);
}

.grocery-section-chevron::after {
  content: "›";
  display: inline-block;
  transform: rotate(90deg);
  transition: transform var(--transition-fast);
  font-size: 1.25rem;
  line-height: 1;
}

.grocery-section-chevron--closed::after {
  transform: rotate(0deg);
}

/* ── Liste d'items ──────────────────────────────────────── */
.grocery-items-list {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

/* ── Item individuel ────────────────────────────────────── */
.grocery-item {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.625rem 0.875rem;
  background: var(--color-bg-white);
  border-radius: var(--radius-card-compact);
  border: var(--border-thin);
  transition: background var(--transition-fast), opacity var(--transition-fast);
}

.grocery-item--checked {
  /* Pas d'opacité globale : elle faisait chuter le contraste du texte sous AA.
     L'état « fait » reste signalé par le fond, le barré du nom et la coche. */
  background: var(--color-bg-secondary);
}

/* ── Bouton checkbox ────────────────────────────────────── */
.btn-checkbox {
  width: 36px;
  height: 36px;
  min-width: 36px;
  min-height: 36px;
  border-radius: 50%;
  border: 2px solid var(--color-border-strong);
  background: transparent;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  padding: 0;
  color: transparent;
  transition: background 0.15s ease, border-color 0.15s ease;
}

.btn-checkbox:hover {
  border-color: var(--color-primary);
  background: color-mix(in srgb, var(--color-primary) 8%, transparent);
}

.btn-checkbox .btn-checkbox-icon {
  opacity: 0;
  transform: scale(0.4);
  transition: opacity 0.15s ease, transform 0.15s ease;
}

.btn-checkbox--checked {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: var(--color-bg-white);
  animation: checkPop 0.25s ease-out;
}

.btn-checkbox--checked:hover {
  background: var(--color-primary-hover);
  border-color: var(--color-primary-hover);
}

.btn-checkbox--checked .btn-checkbox-icon {
  opacity: 1;
  transform: scale(1);
}

@keyframes checkPop {
  0%   { transform: scale(0.75); }
  60%  { transform: scale(1.15); }
  100% { transform: scale(1); }
}

/* Colonne nom + badge : occupe l'espace flexible de la ligne, le badge passe dessous. */
.grocery-item-main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
}

.grocery-item-name {
  min-width: 0;
  font-size: var(--font-size-base);
  color: var(--color-ink);
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: color var(--transition-fast), text-decoration var(--transition-fast);
}

/* Badge « Était X — déjà acheté ? » : réutilise le palette ambre de .badge-amber,
   ajoute juste le positionnement dans la colonne (calé à gauche, texte pouvant passer à la ligne). */
.grocery-item-badge {
  align-self: flex-start;
  white-space: normal;
  font-size: var(--font-size-xs);
  padding: 0.1em 0.5em;
}

.grocery-item-name--checked {
  text-decoration: line-through;
  color: var(--color-ink-3);
}

/* ── Quantité éditable ──────────────────────────────────── */
.grocery-item-qty-wrapper {
  display: flex;
  align-items: center;
}

.grocery-item-qty {
  font-size: var(--font-size-sm);
  color: var(--color-ink-3);
  white-space: nowrap;
  cursor: pointer;
  border-bottom: 1px dotted transparent;
  transition: color var(--transition-fast), border-color var(--transition-fast);
}

.grocery-item-qty:hover {
  color: var(--color-primary);
  border-bottom-color: var(--color-primary);
}

/* Sur écran tactile (pas de survol), rendre l'affordance « quantité éditable »
   permanente : sinon rien n'indique qu'on peut la modifier au doigt. */
@media (hover: none) {
  .grocery-item-qty {
    border-bottom-color: var(--color-border-strong);
  }
}

/* Masqué par défaut — affiché via .is-editing (aucun flash avant Stimulus) */
.grocery-qty-form {
  display: none;
  align-items: center;
  gap: 0.35rem;
}

/* Mode édition : afficher le formulaire, masquer le texte */
.grocery-item-qty-wrapper.is-editing .grocery-qty-form {
  display: flex;
}

.grocery-item-qty-wrapper.is-editing .grocery-item-qty {
  display: none;
}

.grocery-qty-input {
  width: 5rem;
  padding: 0.25rem 0.4rem;
  font-size: var(--font-size-sm);
  border: 1px solid var(--color-primary);
  border-radius: 4px;
  background: var(--color-bg-white);
  color: var(--color-ink);
  line-height: 1.4;
}

.grocery-qty-unit {
  font-size: var(--font-size-xs);
  color: var(--color-ink-3);
  white-space: nowrap;
}

.grocery-qty-save-btn,
.grocery-qty-cancel-btn {
  width: 28px;
  height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 6px;
  cursor: pointer;
  flex-shrink: 0;
  padding: 0;
  transition: background var(--transition-fast), color var(--transition-fast);
  min-width: unset;
  min-height: unset;
}

.grocery-qty-save-btn {
  background: var(--color-primary);
  color: var(--color-bg-white);
}

.grocery-qty-save-btn:hover {
  background: var(--color-primary-hover);
}

.grocery-qty-cancel-btn {
  background: var(--color-bg-secondary);
  color: var(--color-ink-3);
}

.grocery-qty-cancel-btn:hover {
  background: var(--color-border);
  color: var(--color-ink);
}

/* ── Bouton supprimer (✕) ───────────────────────────────── */
.btn-remove {
  background: transparent;
  border: none;
  cursor: pointer;
  padding: 0.25rem 0.375rem;
  border-radius: var(--radius-icon-btn);
  color: var(--color-ink-4);
  font-size: 0.8rem;
  line-height: 1;
  transition: background var(--transition-fast), color var(--transition-fast);
  flex-shrink: 0;
  min-width: unset;
  min-height: unset;
}

.btn-remove:hover {
  background: var(--color-danger-light);
  color: var(--color-danger);
}

/* ── État vide ──────────────────────────────────────────── */
.grocery-empty {
  padding: 2rem;
  text-align: center;
  color: var(--color-ink-3);
  background: var(--color-bg-secondary);
  border-radius: var(--radius-card);
  border: var(--border-width) dashed var(--color-border-strong);
}

/* ── Ajout manuel ───────────────────────────────────────── */
.grocery-add-manual {
  margin-top: 2rem;
  padding-top: 1.5rem;
  border-top: var(--border-thin);
}

.grocery-add-manual-label {
  font-size: var(--font-size-xs);
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-ink-3);
  margin-bottom: 1rem;
}

.grocery-add-form {
  background: var(--color-bg-white);
  border-radius: var(--radius-card);
  border: var(--border-thin);
  padding: 1.25rem 1.5rem;
}

.grocery-add-row {
  display: flex;
  gap: 0.5rem;
  align-items: flex-end;
  flex-wrap: wrap;
}

/* Rangée de saisie compacte : à la taille de texte courante, les cinq champs ne
   tenaient pas sur une ligne dans les 60 % que la page laisse à la liste, et le
   rayon — le plus long à lire — était le premier sacrifié. Un cran plus petit et
   des marges internes resserrées lui rendent la centaine de pixels qui lui
   manquait. (En mobile, la règle plus bas garde 16 px : anti-zoom iOS.) */
.grocery-add-form .form-input,
.grocery-add-form .form-select {
  font-size: var(--font-size-sm);
  padding: 0.75rem 0.625rem;
}

.grocery-add-row .form-group {
  /* Chaque champ reçoit sa largeur plus bas, et le min-width par défaut
     l'empêcherait de la tenir. */
  min-width: 0;
  /* Annule le margin-bottom global de .form-group (hérité d'authentication.css) :
     dans cette rangée flex alignée en bas, ce margin décalait les champs vers le
     haut par rapport au bouton (l'alignement flex se fait sur la boîte de marge). */
  margin-bottom: 0;
}

.grocery-add-submit {
  flex-shrink: 0;
  align-self: flex-end;
  /* Sans effet tant que la rangée tient sur une ligne — le champ « Article »
     y absorbe déjà toute la place libre. Quand elle passe à la ligne, en
     revanche, le bouton se cale à droite au lieu de rester orphelin à gauche. */
  margin-left: auto;
}

/* ── Autocomplétion de l'article ────────────────────────── */
/* Largeurs de la rangée : chacune est figée (flex: 0 0), à la mesure de ce
   que son champ doit montrer. Rien ne les fait varier — ni la fenêtre, ni l'état
   du formulaire : c'est ce qui évite que la rangée se réorganise sous les doigts
   au moment où un article est reconnu et où la puce remplace le sélecteur de
   rayon. Quand la place manque, la rangée passe à la ligne plutôt que d'écraser
   ses champs.
   (Sélecteurs à deux classes pour l'emporter sur les règles communes de la
   rangée ; en mobile, la requête média rend à chaque champ toute la largeur.) */
.grocery-add-row .grocery-add-qty {
  flex: 0 0 84px;
}

/* De quoi afficher l'invite « Unité… » en entier : un sélecteur dont l'invite
   est tronquée fait négligé. */
.grocery-add-row .grocery-add-unit {
  flex: 0 0 96px;
}

/* Le rayon, sélecteur tant que l'article n'est pas reconnu et puce ensuite : une
   seule largeur pour les deux, celle du plus long des vingt-deux libellés
   (« Produits frais en libre-service »). C'est cette égalité qui fait que rien
   ne bouge au moment du rattachement. */
.grocery-add-row .grocery-add-shelf,
.grocery-add-row .grocery-add-known {
  flex: 0 0 260px;
}

/* Seul champ à respirer : il recueille toute la place que les autres laissent —
   c'est le seul où l'on écrit un texte de longueur imprévisible. */
.grocery-add-row .grocery-add-article {
  flex: 1 1 116px;
  /* Ancre de la liste des propositions, qui flotte sous le champ. */
  position: relative;
}

/* La liste flotte au-dessus de la page : elle ne doit ni pousser les champs
   voisins ni allonger la carte à chaque frappe.

   Elle s'élargit au-delà du champ quand les noms le demandent — « Fromage ail
   et fines herbes (boursin, fromage aux herbes) » tenait sur cinq lignes dans
   la largeur du champ — sans jamais sortir de l'écran. */
.grocery-suggestions {
  position: absolute;
  top: 100%;
  left: 0;
  width: max-content;
  min-width: 100%;
  max-width: min(26rem, 80vw);
  /* Au-dessus de la carte du formulaire, sous les bandeaux flash (9999) et la
     barre de navigation (170) : la liste ne recouvre que ce qui la porte. */
  z-index: 100;
  margin: 0.25rem 0 0;
  padding: 0;
  list-style: none;
  max-height: 15rem;
  overflow-y: auto;
  background: var(--color-bg-white);
  border: var(--border-thin);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-lg);
}

/* Une proposition tient sur une ligne, coupée s'il le faut : la liste se
   parcourt du regard, et six noms de deux lignes se lisent moins bien que six
   noms d'une seule. */
.grocery-suggestion {
  padding: 0.5rem 0.75rem;
  cursor: pointer;
  font-size: var(--font-size-sm);
  color: var(--color-ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.grocery-suggestion:hover,
.grocery-suggestion[aria-selected="true"] {
  background: var(--color-bg-secondary);
}

/* ── Rayon d'un article reconnu ─────────────────────────── */
/* La boîte d'un champ, en transparent : mêmes marges internes verticales et
   même bordure que .form-input, pour que la puce s'aligne sur ses voisins dans
   une rangée alignée en bas. */
.grocery-add-chip {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.75rem 0;
  border: var(--border-width) solid transparent;
}

.grocery-add-chip-label {
  font-size: var(--font-size-sm);
  font-weight: 500;
  color: var(--color-ink);
}

/* .form-group est en display: flex, ce qui l'emporte sur le display: none que
   l'attribut hidden tient de la feuille de style du navigateur : sans ce repli
   explicite, le sélecteur de rayon et la puce qui le remplace resteraient
   affichés tous les deux. */
.grocery-add-row .form-group[hidden] {
  display: none;
}

/* Le bouton doit avoir exactement la hauteur des champs pour s'aligner sur eux
   (rangée alignée en bas). Mêmes marges internes verticales que .form-input ;
   on ajoute une bordure transparente pour compenser celle des champs (1px) et
   on lève le plancher min-height de .btn. La hauteur reste > 44px (cible tactile).

   Sur une rangée, il se réduit à son « + » : les 70 px que son libellé occupait
   vont au rayon, qu'on ne parvenait plus à lire. Le mot revient en mobile, où
   le bouton s'étale sur toute la largeur (cf. la requête média plus bas). */
.grocery-add-submit .btn {
  /* Exactement la hauteur d'un champ, et non le plancher de 44 px de .btn : son
     icône (16 px) est plus courte qu'une ligne de texte, si bien que le bouton
     mesurait 5 px de moins que ses voisins — alignés par le bas, son sommet
     retombait sous le leur. Même construction qu'un .form-input de la rangée :
     marges internes, bordures, et une ligne de texte à la taille des champs
     (1,5 étant la hauteur de ligne que le navigateur applique par défaut). */
  min-height: calc(1.5rem + 2 * var(--border-width) + 1.5 * var(--font-size-sm));
  border: var(--border-width) solid transparent;
  padding: 0.75rem 0.875rem;
}

.grocery-add-btn-label {
  display: none;
}

/* ── Desktop : liste resserrée ──────────────────────────── */
/* Sur grand écran, .grocery-item-main (flex: 1) étire chaque ligne sur toute
   la largeur du .mc-page-wrap, créant un grand vide entre le nom et la
   quantité/suppression. On resserre la liste et le formulaire d'ajout pour
   qu'ils se lisent comme une liste de courses plutôt qu'un tableau étiré. */
@media (min-width: 768px) {
  #grocery_list,
  .grocery-add-manual {
    max-width: 60%;
    margin-left: auto;
    margin-right: auto;
  }
}

/* ── Responsive ─────────────────────────────────────────── */
@media (max-width: 767px) {
  /* Moins de vide au-dessus du premier rayon : ce lien est le seul élément
     entre le titre et la liste, il n'a pas besoin d'autant d'air qu'en desktop. */
  .grocery-list-controls {
    margin-bottom: 0.375rem;
  }

  /* Rayons plus resserrés : sur petit écran, l'espace entre deux rayons
     ne doit pas ajouter plus de vide que l'espace interne d'un rayon. */
  .grocery-section {
    margin-bottom: 1rem;
  }

  .grocery-item {
    flex-wrap: wrap;
  }

  /* Cibles tactiles cœur du parcours « je coche au supermarché », une main :
     la coche et la suppression doivent faire au moins 44px au doigt. */
  .btn-checkbox {
    width: var(--min-touch-target);
    height: var(--min-touch-target);
    min-width: var(--min-touch-target);
    min-height: var(--min-touch-target);
  }

  .btn-remove {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: var(--min-touch-target);
    min-height: var(--min-touch-target);
  }

  /* Boutons enregistrer/annuler de la quantité : cible tactile confortable. */
  .grocery-qty-save-btn,
  .grocery-qty-cancel-btn {
    width: var(--min-touch-target);
    height: var(--min-touch-target);
  }

  /* En-tête de rayon (accordéon) : hauteur suffisante pour ouvrir/fermer au pouce. */
  .grocery-section-header {
    padding-top: 0.6rem;
    padding-bottom: 0.6rem;
  }

  /* Zone tactile un peu plus haute autour de la quantité éditable. */
  .grocery-item-qty {
    padding: 0.35rem 0.25rem;
  }

  /* « Tout fermer / ouvrir » : un peu plus de surface tappable. */
  .btn-text-sm {
    padding: 0.5rem 0.25rem;
  }

  .grocery-add-manual {
    margin-top: 0.75rem;
    padding-top: 0.5rem;
  }

  .grocery-add-manual-label {
    margin-bottom: 0.375rem;
  }

  .grocery-add-form {
    padding: 0.5rem 0.75rem;
  }

  .grocery-add-row {
    flex-direction: column;
    gap: 0.15rem;
  }

  /* Empilée, la puce n'a plus de champ voisin sur qui s'aligner : le padding
     qui lui donnait leur hauteur ne ferait plus que du vide. On garde celui
     des champs en mobile (cf. .grocery-add-form .form-input). */
  .grocery-add-chip {
    padding: 0.3rem 0;
  }

  /* La rangée est empilée : les bases flex réglées pour le desktop gouvernent
     alors la HAUTEUR des champs — 140 px de vide sous « Article ». Chaque champ
     reprend ici la hauteur de son contenu et toute la largeur. */
  .grocery-add-row .form-group {
    flex: 1 1 auto;
    width: 100%;
  }

  .grocery-add-form .form-label {
    margin-bottom: 0.05rem;
  }

  .grocery-add-form .form-input,
  .grocery-add-form .form-select {
    padding: 0.3rem 0.5rem;
    /* 16px : évite le zoom iOS au focus lors de l'ajout d'un article en rayon. */
    font-size: 16px;
  }

  /* Édition inline de la quantité : même protection anti-zoom iOS sur mobile. */
  .grocery-qty-input {
    font-size: 16px;
  }

  /* La rangée est empilée avec un gap serré (0.15rem) adapté aux paires
     label/champ. Le bouton n'appartient à aucune paire : on lui rend de l'air
     pour le détacher du dernier champ (« Rayon ») et éviter le tap accidentel. */
  .grocery-add-submit {
    width: 100%;
    align-self: stretch;
    margin-top: 0.75rem;
  }

  .grocery-add-submit .btn {
    width: 100%;
    justify-content: center;
    padding: 0.75rem 1.5rem;
  }

  /* Le bouton s'étale sur toute la largeur : rien ne réclame plus la place que
     son libellé prenait sur la rangée du desktop. */
  .grocery-add-btn-label {
    display: inline;
  }
}
/*
 * Indicateur global d'état réseau (PWA — R0.3).
 * Bandeau discret affiché en bas de l'écran : « Hors ligne » / « Synchronisation… ».
 * Masqué par défaut, révélé par le controller Stimulus network-status.
 */

.network-status {
  position: fixed;
  left: 50%;
  /* Au-dessus de l'indicateur d'accueil iOS (zone sûre) */
  bottom: calc(1rem + env(safe-area-inset-bottom));
  z-index: 1000;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.4rem 0.85rem;
  border-radius: 999px;
  background: var(--color-ink);
  color: var(--color-bg-white);
  font-size: 0.8125rem;
  font-weight: 600;
  line-height: 1;
  box-shadow: var(--shadow-lg);

  /* Masqué par défaut : hors du champ + non interactif, transition douce. */
  opacity: 0;
  transform: translate(-50%, 150%);
  pointer-events: none;
  transition: opacity 0.25s ease, transform 0.25s ease;
}

.network-status--visible {
  opacity: 1;
  transform: translate(-50%, 0);
}

/* Point d'état coloré selon la situation. */
.network-status__dot {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: currentColor;
  flex: 0 0 auto;
}

.network-status--offline {
  background: var(--color-ink-2);
}

.network-status--offline .network-status__dot {
  background: var(--color-danger);
}

.network-status--syncing .network-status__dot {
  background: var(--color-accent);
  animation: network-status-pulse 1s ease-in-out infinite;
}

@keyframes network-status-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.3; }
}
/*
 * This is a manifest file that'll be compiled into application.css, which will include all the files
 * listed below.
 *
 * Any CSS (and SCSS, if configured) file within this directory, lib/assets/stylesheets, or any plugin's
 * vendor/assets/stylesheets directory can be referenced here using a relative path.
 *
 * You're free to add application-wide styles to this file and they'll appear at the bottom of the
 * compiled file so the styles you add here take precedence over styles defined in any other CSS
 * files in this directory. Styles in this file should be added after the last require_* statement.
 * It is generally better to create a new file per style scope.
 *
















 */
