/* Variables CSS - Style iOS Premium */
:root {
  /* Couleurs iOS System */
  --primary-color: #007aff;
  --primary-light: #5ac8fa;
  --primary-dark: #0051d5;
  --secondary-color: #1c1c1e;
  --secondary-light: #2c2c2e;
  --tertiary-color: #3a3a3c;

  /* Couleurs sémantiques */
  --success-color: #34c759;
  --danger-color: #ff3b30;
  --warning-color: #ff9500;
  --info-color: #5ac8fa;

  /* Backgrounds iOS */
  --bg-primary: #f2f2f7;
  --bg-secondary: #ffffff;
  --bg-tertiary: #f9f9f9;
  --bg-quaternary: rgba(255, 255, 255, 0.8);

  /* Glassmorphism */
  --glass-bg: rgba(255, 255, 255, 0.25);
  --glass-border: rgba(255, 255, 255, 0.18);
  --glass-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);

  /* Text colors */
  --text-primary: #000000;
  --text-secondary: #3c3c43;
  --text-tertiary: #8e8e93;
  --text-quaternary: #c7c7cc;

  /* Borders & Separators */
  --separator-color: rgba(60, 60, 67, 0.12);
  --border-radius-small: 8px;
  --border-radius-medium: 12px;
  --border-radius-large: 16px;
  --border-radius-xl: 20px;

  /* Shadows iOS */
  --shadow-xs: 0 1px 3px rgba(0, 0, 0, 0.08);
  --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.12);
  --shadow-md: 0 4px 16px rgba(0, 0, 0, 0.16);
  --shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.2);
  --shadow-xl: 0 16px 64px rgba(0, 0, 0, 0.24);
  /* Utilisée par les cartes (contest/campaign/group/reward-card...) pour leur ombre au
     repos — n'était jamais définie (retour du 20/08/2026 : "l'ombre n'apparaît qu'au
     survol"), donc var(--shadow) ne renvoyait rien et ces cartes n'avaient aucune
     ombre tant qu'on ne passait pas la souris dessus (le survol, lui, pose une valeur
     écrite en dur, indépendante de cette variable). */
  --shadow: var(--shadow-sm);

  /* Animations */
  --transition-quick: all 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94);
  --transition-smooth: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
  --transition-bounce: all 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55);

  /* Spacing */
  --spacing-xs: 4px;
  --spacing-sm: 8px;
  --spacing-md: 16px;
  --spacing-lg: 24px;
  --spacing-xl: 32px;
  --spacing-2xl: 48px;
}

/* Thèmes iOS Premium */
[data-theme="blue"] {
  --primary-color: #007aff;
  --primary-light: #5ac8fa;
  --primary-dark: #0051d5;
  --accent-gradient: linear-gradient(135deg, #007aff 0%, #5ac8fa 100%);
}

[data-theme="pink"] {
  --primary-color: #ff2d92;
  --primary-light: #ff69b4;
  --primary-dark: #c7005c;
  --accent-gradient: linear-gradient(135deg, #ff2d92 0%, #ff69b4 100%);
}

[data-theme="green"] {
  --primary-color: #34c759;
  --primary-light: #30d158;
  --primary-dark: #248a3d;
  --accent-gradient: linear-gradient(135deg, #34c759 0%, #30d158 100%);
}

[data-theme="orange"] {
  --primary-color: #ff9500;
  --primary-light: #ffac33;
  --primary-dark: #cc7700;
  --accent-gradient: linear-gradient(135deg, #ff9500 0%, #ffac33 100%);
}

[data-theme="purple"] {
  --primary-color: #af52de;
  --primary-light: #bf5af2;
  --primary-dark: #8e44ad;
  --accent-gradient: linear-gradient(135deg, #af52de 0%, #bf5af2 100%);
}

[data-theme="indigo"] {
  --primary-color: #5856d6;
  --primary-light: #7c7ce8;
  --primary-dark: #4b49c4;
  --accent-gradient: linear-gradient(135deg, #5856d6 0%, #7c7ce8 100%);
}

[data-theme="beige"] {
  --primary-color: #a2845e;
  --primary-light: #d2b48c;
  --primary-dark: #8b6914;
  --accent-gradient: linear-gradient(135deg, #a2845e 0%, #d2b48c 100%);
}

[data-theme="gray"] {
  --primary-color: #6d6d6d;
  --primary-light: #8e8e93;
  --primary-dark: #48484a;
  --accent-gradient: linear-gradient(135deg, #6d6d6d 0%, #8e8e93 100%);
}

/* Reset et base - iOS Style */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* overflow-x: hidden sur html ET body : sur iPad Safari, le mettre seulement sur
   body laisse parfois un léger scroll/rebond horizontal résiduel (élastique iOS)
   si un descendant dépasse ne serait-ce que de quelques pixels — html doit aussi
   le bloquer pour que ce soit garanti, pas juste "généralement le cas". */
html {
  overflow-x: hidden; /* repli pour navigateurs sans support de "clip" (rare) */
  /* `clip` (pas juste `hidden`) : Safari permet encore un scroll DÉCLENCHÉ AU DOIGT
     (le geste tactile pousse un offset de scroll) même avec overflow-x:hidden — cette
     valeur ne fait que clipper le RENDU, elle n'interdit pas le scroll lui-même par
     programmation/geste. `clip` l'interdit réellement (aucun offset de scroll
     possible du tout sur cet axe, quelle que soit la source). Deux déclarations : la
     seconde écrase la première si le navigateur comprend "clip", sinon il l'ignore et
     garde "hidden". Voir aussi #pageScrollGuard un peu plus bas et le body ci-dessous.
     Retour du 24/08/2026, confirmé sur Safari iPad. */
  overflow-x: clip;
  width: 100%;
  /* Voir le commentaire sur body ci-dessous : touch-action doit aussi être posé ici,
     pas seulement sur body, pour bloquer fiablement le balayage horizontal tactile
     sur iPad (retour du 24/08/2026 : encore possible, décalait toute la page). */
  touch-action: pan-y pinch-zoom;
}

body {
  font-family:
    -apple-system, BlinkMacSystemFont, "SF Pro Display", "Segoe UI", Roboto,
    Helvetica, Arial, sans-serif;
  background: var(--bg-primary);
  color: var(--text-primary);
  line-height: 1.6;
  font-size: 16px;
  font-weight: 400;
  overflow-x: hidden; /* repli pour navigateurs sans support de "clip" (rare) */
  overflow-x: clip; /* voir le commentaire détaillé sur html ci-dessus */
  width: 100%;
  /* Coupe le rebond élastique horizontal (overscroll) sur iPadOS/tactile : même si
     rien ne déborde, un balayage horizontal un peu appuyé peut sinon tirer toute la
     page sur le côté puis la faire "rebondir" en retour — gênant sur tablette où
     l'utilisateur scrolle beaucoup au doigt. N'affecte pas le scroll vertical. */
  overscroll-behavior-x: none;
  /* iOS Safari ignore volontairement `user-scalable=no` de la balise viewport (accès
     zoom toujours permis par accessibilité, peu importe ce que demande le site) : le
     double-tap-to-zoom en particulier ne peut être coupé que via touch-action, pas par
     la balise viewport seule — d'où le zoom encore possible sur iPad malgré celle-ci. */
  /* `manipulation` (= pan-x + pan-y + pinch-zoom, sans double-tap) autorisait encore
     le balayage horizontal au doigt — c'est un réglage du GESTE tactile, indépendant
     de overflow-x:hidden (qui ne clippe que le layout, pas toujours fiable seul sous
     Safari/iPadOS pour empêcher le geste lui-même). Restreint à pan-y + pinch-zoom :
     scroll vertical et zoom pincé toujours possibles, plus aucun balayage horizontal
     ni double-tap-zoom (retour du 24/08/2026 : "je peux le faire et ça décale toute
     la page"). Le pinch à deux doigts reste de toute façon garanti par iOS depuis la
     version 10, aucun site ne peut le bloquer — voir Session 2026-08-17.
     Cohérent avec .qr-editor-stage (qr-editor.html) qui utilise déjà touch-action:none
     pour la même raison sur sa propre zone de drag. */
  touch-action: pan-y pinch-zoom;
}

/* Empêcher le scroll quand on est sur login/register */
body.no-scroll {
  overflow: hidden;
}

/* Wrapper unique de tout le contenu de <body>, sur toutes les pages (voir chaque
   fichier HTML : <div id="pageScrollGuard"> juste après <body>). Correctif du
   24/08/2026 : overflow-x:hidden posé directement sur html/body (ci-dessus) ne suffit
   PAS de façon fiable sous Safari/iPadOS — bug documenté où le scroll horizontal du
   DOCUMENT racine ignore cette règle dès qu'un seul élément déborde ne serait-ce que
   de quelques pixels, aussi loin dans l'arbre soit-il. Un simple overflow:hidden posé
   sur un div normal, lui, clippe réellement (ce n'est plus le scroll racine qui est en
   jeu, mais celui d'une boîte de layout classique — tous les navigateurs, iOS compris,
   le respectent correctement).
   `display:block; width:100%;` suffisent : pas de height fixe/position fixed ici, donc
   AUCUN impact sur le scroll vertical normal (qui continue de se faire au niveau du
   document comme avant), ni sur les éléments position:fixed existants (modales,
   bouton flottant "remonter en haut", toasts) — un div sans transform/filter/
   will-change ne crée pas de nouveau contexte de positionnement pour eux, ils restent
   positionnés par rapport au viewport exactement comme avant ce changement. */
#pageScrollGuard {
  display: block;
  width: 100%;
  overflow-x: hidden; /* repli pour navigateurs sans support de "clip" (rare) */
  overflow-x: clip; /* voir le commentaire détaillé sur html en haut du fichier */
}

/* Scroll de l'arrière-plan bloqué pendant qu'une modale est ouverte (voir
   lockBodyScroll()/unlockBodyScroll() dans app.js). `position: fixed` (plutôt qu'un
   simple overflow:hidden) est nécessaire pour bloquer fiablement le scroll tactile sous
   Safari/iPadOS — `top` est fixé dynamiquement en JS à la position de scroll actuelle
   pour ne pas sauter visuellement en haut de page à l'ouverture. */
body.modal-open-lock {
  position: fixed;
  left: 0;
  right: 0;
  width: 100%;
  overflow: hidden;
}

/* Glassmorphism Base */
.glass {
  background: var(--glass-bg);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  border: 1px solid var(--glass-border);
  box-shadow: var(--glass-shadow);
}

/* iOS Card Style */
.ios-card {
  background: var(--bg-secondary);
  border-radius: var(--border-radius-large);
  box-shadow: var(--shadow-sm);
  border: 1px solid var(--separator-color);
  transition: var(--transition-smooth);
  overflow: hidden;
}

.ios-card:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-md);
}

/* Pages principales */
.page {
  display: none;
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 1000;
}

.page.active {
  display: block;
}

/* L'application doit pouvoir scroller */
#app {
  position: relative;
  height: auto;
  min-height: 100vh;
}

#app.active {
  position: relative;
  height: auto;
}

/* Sécurité supplémentaire - masquer complètement l'app quand inactive */
#app:not(.active) {
  visibility: hidden;
  opacity: 0;
  pointer-events: none;
}

/* Page de connexion iOS */
.login-container {
  min-height: 100vh;
  height: 100%;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(
    135deg,
    var(--primary-color) 0%,
    var(--primary-light) 50%,
    var(--primary-dark) 100%
  );
  position: relative;
  overflow: hidden;
  padding: var(--spacing-xl) var(--spacing-md);
}

/* S'assurer que les pages de connexion occupent tout l'écran */
#loginPage,
#registerPage {
  overflow: hidden;
}

.login-container::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="grain" width="100" height="100" patternUnits="userSpaceOnUse"><circle cx="25" cy="25" r="1" fill="white" opacity="0.1"/><circle cx="75" cy="75" r="1" fill="white" opacity="0.1"/></pattern></defs><rect width="100" height="100" fill="url(%23grain)"/></svg>');
  opacity: 0.3;
}

.login-box {
  background: var(--glass-bg);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  padding: var(--spacing-2xl);
  border-radius: var(--border-radius-xl);
  box-shadow: var(--shadow-xl);
  border: 1px solid var(--glass-border);
  width: 100%;
  max-width: 480px;
  text-align: center;
  position: relative;
  z-index: 1;
  margin: var(--spacing-md) 0;
  max-height: 90vh;
  overflow-y: auto;
  /* Scrollbar masquée : le scroll (molette/tactile) reste actif, juste sans barre
     visible — même traitement que .client-details-content. */
  scrollbar-width: none;
}

.login-box::-webkit-scrollbar {
  display: none;
}

.login-box h1 {
  color: white;
  margin-bottom: var(--spacing-sm);
  font-size: 32px;
  font-weight: 700;
  letter-spacing: -0.5px;
  text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}

.login-box p {
  color: rgba(255, 255, 255, 0.8);
  margin-bottom: var(--spacing-2xl);
  font-size: 17px;
  font-weight: 400;
  letter-spacing: -0.24px;
}

/* Styles spécifiques pour la page d'inscription */
#registerPage .login-box {
  max-width: 520px;
  padding: var(--spacing-xl) var(--spacing-2xl);
}

#registerPage .login-box h1 {
  margin-bottom: var(--spacing-sm);
}

#registerPage .login-box p {
  margin-bottom: var(--spacing-xl);
}

#registerPage .form-group {
  margin-bottom: var(--spacing-md);
}

#registerPage .btn-primary {
  margin-top: var(--spacing-lg);
  margin-bottom: var(--spacing-md);
}

#registerPage .switch-form {
  margin-top: var(--spacing-sm);
}

/* Formulaires iOS */
.form-group {
  margin-bottom: var(--spacing-lg);
  text-align: left;
}

.form-group label {
  display: block;
  margin-bottom: var(--spacing-sm);
  font-weight: 600;
  font-size: 17px;
  color: var(--text-primary);
  letter-spacing: -0.24px;
}

.form-group input,
.form-group textarea,
.form-group select {
  width: 100%;
  padding: var(--spacing-md);
  border: 2px solid var(--separator-color);
  border-radius: var(--border-radius-medium);
  font-size: 17px;
  font-weight: 400;
  letter-spacing: -0.24px;
  transition: var(--transition-smooth);
  background: var(--bg-secondary);
  color: var(--text-primary);
  font-family: inherit;
  min-height: 50px;
}

.form-group input:focus,
.form-group textarea:focus,
.form-group select:focus {
  outline: none;
  border-color: var(--primary-color);
  box-shadow: 0 0 0 4px rgba(0, 122, 255, 0.1);
  transform: translateY(-1px);
}

.form-group input::placeholder,
.form-group textarea::placeholder {
  color: var(--text-tertiary);
  font-weight: 400;
}

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

/* Boutons iOS Premium */
.btn-primary {
  background: linear-gradient(
    135deg,
    var(--primary-color) 0%,
    var(--primary-light) 100%
  );
  color: white;
  border: none;
  padding: var(--spacing-md) var(--spacing-xl);
  border-radius: var(--border-radius-large);
  cursor: pointer;
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -0.24px;
  transition: var(--transition-quick);
  box-shadow: var(--shadow-sm);
  position: relative;
  overflow: hidden;
  width: 100%;
  text-align: center;
  min-height: 50px;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Beaucoup de boutons ont une icône suivie du texte séparés seulement par un espace
     dans le HTML (ex. <i class="fas fa-plus"></i> Ajouter un client) : un simple
     caractère espace ne donne presque aucun écart visuel. Un vrai gap flex s'applique
     entre l'icône et le texte (le texte forme son propre item flex anonyme). */
  gap: var(--spacing-sm);
}

.btn-primary::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(255, 255, 255, 0.1);
  opacity: 0;
  transition: var(--transition-quick);
}

.btn-primary:hover::before {
  opacity: 1;
}

.btn-primary:active {
  transform: scale(0.98);
  box-shadow: var(--shadow-xs);
}

.btn-secondary {
  background: var(--bg-secondary);
  color: var(--primary-color);
  border: 2px solid var(--primary-color);
  padding: calc(var(--spacing-md) - 2px) calc(var(--spacing-xl) - 2px);
  border-radius: var(--border-radius-large);
  cursor: pointer;
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -0.24px;
  transition: var(--transition-quick);
  box-shadow: var(--shadow-xs);
  min-height: 50px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--spacing-sm);
}

.btn-secondary:hover {
  background: var(--primary-color);
  color: white;
  transform: translateY(-1px);
  box-shadow: var(--shadow-sm);
}

.btn-secondary:active {
  transform: scale(0.98);
}

.btn-danger {
  background: linear-gradient(135deg, var(--danger-color) 0%, #ff6b5a 100%);
  color: white;
  border: none;
  padding: var(--spacing-sm) var(--spacing-lg);
  border-radius: var(--border-radius-large);
  cursor: pointer;
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.24px;
  transition: var(--transition-quick);
  box-shadow: var(--shadow-xs);
  min-height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.btn-danger:hover {
  transform: translateY(-1px);
  box-shadow: var(--shadow-sm);
}

.btn-danger:active {
  transform: scale(0.98);
}

.btn-primary:disabled,
.btn-secondary:disabled,
.btn-danger:disabled,
.btn-success:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  transform: none !important;
}

.btn-success {
  background: linear-gradient(135deg, var(--success-color) 0%, #28a745 100%);
  color: white;
  border: none;
  padding: var(--spacing-sm) var(--spacing-lg);
  border-radius: var(--border-radius-large);
  cursor: pointer;
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.24px;
  transition: var(--transition-quick);
  box-shadow: var(--shadow-xs);
  min-height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--spacing-xs);
}

.btn-success:hover {
  transform: translateY(-1px);
  box-shadow: var(--shadow-sm);
}

.btn-success:active {
  transform: scale(0.98);
}

.switch-form {
  margin-top: 1rem;
  color: #666;
}

.switch-form a {
  color: var(--primary-color);
  text-decoration: none;
}

.switch-form a:hover {
  text-decoration: underline;
}

/* Pages login/register : labels + lien "Déjà un compte ?" peu lisibles sur le
   fond dégradé violet, même couleur que le reste des textes de formulaire
   pensés pour un fond blanc. On les aligne sur le blanc du logo/titre. */
.login-container .form-group label {
  color: white;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
}

.login-container .switch-form {
  color: rgba(255, 255, 255, 0.85);
}

.login-container .switch-form a {
  color: white;
  font-weight: 700;
  text-decoration: underline;
}

.legal-footer-links {
  margin-top: 0.75rem;
  font-size: 0.78rem;
  color: rgba(255, 255, 255, 0.65);
}

.legal-footer-links a {
  color: rgba(255, 255, 255, 0.85);
  text-decoration: underline;
}

/* Case CGU du formulaire d'inscription. */
.checkbox-group {
  display: flex;
  align-items: flex-start;
}

/* Spécificité (.form-group + .checkbox-label) plus forte que .checkbox-label seule :
   sans ce préfixe, la règle générique ".form-group label { display: block;
   font-size: 17px; font-weight: 600; margin-bottom: ... }" gagnait (même bug que le
   switch "Programmer l'envoi" des campagnes) — la case et le texte perdaient leur
   alignement flex prévu et la case-à-cocher se retrouvait en gros texte gras au lieu
   d'une petite mention discrète sous le mot de passe. */
.form-group .checkbox-label {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  font-size: 0.85rem;
  font-weight: 400;
  line-height: 1.4;
  color: rgba(255, 255, 255, 0.85);
  cursor: pointer;
  margin-bottom: 0;
}

/* Checkbox native masquée mais gardée dans le DOM (accessibilité clavier/lecteurs
   d'écran intacte) — c'est le <span> juste après qui dessine la vraie case, pour un
   rendu identique sur tous les navigateurs (le rendu par défaut change trop d'un
   navigateur/OS à l'autre pour rester cohérent avec le reste de l'UI). */
.checkbox-label input[type='checkbox'] {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.checkbox-label .checkbox-box {
  flex-shrink: 0;
  width: 20px;
  height: 20px;
  margin-top: 1px;
  border-radius: 6px;
  border: 2px solid rgba(255, 255, 255, 0.6);
  background: rgba(255, 255, 255, 0.08);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background-color 0.15s ease, border-color 0.15s ease, transform 0.1s ease;
}

.checkbox-label .checkbox-box::after {
  content: '';
  width: 5px;
  height: 9px;
  border: solid white;
  border-width: 0 2px 2px 0;
  transform: rotate(45deg) scale(0);
  transition: transform 0.15s ease;
  margin-top: -2px;
}

.checkbox-label:hover .checkbox-box {
  border-color: white;
}

.checkbox-label input[type='checkbox']:checked + .checkbox-box {
  background: var(--primary-color);
  border-color: var(--primary-color);
}

.checkbox-label input[type='checkbox']:checked + .checkbox-box::after {
  transform: rotate(45deg) scale(1);
}

.checkbox-label input[type='checkbox']:focus-visible + .checkbox-box {
  outline: 2px solid white;
  outline-offset: 2px;
}

.checkbox-label input[type='checkbox']:active + .checkbox-box {
  transform: scale(0.92);
}

/* Variante pour fond clair (modales) — la variante de base est pensée pour la page de
   connexion, fond sombre (bordure blanche translucide, texte blanc). Utilisée pour
   "Concours actif" dans la modale concours — voir contestModal dans index.html. */
.form-group .checkbox-label-light {
  color: var(--text-primary, #1c1c1e);
}

.checkbox-label-light .checkbox-box {
  border-color: var(--separator-color);
  background: white;
}

.checkbox-label-light:hover .checkbox-box {
  border-color: var(--primary-color);
}

.checkbox-label a {
  color: white;
  font-weight: 700;
  text-decoration: underline;
}

/* Jauge de force du mot de passe + liste des exigences (page d'inscription) */
.password-strength-bar {
  height: 6px;
  border-radius: 3px;
  background: rgba(255, 255, 255, 0.25);
  margin-top: 0.5rem;
  overflow: hidden;
}

.password-strength-fill {
  height: 100%;
  width: 0%;
  background: #e74c3c;
  border-radius: 3px;
  transition:
    width 0.25s ease,
    background-color 0.25s ease;
}

.password-strength-fill.weak {
  width: 25%;
  background: #e74c3c;
}

.password-strength-fill.medium {
  width: 60%;
  background: #f39c12;
}

.password-strength-fill.strong {
  width: 100%;
  background: #27ae60;
}

.password-requirements {
  list-style: none;
  padding: 0;
  margin: 0.6rem 0 0;
  font-size: 0.85rem;
}

.password-requirements li {
  color: rgba(255, 255, 255, 0.75);
  margin-bottom: 0.25rem;
}

.password-requirements li::before {
  content: "✗ ";
  color: #ff6b6b;
  font-weight: 700;
}

.password-requirements li.met {
  color: white;
}

.password-requirements li.met::before {
  content: "✓ ";
  color: #2ecc71;
}

/* Application principale */
#app {
  display: flex;
  min-height: 100vh;
}

/* Sidebar iOS */
.sidebar {
  width: 280px;
  background: var(--glass-bg);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  border-right: 1px solid var(--glass-border);
  color: var(--text-primary);
  position: fixed;
  /* 100vh STATIQUE, pas 100dvh (retour en arrière du 24/08/2026) : 100dvh avait
     résolu le "Déconnexion coupée" mais recalcule sa valeur en temps réel pendant
     l'animation de la barre d'outils Safari — la sidebar se redimensionnait alors
     visiblement en cadence avec le scroll de la page, donnant l'impression qu'elle
     "bougeait avec" le contenu ("ça emmène le scroll aussi à gauche"). Le
     resserrement des espacements du menu (voir .sidebar-menu/.menu-item plus bas)
     garantit désormais que tout tient même dans la plus PETITE hauteur réelle
     possible, donc plus besoin du suivi dynamique de dvh : 100vh (la valeur MAXIMALE,
     fixe, ne se recalcule jamais) fait que la boîte de la sidebar ne change plus
     jamais de taille — seule la barre d'outils Safari la recouvre temporairement par
     endroits, sans aucune animation de redimensionnement visible côté sidebar. */
  height: 100vh;
  left: 0;
  top: 0;
  /* z-index STRICTEMENT supérieur à .page (1000) — la sidebar a désormais la classe
     .page en plus (voir index.html) pour se synchroniser avec l'écran de connexion,
     mais #app (aussi .page, z-index:1000) occupe TOUTE la largeur de l'écran (le
     margin-left:280px de .main-content ne décale que le contenu visible, pas la
     boîte de #app elle-même) et arrive après la sidebar dans le DOM — à z-index égal,
     #app passait donc PAR-DESSUS la sidebar et interceptait tous les clics dessus
     (bug critique introduit le 24/08/2026, plus aucun lien du menu ne réagissait).
     1001 garantit que la sidebar reste toujours au-dessus, quel que soit l'ordre DOM. */
  z-index: 1001;
  box-shadow: var(--shadow-lg);
  /* Scroll impossible sur la sidebar (retour du 24/08/2026) : overflow:hidden clippe
     tout contenu qui dépasserait sa hauteur, et touch-action:none coupe le geste de
     glissement tactile lui-même — la même combinaison que .qr-editor-stage
     (qr-editor.html) et le correctif overflow:clip du scroll horizontal global. */
  overflow: hidden;
  touch-action: none;
  /* "Glissement" visuel pendant qu'on scrolle le contenu à côté (retour du
     24/08/2026, persistait malgré la sortie de #pageScrollGuard) : artefact de rendu
     connu Safari/iPadOS, pas un vrai déplacement — un élément position:fixed n'est
     pas toujours composé sur son propre calque graphique pendant un scroll actif,
     WebKit peut alors le laisser visuellement "traîner" derrière le contenu qui
     défile avant de le recaler. Forcer un calque GPU dédié élimine ce décalage. */
  transform: translateZ(0);
  -webkit-transform: translateZ(0);
  will-change: transform;
  /* Colonne flex (header + menu) pour que .sidebar-menu ci-dessous puisse se
     comprimer avec flex:1/min-height:0 plutôt que de dépendre uniquement d'un calcul
     de hauteur précis — garantit que "Déconnexion" reste dans les limites visibles
     même si 100dvh est légèrement sous-estimé sur un modèle/orientation particulier. */
  display: flex;
  flex-direction: column;
}

/* Bug critique corrigé le 24/08/2026 : la sidebar porte aussi la classe .page (voir
   index.html, id="appSidebar") pour se cacher/afficher en même temps que l'écran de
   connexion (.page{display:none} / .page.active{display:block}). Mais le display:flex
   ci-dessus, déclaré PLUS BAS dans ce fichier que la règle .page de base, l'emportait
   TOUJOURS en cascade (même spécificité, seul l'ordre de déclaration décide) — la
   sidebar restait donc visible en permanence, y compris sur l'écran de connexion,
   quel que soit l'état de la classe .active. :not(.active) a une spécificité
   strictement supérieure à .sidebar seul, donc cette règle gagne toujours quand la
   sidebar ne doit PAS être affichée, indépendamment de l'ordre dans le fichier. */
.sidebar:not(.active) {
  display: none;
}

.sidebar-header {
  padding: var(--spacing-lg) var(--spacing-xl);
  border-bottom: 1px solid var(--separator-color);
  text-align: center;
  background: rgba(255, 255, 255, 0.05);
  flex-shrink: 0;
}

.sidebar-header h2 {
  color: var(--text-primary);
  font-size: 24px;
  font-weight: 700;
  letter-spacing: -0.5px;
  margin: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* Logo FideliTea (26/08/2026, fichier fourni par l'utilisateur ; changé le
   04/09/2026 pour la version cadenas). Taille relative à la police du titre
   porteur (sidebar 24px, login/register 32px) plutôt qu'une taille fixe,
   pour rester proportionné aux deux. 2.2em plutôt que 1.15em (retour du
   26/08/2026 : "agrandis un peu, on le voit pas assez") — le logo est une
   illustration détaillée, trop petit il devient illisible.
   Le titre porteur (.sidebar-header h2 / .login-box h1) est en
   inline-flex + align-items:center plutôt qu'un simple vertical-align sur
   l'image seule (retour du 04/09/2026 : nouveau logo pas aligné avec le
   texte) — vertical-align dépend de la baseline du texte et ne recentre pas
   fiablement une icône dont le poids visuel dans son viewBox diffère de
   l'ancien logo ; le flex sur le conteneur centre les deux au même axe quel
   que soit le logo utilisé. */
.brand-logo-icon {
  height: 2.2em;
  width: auto;
  margin-right: 0.2em;
}

/* Login/register (04/09/2026) : le logo y est inliné en SVG (pas un <img>,
   qui ne peut pas hériter des couleurs de la page) avec ses tracés noirs
   remplacés par currentColor, pour prendre la couleur du thème actif au lieu
   du noir fixe utilisé ailleurs (sidebar/favicon, qui restent sur le fichier
   .svg externe noir). L'étiquette du sachet reste blanche (fill:#ffffff en
   dur dans le SVG, non concernée par currentColor). */
.login-box .brand-logo-icon {
  color: var(--primary-color);
}

.sidebar-menu {
  list-style: none;
  /* Espacements resserrés (retour du 24/08/2026 : "Déconnexion" toujours coupée en
     bas) — plutôt que de dépendre d'un calcul de hauteur précis (100dvh peut encore
     varier légèrement selon l'état de la barre d'outils Safari au moment du rendu),
     on réduit franchement l'espace pris par les 9 éléments pour garantir une marge
     confortable sur toutes les tailles d'iPad. flex:1/min-height:0/overflow:hidden :
     filet de sécurité ultime, clippe plutôt que déborder si jamais ça ne suffisait
     toujours pas (ne devrait plus jamais se déclencher avec ces tailles). */
  padding: var(--spacing-md) 0;
  flex: 1;
  min-height: 0;
  overflow: hidden;
}

.sidebar-menu li {
  margin-bottom: 2px;
}

.menu-item {
  display: flex;
  align-items: center;
  color: var(--text-secondary);
  text-decoration: none;
  padding: 0.65rem var(--spacing-xl);
  transition: var(--transition-smooth);
  border-radius: 0 var(--border-radius-large) var(--border-radius-large) 0;
  margin-right: var(--spacing-md);
  font-size: 17px;
  font-weight: 500;
  letter-spacing: -0.24px;
  position: relative;
}

.menu-item:hover {
  background: rgba(0, 122, 255, 0.08);
  color: var(--primary-color);
  transform: translateX(4px);
}

.menu-item.active {
  background: var(--primary-color);
  color: white;
  box-shadow: var(--shadow-sm);
}

.menu-item i {
  margin-right: var(--spacing-md);
  width: 20px;
  font-size: 18px;
  text-align: center;
}

.menu-item.logout {
  border-top: 1px solid var(--separator-color);
  /* Espace réduit (retour du 24/08/2026, "Déconnexion" coupée) — était spacing-xl
     (32px) sur margin ET padding, un bloc de plus de 64px avant même l'item lui-même,
     largement suffisant pour pousser Déconnexion hors des tailles d'iPad les plus
     compactes. La séparation reste visible via la bordure. */
  margin-top: var(--spacing-md);
  padding-top: var(--spacing-md);
}

.menu-item.logout:hover {
  background: rgba(255, 59, 48, 0.08);
  color: var(--danger-color);
}

/* Contenu principal iOS */
/* Habillage "verre dépoli" étendu à toute l'app (demande du 01/09/2026, après
   validation du rendu sur l'onglet Statistiques seul) — le fond en dégradés
   violet/bleu/blanc vit ici, sur .main-content, donc sous toutes les pages
   authentifiées (Dashboard/Clients/Groupes/Récompenses/Campagnes/Jeux concours/
   Statistiques/Mon Compte/Réglages) et sous les modales (voir #app plus bas, qui
   redéfinit --bg-primary/--bg-secondary/--bg-tertiary en translucide pour que les
   cartes/tableaux/formulaires — tous déjà construits sur ces 3 variables — captent
   ce fond au lieu de rester opaques). N'affecte PAS l'écran de connexion/inscription
   (en dehors de #app dans le DOM, voir index.html) ni la sidebar (déjà glass de son
   côté, indépendante). Pas de background-attachment:fixed : bugué sur iOS Safari
   (poste principal de l'app, voir PRODUCT.md), le dégradé défile avec le contenu. */
.main-content {
  flex: 1;
  margin-left: 280px;
  background:
    radial-gradient(1100px 620px at 6% -10%, rgba(94, 92, 230, 0.22), transparent 60%),
    radial-gradient(900px 560px at 100% -6%, rgba(90, 200, 250, 0.2), transparent 58%),
    radial-gradient(1000px 700px at 46% 115%, rgba(175, 82, 222, 0.14), transparent 62%),
    linear-gradient(180deg, #eef1fb 0%, #f5f7fd 55%, #eef2fc 100%);
  min-height: 100vh;
  /* Filet de sécurité : si un enfant dépasse malgré tout de quelques pixels (texte
     non cassable, grille mal calculée...), ça reste invisible au lieu d'ouvrir un
     scroll horizontal sur toute la page — surtout utile sur tablette (769-1280px),
     où la sidebar fixe de 280px laisse une largeur de contenu plus juste. "clip" (pas
     juste "hidden") : voir le commentaire détaillé sur html en haut du fichier —
     hidden seul n'empêche pas toujours le scroll déclenché au doigt sous Safari/iPad. */
  overflow-x: hidden;
  overflow-x: clip;
  max-width: 100%;
}

/* #app (pas .main-content) : englobe aussi les modales (voir index.html, elles sont
   soeurs de <main class="main-content"> mais toutes deux enfants de #app), donc les
   redéfinir ici plutôt que sur .main-content leur applique la même translucidité —
   sinon les modales resteraient opaques, seule incohérence visible en ouvrant
   n'importe quelle fenêtre par-dessus une page devenue vitrée. Ne touche jamais
   l'écran de connexion/inscription (hors de #app) ni la sidebar (déjà glass,
   indépendante de ces 3 variables). */
#app {
  --bg-primary: rgba(238, 241, 251, 0.55);
  --bg-secondary: rgba(255, 255, 255, 0.62);
  --bg-tertiary: rgba(249, 249, 253, 0.55);
}

/* Flou d'arrière-plan sur les grands conteneurs qui utilisent ces 3 variables — une
   variable CSS ne peut pas ajouter une propriété qu'un sélecteur ne déclare pas
   déjà (même piège déjà rencontré et documenté le 26/08/2026, voir Session -
   2026-08-26 dans le vault Obsidian), donc backdrop-filter doit être posé
   explicitement ici plutôt que de compter sur la redéfinition de --bg-* seule.
   .main-header exclu : traité à part juste au-dessus, plus opaque pour une raison
   précise (bug de trainée colorée au scroll). Liste non exhaustive par nature — un
   composant plus rare resterait translucide (via la variable) mais sans flou,
   correct mais moins glossy, à compléter au cas par cas si un oubli remonte. */
.ios-card,
.stat-card,
.table-container,
.client-card,
.reward-card,
.campaign-card,
.settings-form,
.account-form,
.account-danger-zone,
.modal-content,
.group-card,
.group-preset-card,
.contest-card,
.campaign-type-card,
.chart-card:not(.stats-card) {
  backdrop-filter: blur(20px) saturate(160%);
  -webkit-backdrop-filter: blur(20px) saturate(160%);
}

.main-header {
  /* Volontairement PLUS opaque que les 62% de --bg-secondary ci-dessous (voir bloc
     #app) : un bouton bleu saturé juste sous l'en-tête (ex. "Créer une campagne")
     transparaissait déjà à travers un fond translucide en une trainée bleue floutée
     peu jolie au scroll — retour du 20/08/2026, toujours valable. 90% + flou reste
     "verre" (cohérent avec le reste de l'app) sans reproduire ce bug. */
  background: rgba(255, 255, 255, 0.9);
  backdrop-filter: blur(20px) saturate(160%);
  -webkit-backdrop-filter: blur(20px) saturate(160%);
  padding: var(--spacing-xl) var(--spacing-2xl);
  border-bottom: 1px solid var(--separator-color);
  box-shadow: var(--shadow-sm);
  display: flex;
  justify-content: space-between;
  align-items: center;
  position: sticky;
  top: 0;
  z-index: 100;
}

.hamburger-menu {
  display: none;
}

.main-header h1 {
  color: var(--text-primary);
  font-size: 28px;
  font-weight: 700;
  letter-spacing: -0.5px;
  margin: 0;
}

.user-info {
  display: flex;
  align-items: center;
  color: var(--text-secondary);
  font-size: 17px;
  font-weight: 500;
  letter-spacing: -0.24px;
}

/* Sections de contenu iOS */
.content-section {
  display: none;
  padding: var(--spacing-2xl);
}

.content-section.active {
  display: block;
}

.section-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: var(--spacing-lg);
}

.section-header h2 {
  color: var(--text-primary);
  font-size: 34px;
  font-weight: 700;
  letter-spacing: -0.5px;
  margin: 0;
}

.section-header .btn-primary {
  width: auto;
  min-width: 160px;
}

/* Boutons "Créer une campagne Email/SMS" — Email aligné à gauche, SMS à droite
   (retour du 24/08/2026), pas collés aux bords : .campaigns-section a déjà son
   propre padding (1.5rem), donc space-between à l'intérieur de ce padding suffit,
   pas besoin d'un padding supplémentaire ici. Wrap + largeur mini permet un retour à
   la ligne propre en dessous du .btn-primary défaut (100%) plutôt qu'un écrasement,
   si jamais l'espace manque. */
.campaign-create-buttons {
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--spacing-md);
}

.campaign-create-buttons .btn-primary {
  width: auto;
  flex: 0 1 260px;
  max-width: 320px;
}

.groups-header-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-md);
}

.groups-header-actions .btn-secondary {
  width: auto;
}

/* 620px : largeur unifiée des modales de contenu, voir #groupDetailsModal plus bas. */
#groupPresetsModal .modal-content {
  max-width: 620px;
}

.group-presets-body {
  padding: var(--spacing-xl);
  max-height: 75vh;
  overflow-y: auto;
}

.group-presets-intro {
  color: var(--text-secondary);
  font-size: 0.9rem;
  margin: 0 0 var(--spacing-lg);
}

.group-presets-list {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-md);
}

.group-preset-card {
  display: flex;
  align-items: center;
  gap: var(--spacing-md);
  padding: var(--spacing-md) var(--spacing-lg);
  border: 1px solid var(--separator-color);
  border-left: 4px solid var(--preset-color, var(--primary-color));
  border-radius: var(--border-radius-medium);
  cursor: pointer;
  transition: background 0.15s ease;
}

.group-preset-card:hover {
  background: var(--bg-tertiary);
}

.group-preset-icon {
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Repli pour Safari/iPadOS < 16.2 (color-mix pas supporté) : fond neutre plutôt
     que teinté, la propriété suivante prend le dessus si color-mix est reconnu. */
  background: var(--bg-tertiary);
  background: color-mix(in srgb, var(--preset-color, var(--primary-color)) 15%, transparent);
  color: var(--preset-color, var(--primary-color));
  font-size: 1rem;
}

.group-preset-info h4 {
  margin: 0 0 2px;
  font-size: 0.95rem;
  color: var(--text-primary);
}

.group-preset-info p {
  margin: 0;
  font-size: 0.82rem;
  color: var(--text-secondary);
}

@media (max-width: 768px) {
  .groups-header-actions {
    width: 100%;
    flex-direction: column;
  }

  .groups-header-actions button {
    width: 100%;
  }
}

/* Dashboard iOS */
.stats-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: var(--spacing-lg);
  margin-bottom: var(--spacing-2xl);
}

.stat-card {
  background: var(--bg-secondary);
  padding: var(--spacing-xl);
  border-radius: var(--border-radius-large);
  box-shadow: var(--shadow-sm);
  border: 1px solid var(--separator-color);
  display: flex;
  align-items: center;
  transition: var(--transition-smooth);
  position: relative;
  overflow: hidden;
}

.stat-card::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 4px;
  height: 100%;
  background: linear-gradient(
    135deg,
    var(--primary-color),
    var(--primary-light)
  );
  border-radius: 0 2px 2px 0;
}

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

/* Cartes du dashboard qui renvoient vers leur page (Clients/Groupes/Récompenses/
   Campagnes, Points distribués -> onglet Statistiques). */
.stat-card-link {
  cursor: pointer;
}

.stat-icon {
  background: linear-gradient(
    135deg,
    var(--primary-color),
    var(--primary-light)
  );
  color: white;
  width: 60px;
  height: 60px;
  border-radius: var(--border-radius-medium);
  display: flex;
  align-items: center;
  justify-content: center;
  margin-right: var(--spacing-lg);
  font-size: 24px;
  box-shadow: var(--shadow-sm);
}

.stat-info h3 {
  font-size: 32px;
  font-weight: 700;
  color: var(--text-primary);
  margin-bottom: var(--spacing-xs);
  letter-spacing: -0.5px;
}

.stat-info p {
  color: var(--text-secondary);
  font-size: 17px;
  font-weight: 500;
  letter-spacing: -0.24px;
  margin: 0;
}

.dashboard-charts {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.5rem;
}

/* Onglet Statistiques : plusieurs encadrés .chart-card côte à côte au lieu de la
   colonne unique du dashboard principal (qui n'a qu'un seul chart-card, "Activité
   récente"). Cartes plus larges que la grille de base (340 au lieu de 280) : les
   encadrés statistiques contiennent plus de contenu (légendes, listes de 10 clients)
   qu'une simple carte compteur et ont besoin de plus de place pour rester lisibles. */
/* 3 colonnes fixes (pas auto-fit) : avec une largeur fluide, le nombre de colonnes
   change selon l'écran et casse le pavage des cartes "larges" (stats-card-wide,
   span 2) — sur un moniteur très large, auto-fit pouvait donner 4 ou 5 colonnes, et
   une carte span-2 ne retombe plus jamais pile avec les cartes simples autour :
   trous et décalages selon la largeur d'écran. À 3 colonnes fixes, les 8 encadrés
   sont assemblés en alternant strictement Large(span 2)/Étroit(span 1) — voir
   l'ordre exact dans renderStatsBlocks (app.js) : Panier[L] Points[E] Clients[L]
   Engagement[E] Top10 points[L] Récompenses[E] Top10 visites[L] Campagnes[E]. Chaque
   paire Large+Étroit remplit exactement une rangée (2+1=3 colonnes), 4 rangées
   pleines sans le moindre trou — un vrai quadrillage, plutôt qu'une grille qui
   "respire" différemment selon la taille de la fenêtre. */
.stats-charts {
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1.75rem;
  /* stretch (pas start) : les cartes d'une même rangée s'alignent sur la même
     hauteur, ex. "Points"/"Engagement" à côté d'un "Clients" plus haut (genre +
     consentement) — évite l'effet de bas de cartes en escalier. */
  align-items: stretch;
}

.stats-charts .stats-card {
  display: flex;
  flex-direction: column;
  height: 100%;
}

/* Hauteur plancher commune aux cartes "simples" (Points/Engagement/Récompenses/
   Campagnes) : align-items: stretch n'égalise que les cartes d'une même rangée,
   pas d'une rangée à l'autre — sans ce plancher, la rangée Points/Engagement
   pouvait rester nettement plus basse que la rangée Récompenses/Campagnes juste en
   dessous, cassant l'effet de quadrillage uniforme recherché. Les cartes larges
   (Top 10, listes de 10 lignes) le dépassent naturellement, pas besoin d'y toucher. */
.stats-charts .stats-card:not(.stats-card-wide) {
  min-height: 300px;
}

/* Pousse la note en petit texte (ex. "Faute d'historique...") en bas de carte même
   quand une carte voisine, plus haute sur la même rangée (voir align-items: stretch
   ci-dessus), laisse de l'espace vide — sinon la note se retrouvait collée juste sous
   le contenu, avec le reste de la carte vide en dessous, pendant que la carte d'à
   côté remplissait toute sa hauteur : rangée visuellement déséquilibrée. */
.stats-charts .stats-kpi-note {
  margin-top: auto;
  padding-top: 0.75rem;
}

/* Le fond en dégradés violet/bleu vit désormais sur .main-content (étendu à toute
   l'app le 01/09/2026, voir ce commentaire là-bas) — plus besoin d'un fond dédié ici,
   #statsContent hérite du même fond que toutes les autres pages pour un rendu
   cohérent d'un onglet à l'autre. */

/* Filet de couleur à gauche retiré au profit d'un liseré haut lumineux (glossy
   highlight), plus cohérent avec des panneaux de verre qu'une barre pleine — voir
   .stats-card-icon plus bas pour où la couleur de marque continue de vivre. */
.stats-card {
  position: relative;
  overflow: hidden;
}

.stats-card::before {
  content: "";
  position: absolute;
  top: 0;
  left: 8%;
  right: 8%;
  height: 1px;
  background: linear-gradient(
    90deg,
    transparent,
    rgba(255, 255, 255, 0.95),
    transparent
  );
}

/* En-tête de l'onglet Statistiques : le sélecteur de période (avec son label
   au-dessus) et le bouton de bascule graphiques/chiffres n'ont pas la même hauteur
   naturelle — les aligner sur leur bas (flex-end) plutôt que leur centre donne un
   bord inférieur commun, net, au lieu d'un décalage vertical visible entre les deux. */
.stats-header {
  align-items: flex-end;
  flex-wrap: wrap;
  gap: 1rem;
}

.stats-period-field {
  margin-bottom: 0;
}

/* En-tête assorti aux panneaux de verre en dessous : select + bouton en pastille
   translucide plutôt que le blanc opaque par défaut de .form-group select/.btn-secondary. */
.stats-header select {
  background: rgba(255, 255, 255, 0.6);
  backdrop-filter: blur(16px) saturate(160%);
  -webkit-backdrop-filter: blur(16px) saturate(160%);
  border: 1px solid rgba(255, 255, 255, 0.7);
}

.stats-header select:focus {
  box-shadow: 0 0 0 4px rgba(94, 92, 230, 0.14);
}

.stats-header .btn-secondary {
  background: rgba(255, 255, 255, 0.6);
  backdrop-filter: blur(16px) saturate(160%);
  -webkit-backdrop-filter: blur(16px) saturate(160%);
  border-color: rgba(255, 255, 255, 0.7);
  box-shadow: 0 4px 14px rgba(76, 60, 190, 0.1);
}

@media (max-width: 640px) {
  .stats-header {
    align-items: stretch;
  }

  .stats-period-field,
  .stats-header #statsViewToggle {
    width: 100%;
  }
}

/* Panneau de verre dépoli : fond blanc très translucide + flou d'arrière-plan (capte
   les dégradés violet/bleu de #statsContent derrière), bordure claire fine en guise de
   reflet, ombre douce teintée (jamais un simple gris neutre) pour l'effet "flottant".
   -webkit-backdrop-filter dupliqué pour Safari/iPadOS (poste principal de l'app, voir
   PRODUCT.md) qui ne suit pas encore la propriété standard seule. */
.stats-card {
  padding: var(--spacing-xl, 1.5rem);
  background: linear-gradient(
    165deg,
    rgba(255, 255, 255, 0.72) 0%,
    rgba(255, 255, 255, 0.4) 100%
  );
  backdrop-filter: blur(24px) saturate(160%);
  -webkit-backdrop-filter: blur(24px) saturate(160%);
  border: 1px solid rgba(255, 255, 255, 0.6);
  border-radius: var(--border-radius-xl);
  box-shadow:
    0 8px 32px rgba(76, 60, 190, 0.14),
    inset 0 1px 0 rgba(255, 255, 255, 0.65);
  transition: var(--transition-smooth);
}

.stats-card:hover {
  box-shadow:
    0 16px 44px rgba(76, 60, 190, 0.2),
    inset 0 1px 0 rgba(255, 255, 255, 0.75);
  border-color: rgba(255, 255, 255, 0.8);
  transform: translateY(-3px);
}

.stats-card h3 {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  font-size: 1.05rem;
  font-weight: 700;
  letter-spacing: -0.24px;
  color: var(--text-primary);
  margin-bottom: 1rem;
}

/* Badge glossy violet → bleu, fixe (pas la couleur de thème du marchand) : c'est la
   signature de ce nouvel habillage Statistiques, cohérente quel que soit le thème
   choisi dans Réglages — le reste de l'app garde --primary-color, cette section a sa
   propre identité visuelle assumée. Reflet interne (inset) en haut à gauche pour le
   rendu "surface glossy" demandé, pas un simple aplat de couleur. */
.stats-card-icon {
  flex-shrink: 0;
  width: 34px;
  height: 34px;
  border-radius: var(--border-radius-medium);
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(135deg, #6a5cff 0%, #5ac8fa 100%);
  color: white;
  font-size: 14px;
  box-shadow:
    0 4px 14px rgba(94, 92, 230, 0.4),
    inset 0 1px 1px rgba(255, 255, 255, 0.55);
}

/* Cartes au contenu plus dense (Top 10, Clients) : sur 2 colonnes de large sur la
   grille à 3 colonnes du desktop, pour éviter d'entasser noms/barres dans une
   colonne étroite. Repasse à 1 colonne (voir la règle .stats-card-wide dans les
   paliers tablette/mobile plus bas) dès que la grille elle-même n'a plus que 1 ou 2
   colonnes — span 2 sur une grille à 2 colonnes prendrait toute la ligne à lui seul
   et casserait le pavage régulier avec les cartes simples. */
.stats-card-wide {
  grid-column: span 2;
}

.stats-kpi-list {
  list-style: none;
  padding: 0;
  margin: 0;
}

.stats-kpi-list li {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  padding: 0.5rem 0.6rem;
  border-radius: var(--border-radius-medium);
  color: var(--text-secondary);
  font-size: 0.9rem;
  transition: var(--transition-quick);
}

/* Zébrage (nth-child) retiré : sur des listes courtes (2-4 lignes) ça se lisait comme
   un motif aléatoire plutôt qu'une aide à la lecture (retour utilisateur) — remplacé
   par un simple séparateur fin, plus discret. */
.stats-kpi-list li:not(:last-child) {
  border-bottom: 1px solid var(--separator-color);
}

.stats-kpi-list li:hover {
  background: rgba(94, 92, 230, 0.1);
}

.stats-kpi-value {
  font-weight: 700;
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
}

.stats-kpi-note {
  color: var(--text-tertiary, #8e8e93);
  font-size: 0.8rem;
  margin-top: 0.5rem;
}

.stats-subheading {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text-secondary);
  margin: 1rem 0 0.5rem;
}

/* Camembert en CSS pur (conic-gradient posé en background), pas de librairie de
   graphiques externe. */
.stats-chart-wrap {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  flex-wrap: wrap;
}

/* Vrai anneau (pas un camembert plein) : un masque radial perce un trou au centre,
   indépendant de la couleur derrière (contrairement à un cercle central "peint" de la
   couleur du fond, qui aurait fait une couture visible sur le dégradé de .stats-card).
   Le total choisi s'affiche dans le trou via .stats-donut-center. */
.stats-donut-wrap {
  position: relative;
  width: 140px;
  height: 140px;
  min-width: 140px;
  flex-shrink: 0;
}

.stats-donut {
  width: 100%;
  height: 100%;
  border-radius: 50%;
  -webkit-mask: radial-gradient(circle, transparent 58%, black 59%);
  mask: radial-gradient(circle, transparent 58%, black 59%);
  box-shadow:
    0 6px 20px rgba(76, 60, 190, 0.22),
    inset 0 1px 1px rgba(255, 255, 255, 0.5);
  transition: var(--transition-smooth);
}

.stats-donut-center {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
}

.stats-donut-center-value {
  font-size: 1.3rem;
  font-weight: 800;
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.5px;
}

.stats-donut-wrap:hover .stats-donut {
  transform: scale(1.04);
}

.stats-chart-legend {
  list-style: none;
  padding: 0;
  margin: 0;
  flex: 1;
  min-width: 180px;
}

.stats-chart-legend li {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.25rem 0;
  color: var(--text-secondary);
  font-size: 0.9rem;
}

.stats-chart-legend li .stats-kpi-value {
  margin-left: auto;
}

.stats-chart-swatch {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  flex-shrink: 0;
}

/* Graphique en barres horizontales (top 10 clients, consentement...). */
.stats-bar-chart {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

/* Label au-dessus de la barre (pas à côté) : un nom/prénom complet ne doit jamais être
   coupé (ex. "Christelle Cha…") — cette disposition verticale garantit qu'il reste
   toujours entièrement lisible, quelle que soit la largeur de la carte. */
.stats-bar-row {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  font-size: 0.9rem;
}

.stats-bar-top {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.75rem;
}

.stats-bar-label {
  color: var(--text-secondary);
  overflow-wrap: anywhere;
}

/* Top 10 (voir opts.clientIds dans buildBarChart) : plus espacé que les barres
   génériques (consentement, panier...) pour que chaque ligne se distingue bien comme
   une cible cliquable à part entière, pas un simple élément de liste dense. */
.stats-bar-chart-clients {
  gap: 1.1rem;
}

.stats-bar-label-clickable {
  cursor: pointer;
  color: var(--primary-color);
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-color: transparent;
  transition: var(--transition-quick);
}

.stats-bar-label-clickable:hover {
  text-decoration-color: currentColor;
}

/* Version "chiffres bruts" du Top 10 (voir clientRow dans app.js) — même principe
   que .stats-bar-chart-clients : lignes cliquables, plus espacées que les autres
   listes de KPI (consentement, notes...). */
.stats-client-row {
  cursor: pointer;
  padding-top: 0.75rem;
  padding-bottom: 0.75rem;
}

.stats-client-name {
  color: var(--primary-color);
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-color: transparent;
  transition: var(--transition-quick);
}

.stats-client-row:hover .stats-client-name {
  text-decoration-color: currentColor;
}

/* Badge de classement (Top 10 uniquement, voir opts.showRank dans buildBarChart) — or/
   argent/bronze pour le podium, neutre au-delà. */
.stats-bar-rank {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  margin-right: 0.4rem;
  border-radius: 50%;
  font-size: 0.7rem;
  font-weight: 700;
  color: white;
  background: var(--text-tertiary, #8e8e93);
  vertical-align: middle;
}

.stats-bar-rank-1 {
  background: linear-gradient(135deg, #ffd76a, #e0a52c);
}

.stats-bar-rank-2 {
  background: linear-gradient(135deg, #e4e6eb, #b0b4bd);
}

.stats-bar-rank-3 {
  background: linear-gradient(135deg, #e3a370, #b5723e);
}

.stats-bar-track {
  background: rgba(94, 92, 230, 0.08);
  border-radius: 999px;
  height: 10px;
  overflow: hidden;
  box-shadow: inset 0 1px 3px rgba(76, 60, 190, 0.12);
}

.stats-bar-fill {
  height: 100%;
  border-radius: 999px;
  /* --bar-color posé en inline (voir buildBarChart dans app.js) : la couleur change par
     barre (palette tournante), le dégradé lui-même reste défini ici en CSS.
     background-color en repli si color-mix() n'est pas supporté (vieux navigateur). */
  background-color: var(--bar-color);
  background-image: linear-gradient(
    90deg,
    var(--bar-color) 0%,
    color-mix(in srgb, var(--bar-color) 70%, white) 100%
  );
  transition: width 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

.stats-bar-value {
  color: var(--text-primary);
  font-weight: 600;
  white-space: nowrap;
  flex-shrink: 0;
}

/* :not(.stats-card) : .chart-card est réutilisé tel quel par "Activité récente" sur
   le Dashboard, mais partagé aussi par .stats-card (onglet Statistiques) — sans cette
   exclusion, ces règles (déclarées après .stats-card plus haut dans ce fichier)
   écrasaient silencieusement le fond dégradé/bordure/ombre voulus pour les cartes de
   stats avec un simple fond blanc plat, d'où l'incohérence visuelle observée. */
.chart-card:not(.stats-card) {
  background: var(--bg-secondary);
  padding: 1.5rem;
  border-radius: 10px;
  box-shadow: var(--shadow);
}

.chart-card:not(.stats-card) h3 {
  color: var(--secondary-color);
  margin-bottom: 1rem;
}

.activity-list {
  max-height: 300px;
  overflow-y: auto;
}

.activity-item {
  padding: 0.75rem;
  border-bottom: 1px solid #ecf0f1;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.activity-item:last-child {
  border-bottom: none;
}

.no-data {
  text-align: center;
  color: #666;
  font-style: italic;
  padding: 2rem;
}

/* Invite affichée à la place du panel de Statistiques tant que le ratio
   points/euro n'a pas été défini (voir openStatsPage dans app.js) — span sur toute
   la grille pour ne pas se retrouver comprimée dans une seule cellule de colonne. */
.stats-setup-placeholder {
  grid-column: 1 / -1;
  text-align: center;
  padding: 3rem 1.5rem;
  background: var(--bg-secondary);
  border: 1px solid var(--separator-color);
  border-radius: var(--border-radius-large);
  box-shadow: var(--shadow-sm);
}

.stats-setup-placeholder i {
  font-size: 2rem;
  color: var(--primary-color);
  margin-bottom: 1rem;
}

.stats-setup-placeholder p {
  max-width: 420px;
  margin: 0 auto 1.25rem;
  color: var(--text-secondary);
}

/* Barre de recherche */
.search-bar {
  margin-bottom: 1.5rem;
  display: flex;
  align-items: center;
  gap: var(--spacing-lg);
  flex-wrap: wrap;
}

.search-bar input {
  flex: 1;
  max-width: 400px;
  min-width: 250px;
  padding: var(--spacing-sm) var(--spacing-lg);
  border: 1px solid var(--separator-color);
  border-radius: var(--border-radius-xl);
  font-size: 15px;
  font-weight: 400;
  letter-spacing: -0.24px;
  background: var(--bg-secondary);
  color: var(--text-primary);
  transition: var(--transition-quick);
  height: 50px;
}

.search-bar input:focus {
  outline: none;
  border-color: var(--primary-color);
  box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.1);
  background: var(--bg-primary);
}

.search-bar .btn-primary {
  flex-shrink: 0;
  height: 50px;
  min-height: 50px;
  border-radius: var(--border-radius-xl);
  padding: 0 var(--spacing-lg);
  width: auto;
  min-width: auto;
  white-space: nowrap;
}

/* Bouton icône filtre, à côté de la barre de recherche clients */
.search-bar #toggleClientFilters {
  flex-shrink: 0;
  height: 50px;
  width: 50px;
  min-height: 50px;
  min-width: 50px;
  padding: 0;
  border-radius: var(--border-radius-xl);
  display: flex;
  align-items: center;
  justify-content: center;
}

.search-bar #toggleClientFilters.active {
  background: var(--primary-color);
  color: white;
}

/* Panneau de filtre/tri de la liste clients.
   [hidden] doit rester prioritaire sur .client-filters-panel { display: flex } (sinon le
   panneau reste visible même fermé, la règle de classe ayant plus de poids que l'attribut
   HTML) — d'où le second sélecteur ci-dessous, plus spécifique. */
.client-filters-panel {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--spacing-md);
  background: var(--bg-secondary);
  border: 1px solid var(--separator-color);
  border-radius: var(--border-radius-medium);
  padding: var(--spacing-md);
  margin-bottom: 1.5rem;
  font-size: 13px;
}

.client-filters-panel[hidden] {
  display: none;
}

.client-filters-panel .form-group {
  margin-bottom: 0;
  flex: 1 1 160px;
  min-width: 140px;
}

.client-filters-panel .form-group label {
  font-size: 12px;
  margin-bottom: 4px;
}

.client-filters-panel select,
.client-filters-panel input {
  padding: 8px 10px;
  font-size: 13px;
  height: 38px;
}

.client-filters-panel #clientFilterMinVisits {
  min-width: 100px;
}

.client-filters-panel #resetClientFilters {
  flex: 0 0 auto;
  padding: 8px 16px;
  height: 38px;
  font-size: 13px;
}

/* Tablette : 2 champs par ligne au lieu d'un empilement/étirement peu lisible */
@media (max-width: 1280px) {
  .client-filters-panel {
    gap: var(--spacing-sm);
  }

  .client-filters-panel .form-group {
    flex: 1 1 calc(50% - var(--spacing-sm));
    min-width: 130px;
  }
}

@media (max-width: 480px) {
  .client-filters-panel .form-group {
    flex: 1 1 100%;
  }
}

/* Message "recherche approximative" au-dessus de la liste clients — marge haute
   généreuse pour bien le détacher de la barre de recherche juste au-dessus (sinon les
   deux se touchent visuellement et on ne sait plus lequel appartient à quoi). */
.approximate-search-notice {
  margin: 1.5rem 0 0.75rem;
  color: var(--text-secondary);
  font-style: italic;
  font-size: 0.9rem;
}

/* Badge "≈" posé sur un client qui n'a matché que la recherche approximative
   (fautes de frappe/sonorités), à côté d'un résultat exact trouvé par ailleurs
   — voir clientMatchesFuzzy/clientMatchesPhonetic et getFilteredSortedClients
   dans app.js. Couleur ambre : ni une erreur (rouge) ni un résultat sûr (bleu
   du badge points), juste "à vérifier". */
.approx-badge {
  display: inline-block;
  color: #b45309;
  background: rgba(245, 158, 11, 0.15);
  border-radius: 999px;
  width: 18px;
  height: 18px;
  line-height: 18px;
  text-align: center;
  font-size: 12px;
  font-weight: 700;
  vertical-align: middle;
}

/* Séparateur inséré entre les résultats exacts et les suggestions approximatives
   dans la liste clients (table desktop ET cartes mobile), voir renderClientsTable/
   renderMobileClients dans app.js. */
.approx-divider-row td,
.approx-divider {
  padding: 0.6rem 1rem;
  color: var(--text-tertiary, #8e8e93);
  font-size: 0.8rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  border-top: 1px dashed var(--separator-color);
}

.approx-divider {
  margin: 0.75rem 0 0.25rem;
}

/* Tables */
.table-container {
  background: var(--bg-secondary);
  border-radius: 10px;
  overflow-x: auto;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  box-shadow: var(--shadow);
}

/* Version cards (mobile/tablette) de la liste clients - cachée par défaut, affichée en
   dessous du seuil ci-dessous.
   Le seuil est à 1280px, largement au-dessus de la largeur logique réelle de n'importe
   quel iPad (744 à 1366px selon modèle/orientation) : iPadOS Safari active "Demander la
   version bureau" par défaut sur la plupart des sites, ce qui fait reporter à la page une
   largeur de fenêtre virtuelle fixe d'environ 1024-1112px QUELLE QUE SOIT l'orientation
   réelle de l'iPad. Un seuil à 1024px ne se déclenche donc jamais de façon fiable sur
   iPad (d'où le tableau qui débordait malgré le breakpoint) — 1280px met tous les iPad,
   portrait et paysage, du bon côté du seuil dans les deux modes Safari. Sur un vrai
   écran large (laptop/desktop), le tableau reste affiché au-delà de 1280px. */
.mobile-table {
  display: none !important;
}

@media (min-width: 1281px) {
  .mobile-table {
    display: none !important;
  }

  .table-container {
    display: block !important;
  }
}

@media (max-width: 1280px) {
  .table-container {
    display: none !important;
  }

  .mobile-table {
    display: block !important;
  }
}

/* Cards clients — non scopées à un media query : actives dès que .mobile-table est
   affichée (donc jusqu'à 1280px), pour ne pas se retrouver sans style entre 768px et
   1280px. */
.client-card {
  background: var(--bg-secondary);
  border-radius: 8px;
  box-shadow: var(--shadow);
  margin-bottom: 1rem;
  padding: 1rem;
  position: relative;
}

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

.client-name {
  font-weight: bold;
  color: var(--secondary-color);
  font-size: 1.1rem;
  min-width: 0;
  overflow-wrap: break-word;
  word-break: break-word;
}

.client-points {
  background: var(--primary-color);
  color: white;
  padding: 0.25rem 0.75rem;
  border-radius: 12px;
  font-size: 0.8rem;
  font-weight: bold;
  flex-shrink: 0;
  white-space: nowrap;
}

.client-details {
  margin-bottom: 1rem;
}

.client-detail {
  display: flex;
  align-items: center;
  margin-bottom: 0.5rem;
  font-size: 0.9rem;
  color: #666;
}

.client-detail i {
  width: 16px;
  flex-shrink: 0;
  margin-right: 0.5rem;
  color: var(--primary-color);
}

.client-detail span {
  min-width: 0;
  overflow-wrap: break-word;
  word-break: break-word;
}

.client-actions {
  display: flex;
  flex-wrap: nowrap;
  gap: var(--spacing-xs);
}

.client-actions button {
  flex: 1 1 0;
  min-width: 0;
  padding: var(--spacing-sm) var(--spacing-xs);
  border-radius: var(--border-radius-large);
  font-size: 13px;
  min-height: 40px;
  font-weight: 600;
  letter-spacing: -0.24px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--spacing-xs);
  transition: var(--transition-quick);
  white-space: nowrap;
}

.client-actions .btn-secondary {
  background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-light) 100%);
  color: white;
  border: none;
}

.client-actions .btn-secondary:hover {
  transform: translateY(-1px);
  box-shadow: var(--shadow-sm);
}

.client-actions .btn-danger:hover {
  transform: translateY(-1px);
}

/* Entre 769px et 1280px (tablettes, iPad compris), il y a la place de garder le texte
   des boutons d'action (Points/Modifier/Supprimer) au lieu de les réduire à des icônes
   seules comme sur téléphone — ne serrer qu'en dessous de 768px. */
@media (min-width: 769px) and (max-width: 1280px) {
  .client-card {
    padding: 1.25rem;
  }

  .client-actions button {
    min-height: 44px;
    font-size: 14px;
  }

  /* iPad 9"/11" (portrait comme paysage) : la sidebar fixe de 280px grignote une
     bonne partie de la largeur utile sur cette plage (ex. ~554px de contenu en
     portrait 834px) — 2 colonnes fixes (pas auto-fit, même raisonnement que la
     grille desktop à 3 colonnes juste au-dessus) plutôt que de laisser la largeur
     décider du nombre de colonnes. Les cartes "larges" repassent à span 1 (voir
     .stats-card-wide ci-dessous) : sur 2 colonnes, span 2 prendrait toute la ligne
     et casserait le pavage régulier avec les cartes simples. */
  .stats-charts {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 1.25rem;
  }

  .stats-card-wide {
    grid-column: span 1;
  }

  .stats-donut-wrap {
    width: 120px;
    height: 120px;
    min-width: 120px;
  }

  /* Cartes Jeux concours (.contest-card réutilise .campaign-header/.campaign-actions,
     voir renderContests() dans app.js) — la version empilée de ces classes n'existait
     qu'à partir de max-width:768px, donc jamais sur la plage iPad 769-1280px. Résultat
     sur cette plage : .campaign-header restait en ligne (justify-content:space-between)
     et les 3 boutons d'action (QR code/Modifier/Supprimer, avec icône+libellé) se
     retrouvaient écrasés dans le peu de largeur restante à côté du titre — "trop short
     dans les cases" (retour du 24/08/2026). Même traitement que la version mobile
     (empilement titre/actions), scopé à .contest-card pour ne pas changer le
     comportement des cartes Campagnes à cette largeur (pas demandé). */
  .contest-card .campaign-header {
    flex-direction: column;
    align-items: stretch;
    gap: 1rem;
  }

  .contest-card .campaign-actions {
    justify-content: center;
    gap: 0.75rem;
  }

  .contest-card .campaign-actions button {
    flex: 1 1 auto;
    min-height: 46px;
    padding: 0.7rem 0.9rem;
    font-size: 0.9rem;
    white-space: nowrap;
  }

  /* .section-header (titre + description + bouton "Créer...") : même piège que
     .contest-card ci-dessus — la version empilée n'existait qu'à partir de
     max-width:768px, jamais sur la plage iPad 769-1280px. Le bouton restait coincé à
     sa largeur minimale (160px) à côté d'un texte descriptif qui prend le reste de la
     ligne, forçant son propre libellé à casser sur 2-3 lignes ("Créer un jeu
     concours" notamment — retour du 24/08/2026, "c'est pas beau"). Concerne toutes
     les pages avec un bouton "Créer..." dans leur en-tête (Clients, Groupes,
     Récompenses, Campagnes, Jeux concours), pas seulement Jeux concours — même bug
     partagé par la même classe. Reprend le même empilement que la version mobile. */
  .section-header {
    flex-direction: column;
    align-items: stretch;
    gap: 1rem;
    text-align: center;
  }

  .section-header .btn-primary {
    width: 100%;
    max-width: 320px;
    margin: 0 auto;
  }

  /* Exception pour "Créer une campagne Email/SMS" (retour du 24/08/2026) : contrairement
     aux autres .section-header (texte + UN bouton, empilés proprement en colonne), ce
     bloc contient DEUX boutons côte à côte — l'empilement générique juste au-dessus les
     aurait mis l'un sous l'autre en pleine largeur. Row + wrap : côte à côte tant que la
     largeur le permet, repli propre en colonne seulement si vraiment trop étroit. */
  .campaign-create-buttons {
    flex-direction: row;
    align-items: stretch;
    justify-content: space-between;
  }

  .campaign-create-buttons .btn-primary {
    width: auto;
    flex: 1 1 200px;
    max-width: 280px;
    margin: 0;
  }

  /* La case texte (.contest-section-intro, voir plus bas dans le fichier) doit se
     centrer comme le bouton juste en dessous une fois le header empilé en colonne —
     sinon elle reste collée à gauche (align-items:stretch l'étire en largeur jusqu'à
     son max-width mais un bloc n'est jamais centré tout seul sans margin auto). */
  .contest-section-intro {
    max-width: 100%;
    margin: 0 auto;
  }
}

table {
  width: 100%;
  border-collapse: collapse;
  table-layout: fixed;
}

table th,
table td {
  padding: 1rem;
  text-align: left;
  border-bottom: 1px solid #ecf0f1;
}

/* Largeurs des colonnes spécifiques pour la table des clients */
#clientsTable th:nth-child(1),
#clientsTable td:nth-child(1) {
  /* Nom */
  width: 9%;
}

#clientsTable th:nth-child(2),
#clientsTable td:nth-child(2) {
  /* Prénom */
  width: 12%;
}

#clientsTable th:nth-child(3),
#clientsTable td:nth-child(3) {
  /* Téléphone */
  width: 18%;
}

#clientsTable th:nth-child(4),
#clientsTable td:nth-child(4) {
  /* Email */
  width: 27%;
}

#clientsTable th:nth-child(5),
#clientsTable td:nth-child(5) {
  /* Points */
  width: 12%;
}

#clientsTable th:nth-child(6),
#clientsTable td:nth-child(6) {
  /* Actions — élargie pour laisser respirer des boutons à 44px (voir .table-actions) */
  width: 22%;
  padding-right: 1.5rem;
  white-space: nowrap;
}

/* Les 3 boutons d'action (points/modifier/supprimer) utilisaient le style bouton "plein"
   (padding pensé pour du texte) sans jamais avoir été compactés pour cette colonne à
   largeur fixe : à partir d'une largeur de fenêtre modérée, ils dépassaient les 18%
   alloués et se faisaient couper net par le `overflow: hidden` du tableau.
   Le flex est posé sur un <div class="table-actions"> à l'intérieur de la <td> (voir
   renderClients() dans app.js), pas sur la <td> elle-même : Safari/WebKit (iPad) gère mal
   `display: flex` posé directement sur une cellule de tableau, ce qui cassait
   l'alignement de la ligne malgré un rendu correct sous Chrome. */
.table-actions {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

.table-actions button {
  /* 44px = taille minimale recommandée pour une cible tactile confortable (Apple/W3C),
     au lieu des 36px précédents un peu justes. */
  padding: 0;
  width: 44px;
  height: 44px;
  min-height: 44px;
  min-width: 44px;
  flex-shrink: 0;
  font-size: 16px;
  border-radius: var(--border-radius-large);
}

table th {
  background-color: var(--light-color);
  font-weight: 600;
  color: var(--secondary-color);
}

table tbody tr:hover {
  background-color: #f8f9fa;
  cursor: pointer;
}

table tbody tr:hover td {
  background-color: transparent;
}

/* Boutons d'actions dans les tables */
table td .btn-secondary,
table td .btn-danger,
table td .btn-success {
  padding: var(--spacing-xs) var(--spacing-sm);
  min-height: 36px;
  min-width: 36px;
  border-radius: var(--border-radius-large);
  font-size: 14px;
  margin: 0 var(--spacing-xs);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: var(--transition-quick);
}

table td .btn-success:hover {
  transform: translateY(-1px);
  box-shadow: var(--shadow-sm);
}

table td .btn-secondary {
  background: linear-gradient(
    135deg,
    var(--primary-color) 0%,
    var(--primary-light) 100%
  );
  color: white;
  border: none;
}

table td .btn-secondary:hover {
  transform: translateY(-1px);
  box-shadow: var(--shadow-sm);
}

table td .btn-danger {
  min-height: 36px;
  min-width: 36px;
}

table td .btn-danger:hover {
  transform: translateY(-1px);
}

/* Contrôles de tri dans les section-header */
.section-header .sort-controls {
  display: flex;
  align-items: center;
  gap: var(--spacing-md);
}

.section-header .sort-controls label {
  font-size: 15px;
  font-weight: 600;
  color: var(--text-primary);
  letter-spacing: -0.24px;
  white-space: nowrap;
}

.section-header .sort-controls select {
  padding: var(--spacing-sm) var(--spacing-md);
  border: 1px solid var(--separator-color);
  border-radius: var(--border-radius-small);
  background: var(--bg-secondary);
  color: var(--text-primary);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: -0.24px;
  min-width: 220px;
  transition: var(--transition-quick);
  cursor: pointer;
  height: 40px;
}

.section-header .sort-controls select:focus {
  outline: none;
  border-color: var(--primary-color);
  box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.1);
  background: var(--bg-primary);
}

.section-header .sort-controls select:hover {
  border-color: var(--primary-color);
}

/* Grille des récompenses */
.rewards-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 1.5rem;
}

/* Sépare visuellement les catégories de récompenses (classique / anniversaire) sur la
   page Récompenses — un simple filet, pas une carte, pour ne pas rivaliser avec le
   contenu de part et d'autre. */
.rewards-category-divider {
  height: 1px;
  background: var(--separator-color);
  margin: 2.5rem 0 1.5rem;
}

.rewards-category-title {
  margin: 0 0 0.25rem;
}

.reward-card {
  background: var(--bg-secondary);
  padding: 1.5rem;
  border-radius: 10px;
  box-shadow: var(--shadow);
  transition: var(--transition);
  cursor: pointer;
}

.reward-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 15px rgba(0, 0, 0, 0.15);
}

.reward-card h3 {
  color: var(--secondary-color);
  margin-bottom: 0.5rem;
}

.reward-points {
  color: var(--primary-color);
  font-weight: bold;
  font-size: 1.2rem;
  margin-bottom: 1rem;
}

.reward-description {
  color: #666;
  margin-bottom: 1rem;
  font-size: 0.9rem;
}

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

/* Liste des campagnes */
.campaigns-list {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.campaign-card {
  background: var(--bg-secondary);
  padding: 1.5rem;
  border-radius: 10px;
  box-shadow: var(--shadow);
  transition: var(--transition);
  cursor: pointer;
}

.campaign-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 15px rgba(0, 0, 0, 0.15);
}

.campaign-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-bottom: 1rem;
}

/* Bloc titre+badges (premier enfant, sans classe dédiée, voir renderCampaigns()/
   renderContests() dans app.js) : sans min-width:0, ce flex item garde la largeur
   de son contenu (titre long ou plusieurs badges) et peut pousser .campaign-actions
   hors de la carte plutot que de wrapper - meme piège que .contest-lot-row. */
.campaign-header > div:first-child {
  min-width: 0;
}

.campaign-title {
  color: var(--secondary-color);
  font-size: 1.2rem;
  margin-bottom: 0.5rem;
  overflow-wrap: break-word;
}

.campaign-types {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.campaign-type {
  background-color: var(--primary-color);
  color: white;
  padding: 0.25rem 0.75rem;
  border-radius: 15px;
  font-size: 0.8rem;
  font-weight: 500;
}

.campaign-type-email {
  background-color: #2196f3;
  color: white;
}

.campaign-type-sms {
  background-color: #4caf50;
  color: white;
}

.campaign-message {
  color: #666;
  margin-bottom: 1rem;
  font-style: italic;
}

.campaign-stats {
  display: flex;
  gap: 1rem;
  font-size: 0.9rem;
  color: #666;
}

.campaign-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 0.5rem;
  margin-top: 1rem;
}

/* Modal "Ajouter des points" */
.add-points-modal {
  max-width: 380px;
  /* Colonne flex + body extensible : le pied (Annuler/Ajouter) reste toujours
     visible, quelle que soit la hauteur du contenu (presets, pavé numérique...),
     au lieu de risquer d'être coupé par le max-height/overflow:hidden du
     .modal-content. */
  display: flex;
  flex-direction: column;
}

.add-points-modal .modal-header {
  flex-shrink: 0;
}

.add-points-modal .modal-body {
  flex: 1 1 auto;
  min-height: 0;
  padding: var(--spacing-xl);
  overflow-y: auto;
}

.add-points-modal .modal-actions {
  flex-shrink: 0;
  margin-top: 0;
  padding-left: var(--spacing-xl);
  padding-right: var(--spacing-xl);
  padding-bottom: var(--spacing-xl);
}

.add-points-client {
  display: flex;
  align-items: center;
  gap: var(--spacing-md);
  padding: var(--spacing-md);
  background: var(--bg-tertiary);
  border-radius: var(--border-radius-medium);
  margin-bottom: var(--spacing-sm);
}

.add-points-client-avatar {
  width: 44px;
  height: 44px;
  flex-shrink: 0;
  border-radius: 50%;
  background: var(--primary-color);
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 18px;
}

.add-points-client-info {
  min-width: 0;
}

.add-points-client-name {
  font-weight: 700;
  color: var(--text-primary);
  font-size: 16px;
  letter-spacing: -0.24px;
  overflow-wrap: break-word;
  word-break: break-word;
}

.add-points-client-current {
  font-size: 13px;
  color: var(--text-secondary);
}

.add-points-sign-toggle {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--spacing-xs);
  background: var(--bg-tertiary);
  border-radius: var(--border-radius-medium);
  padding: var(--spacing-xs);
  margin-bottom: var(--spacing-sm);
}

.sign-toggle-btn {
  background: transparent;
  border: none;
  color: var(--text-secondary);
  padding: var(--spacing-sm);
  border-radius: var(--border-radius-large);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  transition: var(--transition-quick);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--spacing-xs);
}

.sign-toggle-btn.active[data-sign="1"] {
  background: var(--success-color);
  color: white;
  box-shadow: var(--shadow-sm);
}

.sign-toggle-btn.active[data-sign="-1"] {
  background: var(--danger-color);
  color: white;
  box-shadow: var(--shadow-sm);
}

.add-points-display {
  text-align: center;
  font-size: 36px;
  font-weight: 700;
  color: var(--primary-color);
  letter-spacing: -1px;
  padding: var(--spacing-sm) 0;
  margin-bottom: var(--spacing-sm);
  transition: color 0.15s ease;
}

.add-points-display.negative {
  color: var(--danger-color);
}

.add-points-display::after {
  content: " pts";
  font-size: 20px;
  font-weight: 600;
  color: var(--text-secondary);
}

.add-points-presets {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--spacing-sm);
  margin-bottom: var(--spacing-sm);
}

.add-points-preset-btn {
  background: var(--bg-secondary);
  border: 2px solid var(--primary-color);
  color: var(--primary-color);
  border-radius: var(--border-radius-medium);
  padding: var(--spacing-sm);
  font-size: 15px;
  font-weight: 700;
  cursor: pointer;
  transition: var(--transition-quick);
}

.add-points-preset-btn:hover {
  background: var(--primary-color);
  color: white;
}

.add-points-preset-btn.active {
  background: var(--primary-color);
  color: white;
  box-shadow: var(--shadow-sm);
}

.add-points-keypad {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--spacing-sm);
}

.keypad-btn {
  background: var(--bg-tertiary);
  border: 1px solid var(--separator-color);
  color: var(--text-primary);
  border-radius: var(--border-radius-medium);
  font-size: 18px;
  font-weight: 600;
  min-height: 46px;
  cursor: pointer;
  transition: var(--transition-quick);
  display: flex;
  align-items: center;
  justify-content: center;
}

.keypad-btn:hover {
  background: var(--separator-color);
}

.keypad-btn:active {
  transform: scale(0.96);
}

.keypad-btn-clear {
  color: var(--danger-color);
}

.keypad-btn-back {
  color: var(--text-secondary);
}

.points-presets-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-sm);
  margin-bottom: var(--spacing-sm);
}

.points-presets-chips:empty::before {
  content: "Aucun raccourci défini pour l'instant.";
  font-size: 13px;
  color: var(--text-tertiary);
  font-style: italic;
}

.preset-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-xs);
  background: var(--bg-tertiary);
  border: 1px solid var(--separator-color);
  color: var(--text-primary);
  border-radius: 999px;
  padding: var(--spacing-xs) var(--spacing-xs) var(--spacing-xs) var(--spacing-md);
  font-size: 14px;
  font-weight: 600;
}

.preset-chip-remove {
  background: none;
  border: none;
  cursor: pointer;
  color: var(--text-tertiary);
  width: 22px;
  height: 22px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 15px;
  line-height: 1;
  transition: var(--transition-quick);
}

.preset-chip-remove:hover {
  background: var(--danger-color);
  color: white;
}

.points-presets-add {
  display: flex;
  gap: var(--spacing-sm);
}

.points-presets-add input {
  flex: 1;
  min-width: 0;
}

.points-presets-add button {
  flex-shrink: 0;
  width: auto;
  min-height: auto;
  padding: var(--spacing-sm) var(--spacing-lg);
}

/* Toggle "Programmer l'envoi" (campagnes) */
/* Spécificité (.form-group + label) plus forte que .schedule-toggle-row seule :
   sans ce préfixe, la règle générique ".form-group label { display: block }" gagnait
   et cassait le flex ici — le switch retombait sous le texte au lieu de rester aligné
   à droite avec de l'espace entre les deux (voir le bug remonté par l'utilisateur). */
.form-group .schedule-toggle-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-lg);
  cursor: pointer;
  font-weight: normal;
  margin-bottom: 0;
}

.schedule-toggle-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.schedule-toggle-title {
  font-weight: 600;
  color: var(--text-primary);
  font-size: 15px;
  letter-spacing: -0.24px;
}

.schedule-toggle-hint {
  color: var(--text-secondary);
  font-size: 13px;
  line-height: 1.3;
}

.toggle-switch {
  position: relative;
  flex-shrink: 0;
  display: inline-block;
  width: 50px;
  height: 30px;
}

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

.toggle-switch-slider {
  position: absolute;
  inset: 0;
  background: var(--separator-color);
  border-radius: 999px;
  transition: var(--transition-quick);
}

.toggle-switch-slider::before {
  content: "";
  position: absolute;
  left: 3px;
  top: 3px;
  width: 24px;
  height: 24px;
  background: white;
  border-radius: 50%;
  box-shadow: var(--shadow-xs);
  transition: var(--transition-quick);
}

.toggle-switch input:checked + .toggle-switch-slider {
  background: var(--success-color);
}

.toggle-switch input:checked + .toggle-switch-slider::before {
  transform: translateX(20px);
}

.toggle-switch input:focus-visible + .toggle-switch-slider {
  outline: 2px solid var(--primary-color);
  outline-offset: 2px;
}

.schedule-options {
  margin-top: var(--spacing-md);
  padding: var(--spacing-md);
  background: var(--bg-tertiary);
  border-radius: var(--border-radius-medium);
}

.schedule-options label {
  display: block;
  font-size: 13px;
  color: var(--text-secondary);
  margin-bottom: var(--spacing-xs);
}

/* Formulaire de compte */
.account-form,
.settings-form {
  background: var(--bg-secondary);
  padding: var(--spacing-2xl);
  border-radius: var(--border-radius-large);
  box-shadow: var(--shadow-sm);
  border: 1px solid var(--separator-color);
  width: 100%;
}

.account-danger-zone {
  margin-top: var(--spacing-xl);
  padding: var(--spacing-2xl);
  border-radius: var(--border-radius-large);
  border: 1px solid var(--danger-color);
  background: rgba(255, 59, 48, 0.05);
  width: 100%;
}

.account-danger-zone h3 {
  color: var(--danger-color);
  margin: 0 0 var(--spacing-sm) 0;
  font-size: 17px;
}

.account-danger-zone p {
  color: var(--text-secondary);
  font-size: 14px;
  margin: 0 0 var(--spacing-lg) 0;
  line-height: 1.4;
}

/* Boutons bascule consentement (Mail/SMS) — remplacent d'anciennes checkboxes natives,
   dont l'apparence (carrée sur ordinateur, ronde sur iPad/Safari) dépend du navigateur
   et n'était pas cohérente d'une plateforme à l'autre. Un vrai bouton stylé rend
   exactement pareil partout. Vert = accepte (par défaut), rouge = refuse (cliqué). */
.consent-toggle-hint {
  display: block;
  color: var(--text-tertiary, #8e8e93);
  font-size: 13px;
  margin: -0.25rem 0 0.5rem;
}

.consent-toggle-group {
  display: flex;
  gap: var(--spacing-md);
}

.consent-toggle-btn {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--spacing-xs);
  border: none;
  border-radius: var(--border-radius-large);
  padding: var(--spacing-md) var(--spacing-lg);
  min-height: 50px;
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.24px;
  color: white;
  cursor: pointer;
  transition: var(--transition-quick);
  box-shadow: var(--shadow-sm);
  /* Deux clics rapprochés pour rebasculer vert/rouge sont interprétés par le navigateur
     mobile comme un double-tap "zoomer sur cet élément" — manipulation désactive ce
     zoom sur double-tap tout en gardant le scroll/pincement normal ailleurs sur la page. */
  touch-action: manipulation;
}

.consent-toggle-btn.accepted {
  background: linear-gradient(135deg, var(--success-color) 0%, #28a745 100%);
}

.consent-toggle-btn.refused {
  background: linear-gradient(135deg, var(--danger-color) 0%, #ff6b5a 100%);
}

.consent-toggle-btn:hover {
  transform: translateY(-1px);
  box-shadow: var(--shadow-md);
}

.consent-toggle-btn:active {
  transform: scale(0.98);
}

/* Modales iOS Premium */
.modal {
  display: none;
  position: fixed;
  z-index: 2000;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.4);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  animation: fadeIn 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

.modal.active {
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Modale ouverte par-dessus une autre modale déjà ouverte : sans ça, deux .modal
   actives au même z-index se départagent par ordre DOM, pas par ordre d'ouverture —
   celle déclarée plus tôt dans le HTML se retrouvait cachée derrière l'autre alors
   qu'elle venait de s'ouvrir. Pas de consommateur actuellement (l'insertion de lien
   qui l'utilisait a été retirée le 20/08/2026), gardée pour la prochaine modale
   imbriquée. */
.modal-nested {
  z-index: 2100;
}

.modal-content {
  background: var(--bg-secondary);
  padding: 0;
  border-radius: var(--border-radius-xl);
  box-shadow: var(--shadow-xl);
  border: 1px solid var(--separator-color);
  width: 90%;
  max-width: 500px;
  max-height: 90vh;
  overflow: hidden;
  animation: slideIn 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
  position: relative;
}

/* Largeur unifiée sur les modales "de contenu" (fiche/formulaire dense, pas une
   simple confirmation) — 620px, alignée sur #clientDetailsModal juste plus bas
   (référence explicite de l'utilisateur le 26/08/2026 : "celle des clients est
   bien"). Les petites modales de confirmation (suppression, usage d'une
   récompense...) restent volontairement sur le plafond par défaut de
   .modal-content (500px) plus haut, pas concernées par cette demande — les
   élargir à 620px laisserait un vide autour d'un texte de 2 lignes + 2 boutons. */
#groupDetailsModal .modal-content {
  max-width: 620px;
}

.modal-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: var(--spacing-xl);
  background: var(--bg-tertiary);
  border-bottom: 1px solid var(--separator-color);
}

.modal-header h3 {
  color: var(--text-primary);
  margin: 0;
  font-size: 20px;
  font-weight: 600;
  letter-spacing: -0.24px;
}

.close {
  /* Le caractère "×" du HTML (&times;) n'est jamais parfaitement centré dans sa boîte :
     ça dépend des métriques de la police (espace au-dessus/en dessous du glyphe non
     symétrique), même avec un centrage flex par ailleurs correct. Le glyphe est masqué
     (font-size: 0) et remplacé par une vraie croix dessinée en CSS (::before/::after),
     centrée par position absolue — garantie pixel-perfect quelle que soit la police. */
  font-size: 0;
  cursor: pointer;
  color: var(--text-tertiary);
  transition: var(--transition-quick);
  /* 44x44 : zone tactile minimale recommandée (Apple HIG) — la croix dessinée
     (::before/::after, 14px) reste visuellement identique, seule la zone
     cliquable grandit. Remonté de 32px le 28/08/2026 (retour iPad 9" : boutons
     qui "cliquent mal", faut appuyer plusieurs fois — 32px est sous le minimum
     tactile recommandé, le doigt rate régulièrement la cible). */
  width: 44px;
  height: 44px;
  border-radius: var(--border-radius-small);
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.05);
  position: relative;
}

.close::before,
.close::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 14px;
  height: 2px;
  background: currentColor;
  border-radius: 1px;
}

.close::before {
  transform: translate(-50%, -50%) rotate(45deg);
}

.close::after {
  transform: translate(-50%, -50%) rotate(-45deg);
}

.close:hover {
  color: var(--danger-color);
  background: rgba(255, 59, 48, 0.1);
  transform: scale(1.1);
}

/* Repère visuel au clavier — ajouté le 28/08/2026 avec setupModalAccessibility()
   (app.js), qui rend .close focusable/actionnable au clavier. */
.close:focus-visible {
  outline: 2px solid var(--primary-color);
  outline-offset: 2px;
}

.modal form {
  padding: var(--spacing-xl);
  max-height: calc(90vh - 140px);
  overflow-y: auto;
}

.modal-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-md);
  justify-content: flex-end;
  margin-top: var(--spacing-xl);
  padding-top: var(--spacing-lg);
  border-top: 1px solid var(--separator-color);
}

.modal-actions .btn-primary,
.modal-actions .btn-secondary {
  width: auto;
  min-width: 120px;
}

/* Modale détails groupe : toujours en colonne, pleine largeur, quel que soit
   l'écran — plus simple et plus fiable qu'un rendu en ligne qui dépend d'un seuil
   de largeur (essayé avant, cassait dès que 3 boutons ne tenaient pas exactement
   sur une ligne : le dernier se retrouvait seul, mal aligné). Modifier (action
   principale) en premier, Fermer ensuite, Supprimer en dernier — pas la première
   chose qu'on tape par réflexe. Règles scoped par id (spécificité plus haute que
   les règles génériques .modal-actions ci-dessus) pour garantir ce rendu ici, quoi
   qu'il arrive par ailleurs. */
#groupDetailsModal .modal-actions {
  flex-direction: column;
}

#groupDetailsModal .modal-actions #editGroupBtn {
  order: 1;
}

#groupDetailsModal .modal-actions #cancelGroupDetails {
  order: 2;
}

#groupDetailsModal .modal-actions #deleteGroupBtn {
  order: 3;
}

#groupDetailsModal .modal-actions button {
  width: 100%;
  min-width: 0;
  margin: 0;
}

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

@keyframes slideIn {
  from {
    opacity: 0;
    transform: translateY(-50px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* Responsive */
@media (max-width: 1024px) {
  /* Pas de règle .stats-grid ici : la grille de base (auto-fit, minmax(280px, 1fr))
     plus bas dans ce fichier reste seule responsable jusqu'au palier 768px (où elle
     passe explicitement à 1 colonne). Il y avait ici un "repeat(2, 1fr)" figé à
     exactement 2 colonnes qui, contrairement à auto-fit, ne respecte aucune largeur
     minimale : sur les résolutions intermédiaires (~768-1024px) les cartes du
     dashboard rétrécissaient sous leur largeur minimum voulue (280px, jusqu'à ~240px)
     au lieu de simplement passer à moins de colonnes par ligne. Repéré après l'ajout
     de la carte "Groupes" (5 cartes au lieu de 4), mais le bug existait déjà avant —
     il devenait juste plus visible avec une carte de plus. */

  .rewards-grid {
    grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  }
}

/* Modal Détails Client iOS Premium */
.client-details-content {
  padding: 0;
  max-height: 85vh;
  overflow-y: auto;
  overflow-x: hidden;
  width: 100%;
  /* Pas de max-width ici : la largeur réelle de la modale est déjà plafonnée par
     #clientDetailsModal .modal-content ci-dessous — un second plafond plus étroit
     ici laissait une bande blanche vide à droite quand celui du parent a été
     agrandi. */
  box-sizing: border-box;
  /* Scrollbar masquée : le scroll (molette/tactile) reste actif, juste sans barre
     visible — plus propre que même une version fine. */
  scrollbar-width: none;
  /* Force Safari/iPadOS à recomposer ce conteneur sur son propre calque graphique —
     filet de sécurité supplémentaire contre les résidus de repaint après un scroll
     aller-retour (voir aussi le retrait des backdrop-filter juste en dessous). */
  -webkit-transform: translateZ(0);
  transform: translateZ(0);
}

.client-details-content::-webkit-scrollbar {
  display: none;
}

#clientDetailsModal .modal-content {
  width: 90%;
  max-width: 620px;
  padding: 0;
  overflow: hidden;
  box-sizing: border-box;
}

/* Modale concours : même plafond que #clientDetailsModal juste au-dessus (620px
   au lieu des 500px par défaut) — demande explicite du 24/08/2026, le formulaire
   (thèmes, dates, lots) est aussi dense qu'une fiche client. */
#contestModal .modal-content {
  max-width: 620px;
}

/* Modale QR concours : largeur unifiée 620px (voir style inline dans index.html,
   même plafond que #clientDetailsModal — retour utilisateur du 26/08/2026 :
   "agrandi la carte... celle des clients est bien, même largeur sur toutes les
   cartes du site"). .modal-content a overflow:hidden + max-height:90vh par
   défaut (voir règle générique plus haut) : tout ce qui dépasse disparaît
   silencieusement, ça ne scrolle PAS tout seul — d'où le scroll de secours sur
   `.modal-body` ci-dessous, même si à 620px de large la modale a largement la
   place de tenir sans lui.
   Grille 2x2 (pas 4 boutons sur une seule ligne, essayé puis rejeté le
   26/08/2026 : "Créer une affiche" retombait à la ligne, moche) — 2 colonnes à
   ce gabarit donnent ~270px par bouton, largement assez pour que chaque
   libellé tienne sur une seule ligne (white-space:nowrap en filet de
   sécurité). Espacements généreux (spacing-xl) plutôt que resserrés : une
   carte large avec un contenu trop compact avait l'air disproportionnée
   ("tu as tout shrink"). */
#contestQrModal .modal-body {
  padding: var(--spacing-xl);
  max-height: calc(90vh - 64px);
  overflow-y: auto;
}

#contestQrModal .modal-actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--spacing-md);
  margin-top: var(--spacing-lg);
  padding-top: 0;
  border-top: none;
}

#contestQrModal .modal-actions .btn-primary,
#contestQrModal .modal-actions .btn-secondary {
  white-space: nowrap;
}

@media (max-width: 420px) {
  #contestQrModal .modal-actions {
    grid-template-columns: 1fr;
  }
}

/* Modale Historique client (bouton "Historique" de la fiche) — volontairement plus
   grande que les autres modales : c'est une vue de consultation (on parcourt une
   liste), pas un petit formulaire, autant lui laisser de la place. */
#clientHistoryModal .modal-content {
  width: 92%;
  max-width: 760px;
}

#clientHistoryModal .modal-header h3 {
  font-size: 22px;
}

.client-history-body {
  padding: 2rem;
  max-height: 82vh;
  overflow-y: auto;
}

.client-history-tabs {
  display: flex;
  gap: 0.5rem;
  margin-bottom: 1.5rem;
  border-bottom: 1px solid var(--separator-color);
}

.client-history-tab {
  flex: 1;
  background: none;
  border: none;
  border-bottom: 3px solid transparent;
  padding: 0.9rem 0.5rem;
  color: var(--text-secondary);
  font-weight: 600;
  font-size: 1.05rem;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  transition: var(--transition-quick);
}

.client-history-tab i {
  font-size: 1.1rem;
}

.client-history-tab.active {
  color: var(--primary-color);
  border-bottom-color: var(--primary-color);
}

.client-history-panel {
  display: none;
}

.client-history-panel.active {
  display: block;
}

.client-history-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.client-history-list > li {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  padding: 1rem 1.25rem;
  background: var(--bg-tertiary);
  border-radius: var(--border-radius-large);
  font-size: 1.05rem;
  color: var(--text-secondary);
}

/* Ligne "date — nom de la récompense" (potentiellement longue) à gauche : sans
   min-width:0/overflow-wrap, un flex item garde son min-width:auto (largeur du
   contenu) et peut pousser la modale en scroll horizontal plutôt que de wrapper.
   Le montant en points à droite doit lui rester sur une ligne (nowrap) et ne
   jamais se faire écraser par le libellé (flex-shrink:0) - même principe que
   .stats-bar-label/.stats-bar-value déjà utilisé pour le Top 10. */
.client-history-list > li > span:first-child {
  min-width: 0;
  overflow-wrap: anywhere;
}

.client-history-list > li > span:last-child {
  white-space: nowrap;
  flex-shrink: 0;
}

.client-history-list .stats-kpi-value {
  font-size: 1.1rem;
}

.client-history-points-credit {
  color: var(--success-color);
}

.client-history-points-debit {
  color: var(--danger-color);
}

.client-history-messages > li {
  flex-direction: column;
  align-items: stretch;
  gap: 0.6rem;
}

.client-history-message-top {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 0.9rem;
  color: var(--text-tertiary, #8e8e93);
}

.client-history-message-status {
  font-size: 0.78rem;
  font-weight: 700;
  padding: 3px 10px;
  border-radius: 999px;
}

.client-history-message-status.success {
  background: rgba(52, 199, 89, 0.12);
  color: var(--success-color);
}

.client-history-message-status.failed {
  background: rgba(255, 59, 48, 0.12);
  color: var(--danger-color);
}

.client-history-message-content {
  color: var(--text-primary);
  font-size: 1rem;
  line-height: 1.5;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

@media (max-width: 640px) {
  .client-history-tab span {
    display: none;
  }

  .client-history-body {
    padding: 1.25rem;
  }
}

.client-info-section {
  background: linear-gradient(
    135deg,
    var(--primary-color) 0%,
    var(--primary-light) 50%,
    var(--primary-dark) 100%
  );
  color: white;
  padding: var(--spacing-xl);
  margin: 0;
  border-bottom: none;
  border-radius: var(--border-radius-xl) var(--border-radius-xl) 0 0;
  display: flex;
  align-items: flex-start;
  gap: var(--spacing-lg);
  position: relative;
  background-attachment: fixed;
  min-height: auto;
}

.client-info-section::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background:
    radial-gradient(
      circle at 20% 80%,
      rgba(255, 255, 255, 0.1) 0%,
      transparent 50%
    ),
    radial-gradient(
      circle at 80% 20%,
      rgba(255, 255, 255, 0.1) 0%,
      transparent 50%
    );
  pointer-events: none;
}

.client-info-section .close {
  position: absolute;
  top: var(--spacing-lg);
  right: var(--spacing-lg);
  color: rgba(255, 255, 255, 0.9);
  /* font-size: 0 (pas 20px) : ce bouton a sa propre règle ici, plus spécifique que
     .close seule, qui écrasait le font-size:0 voulu pour masquer le glyphe "×" — d'où
     l'ancienne croix qui restait visible par-dessus celle dessinée en CSS. */
  font-size: 0;
  font-weight: 600;
  cursor: pointer;
  /* Pas de backdrop-filter ici : sur Safari/iPadOS, un effet de flou dans un conteneur
     qui scrolle laisse parfois un résidu blanc à l'écran après un scroll aller-retour
     (bug de repaint connu). Fond légèrement plus opaque à la place, sans le flou. */
  background: rgba(255, 255, 255, 0.22);
  width: 40px;
  height: 40px;
  border-radius: var(--border-radius-medium);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: var(--transition-smooth);
  border: 1px solid rgba(255, 255, 255, 0.2);
  z-index: 10;
}

.client-info-section .close:hover {
  background: rgba(255, 255, 255, 0.25);
  color: white;
  transform: scale(1.05);
  box-shadow: var(--shadow-sm);
}

.client-avatar {
  width: 80px;
  height: 80px;
  /* Pas de backdrop-filter — voir .client-info-section .close juste au-dessus. */
  background: rgba(255, 255, 255, 0.22);
  border: 3px solid rgba(255, 255, 255, 0.3);
  border-radius: var(--border-radius-xl);
  display: flex;
  align-items: center;
  justify-content: center;
  color: white;
  font-size: 28px;
  font-weight: 600;
  flex-shrink: 0;
  box-shadow: var(--shadow-lg);
  position: relative;
  z-index: 2;
}

.client-main-info {
  flex: 1;
  position: relative;
  z-index: 2;
}

.client-main-info h3 {
  color: white;
  margin-bottom: var(--spacing-md);
  font-size: 24px;
  font-weight: 700;
  letter-spacing: -0.5px;
  text-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
  line-height: 1.2;
}

.client-contact {
  margin-bottom: var(--spacing-lg);
}

.contact-item {
  display: flex;
  align-items: center;
  margin-bottom: var(--spacing-sm);
  color: rgba(255, 255, 255, 0.95);
  font-size: 15px;
  font-weight: 500;
  letter-spacing: -0.24px;
}

.contact-item i {
  width: 32px;
  height: 32px;
  margin-right: var(--spacing-sm);
  color: rgba(255, 255, 255, 0.9);
  /* Pas de backdrop-filter — voir .client-info-section .close plus haut. */
  background: rgba(255, 255, 255, 0.22);
  padding: var(--spacing-xs);
  border-radius: var(--border-radius-small);
  font-size: 14px;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: var(--shadow-xs);
}

.consent-yes {
  color: #b7f7c8;
}

.consent-no {
  color: #ffd0c9;
  font-weight: 600;
}

.client-points-display {
  /* Pas de backdrop-filter — voir .client-info-section .close plus haut. */
  background: rgba(255, 255, 255, 0.22);
  padding: var(--spacing-md) var(--spacing-lg);
  border-radius: var(--border-radius-large);
  display: flex;
  align-items: center;
  justify-content: space-between;
  border: 1px solid rgba(255, 255, 255, 0.2);
  box-shadow: var(--shadow-sm);
  position: relative;
  z-index: 2;
}

.client-points-display-clickable {
  cursor: pointer;
  transition: var(--transition-quick);
}

.client-points-display-clickable:hover,
.client-points-display-clickable:active {
  background: rgba(255, 255, 255, 0.32);
  transform: translateY(-1px);
}

.points-label {
  color: rgba(255, 255, 255, 0.95);
  font-weight: 600;
  font-size: 15px;
  letter-spacing: -0.24px;
}

.points-value {
  background: rgba(255, 255, 255, 0.95);
  color: var(--primary-color);
  padding: var(--spacing-xs) var(--spacing-md);
  border-radius: var(--border-radius-xl);
  font-weight: 700;
  font-size: 18px;
  letter-spacing: -0.24px;
  box-shadow: var(--shadow-sm);
}

.rewards-section {
  margin: 0;
  padding: var(--spacing-xl);
  background: var(--bg-primary);
  overflow: hidden;
  box-sizing: border-box;
}

.rewards-section h4 {
  color: var(--text-primary);
  margin-bottom: var(--spacing-lg);
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
  font-size: 20px;
  font-weight: 700;
  letter-spacing: -0.5px;
}

.rewards-section h4 i {
  color: var(--primary-color);
  background: rgba(0, 122, 255, 0.1);
  padding: var(--spacing-sm);
  border-radius: var(--border-radius-medium);
  font-size: 16px;
  box-shadow: var(--shadow-xs);
}

.available-rewards-list {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-md);
  /* Pas de max-height/overflow ici (retiré le 24/08/2026, retour "pas de slider dans
     une carte slidable") : toutes les récompenses font partie du flux normal de la
     carte, c'est .client-details-content (le conteneur englobant) qui scrolle en un
     seul bloc — plus de case scrollable imbriquée dans une autre case scrollable. */
  margin-right: 0;
  padding-right: 0;
}

.available-reward-item {
  background: var(--bg-secondary);
  border: 1px solid var(--separator-color);
  border-radius: var(--border-radius-large);
  padding: var(--spacing-lg);
  display: flex;
  justify-content: space-between;
  align-items: center;
  transition: border-color 0.15s ease, background-color 0.15s ease;
  position: relative;
  min-height: 80px;
}

.available-reward-item:hover {
  border-color: var(--primary-color);
}

.available-reward-item.available {
  background: rgba(52, 199, 89, 0.06);
  border-color: rgba(52, 199, 89, 0.35);
  cursor: pointer;
}

.available-reward-item.available:hover {
  background: rgba(52, 199, 89, 0.1);
  border-color: var(--success-color);
}

.available-reward-item.unavailable {
  border-color: var(--separator-color);
  background: var(--bg-tertiary);
  opacity: 0.6;
  cursor: not-allowed;
}

/* Icône cadeau/cadenas — remplace l'ancien badge "DISPONIBLE"/"Points insuffisants" en
   texte, qui faisait doublon avec la teinte verte/grise déjà portée par la carte. */
.reward-icon {
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 15px;
  margin-right: var(--spacing-md);
}

.reward-icon.available {
  background: rgba(52, 199, 89, 0.15);
  color: var(--success-color);
}

.reward-icon.unavailable {
  background: rgba(142, 142, 147, 0.15);
  color: var(--text-tertiary);
}

.reward-info {
  flex: 1;
  min-width: 0;
  padding-right: var(--spacing-lg);
  text-align: left;
}

.reward-name {
  font-weight: 700;
  color: var(--text-primary);
  margin-bottom: var(--spacing-xs);
  font-size: 16px;
  line-height: 1.3;
  letter-spacing: -0.24px;
  word-wrap: break-word;
  overflow-wrap: break-word;
}

/* Scopée à .available-reward-item (liste de récompenses de la fiche client) : un
   `.reward-description` non scopé ici entrait en collision avec la règle du même nom
   utilisée par la page Récompenses (plus haut dans ce fichier) et, étant définie après,
   écrasait silencieusement son margin-bottom — d'où le texte collé au bouton Modifier
   là-bas alors que rien n'avait changé sur cette page-ci. */
.available-reward-item .reward-description {
  font-size: 14px;
  color: var(--text-secondary);
  line-height: 1.4;
  letter-spacing: -0.24px;
  word-wrap: break-word;
  overflow-wrap: break-word;
  margin: 0;
}

/* Remplace l'ancien texte générique "Points insuffisants" par une info concrète et
   utile : combien de points il manque exactement. */
.reward-missing {
  font-size: 13px;
  color: var(--text-tertiary);
  font-weight: 600;
  margin-top: 2px;
}

.reward-points-badge {
  background: linear-gradient(
    135deg,
    var(--primary-color) 0%,
    var(--primary-light) 100%
  );
  color: white;
  padding: var(--spacing-xs) var(--spacing-md);
  border-radius: var(--border-radius-xl);
  font-weight: 700;
  font-size: 14px;
  letter-spacing: -0.24px;
  box-shadow: var(--shadow-sm);
  white-space: nowrap;
  text-align: center;
  min-width: 70px;
  flex-shrink: 0;
  border: 1px solid rgba(255, 255, 255, 0.2);
}

.reward-points-badge.unavailable {
  background: linear-gradient(
    135deg,
    var(--text-tertiary) 0%,
    var(--text-quaternary) 100%
  );
  box-shadow: var(--shadow-xs);
}

.no-rewards-message {
  text-align: center;
  color: #6c757d;
  font-style: italic;
  padding: 3rem 2rem;
  background: white;
  border: 2px dashed #dee2e6;
  border-radius: 12px;
  margin: 1rem 0;
}

.no-rewards-message::before {
  content: "🎁";
  display: block;
  font-size: 3rem;
  margin-bottom: 1rem;
}

/* Actions de la modal détails client — 3 boutons (Fermer/Historique/Modifier) :
   flex-wrap toutes seules pouvait laisser le 3e bouton isolé sur sa propre ligne dès
   que la largeur totale ne tenait pas pile (voir Obsidian > Methodes > "Disposition
   des boutons de modale"). Grid à colonnes égales : toujours alignés proprement sur
   une seule ligne quand la largeur le permet, jamais de bouton qui déborde seul. */
#clientDetailsModal .modal-actions {
  background: white;
  border-top: 1px solid #dee2e6;
  padding: 1.5rem 2rem;
  margin: 0;
  border-radius: 0 0 10px 10px;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1rem;
  /* Barre fixée en bas de la carte, toujours visible pendant qu'on fait défiler le
     reste (infos client + toutes les récompenses) — retour du 24/08/2026 : "je veux
     la barre fixe en bas". position:sticky (pas fixed) car elle reste ancrée au bas
     de .client-details-content, le SEUL conteneur qui scrolle désormais (voir
     .available-rewards-list, plus de case scrollable imbriquée) — sticky suit ce
     scroll-là précisément, fixed serait relatif à tout l'écran et se déconnecterait
     de la modale si elle n'occupe pas toute la hauteur de l'écran. z-index au-dessus
     du contenu qui défile en dessous. */
  position: sticky;
  bottom: 0;
  z-index: 10;
}

#clientDetailsModal .modal-actions .btn-primary,
#clientDetailsModal .modal-actions .btn-secondary {
  width: 100%;
  min-width: 0;
}

@media (max-width: 560px) {
  #clientDetailsModal .modal-actions {
    grid-template-columns: 1fr;
  }
}

#editClientFromDetails {
  /* Une simple espace entre l'icône et le texte dans le HTML ne donne qu'un espacement
     minime — un vrai gap flex laisse bien plus d'air. */
  gap: var(--spacing-sm);
}

/* Modal Confirmation Utilisation Récompense */
.confirmation-modal {
  max-width: 480px;
  text-align: center;
  overflow: visible;
  padding: 0;
}

.confirmation-header {
  background: linear-gradient(
    135deg,
    var(--primary-color),
    var(--secondary-color)
  );
  color: white;
  padding: 1.75rem 1.5rem 1.5rem 1.5rem;
  margin: 0 0 1rem 0;
  border-radius: 10px 10px 0 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.75rem;
}

.confirmation-header i {
  font-size: 2.5rem;
  opacity: 0.9;
}

.confirmation-header-danger {
  background: linear-gradient(135deg, var(--danger-color), #c0392b);
}

.confirmation-header h3 {
  margin: 0;
  font-size: 1.3rem;
  font-weight: 600;
}

.confirmation-body {
  padding: 1rem 2rem 0 2rem;
}

.confirmation-body p {
  font-size: 1rem;
  color: var(--secondary-color);
  margin-bottom: 1rem;
  margin-top: 0;
}

.reward-confirmation-details {
  background: #f8f9fa;
  border: 2px solid var(--primary-color);
  border-radius: 10px;
  padding: 1.25rem;
  margin: 1rem 0;
}

.reward-name-confirm {
  font-size: 1.1rem;
  font-weight: bold;
  color: var(--secondary-color);
  margin-bottom: 0.5rem;
}

.reward-points-confirm {
  background: var(--primary-color);
  color: white;
  padding: 0.4rem 0.9rem;
  border-radius: 18px;
  font-weight: bold;
  font-size: 0.9rem;
  display: inline-block;
}

.points-remaining {
  background: rgba(39, 174, 96, 0.1);
  color: var(--success-color);
  font-weight: 600;
  font-size: 0.9rem;
  padding: 0.8rem;
  border-radius: 8px;
  margin: 1rem 0 0 0 !important;
  border: 1px solid rgba(39, 174, 96, 0.2);
}

/* Centrer les boutons de la modal de confirmation */
#useRewardConfirmModal .modal-actions {
  display: flex;
  justify-content: center;
  gap: 1rem;
  padding: 1.5rem 2rem;
  margin: 0;
}

#useRewardConfirmModal .modal-actions .btn-primary,
#useRewardConfirmModal .modal-actions .btn-secondary {
  min-width: 120px;
}

#deleteAccountModal .modal-actions {
  display: flex;
  justify-content: center;
  gap: 1rem;
  padding: 1.5rem 2rem;
  margin: 0;
}

#deleteAccountModal .modal-actions .btn-secondary,
#deleteAccountModal .modal-actions .btn-danger {
  min-width: 120px;
}

/* Styles pour les campagnes avancées */
.campaign-modal {
  max-width: 800px;
}

.campaign-type-selector {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--spacing-lg);
  margin-top: var(--spacing-sm);
}

.campaign-type-option {
  cursor: pointer;
}

.campaign-type-option input[type="checkbox"],
.campaign-type-option input[type="radio"] {
  display: none;
}

.campaign-type-card {
  background: var(--bg-secondary);
  border: 2px solid var(--separator-color);
  border-radius: var(--border-radius-large);
  padding: var(--spacing-xl);
  text-align: center;
  transition: var(--transition-smooth);
  min-height: 120px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--spacing-sm);
}

.campaign-type-card i {
  font-size: 2rem;
  color: var(--text-tertiary);
  transition: var(--transition-smooth);
}

.campaign-type-card span {
  font-weight: 600;
  color: var(--text-primary);
  font-size: 1.1rem;
  letter-spacing: -0.24px;
}

.campaign-type-desc {
  font-size: 0.85rem;
  color: var(--text-secondary);
  margin-top: var(--spacing-xs);
}

.campaign-type-option input[type="checkbox"]:checked + .campaign-type-card,
.campaign-type-option input[type="radio"]:checked + .campaign-type-card {
  border-color: var(--primary-color);
  background: rgba(0, 122, 255, 0.05);
  box-shadow: 0 4px 15px rgba(0, 122, 255, 0.15);
}

.campaign-type-option input[type="checkbox"]:checked + .campaign-type-card i,
.campaign-type-option input[type="radio"]:checked + .campaign-type-card i {
  color: var(--primary-color);
}

.campaign-section {
  background: var(--bg-tertiary);
  border: 1px solid var(--separator-color);
  border-radius: var(--border-radius-large);
  padding: var(--spacing-xl);
  margin: var(--spacing-lg) 0;
}

.campaign-section h4 {
  color: var(--text-primary);
  font-size: 1.1rem;
  font-weight: 600;
  margin-bottom: var(--spacing-lg);
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
}

.campaign-section h4 i {
  color: var(--primary-color);
}

.message-counter {
  display: flex;
  justify-content: space-between;
  align-items: center;
  /* Plus d'écart avec le textarea juste au-dessus (spacing-sm collait trop) */
  margin-top: var(--spacing-md, 1rem);
  font-size: 0.85rem;
  color: var(--text-secondary);
}

/* Regroupe "12 caractères" (et "X SMS") en un seul bloc à gauche : avant, ce n'étaient
   que des noeuds de texte + spans juxtaposés dans un conteneur flex, ce que le
   navigateur traite comme AUTANT d'éléments flex séparés — avec justify-content:
   space-between, ça écartait "12" et "caractères" au lieu de les garder collés. */
.message-counter-left {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
}

.message-counter-main {
  white-space: nowrap;
}

/* Séparateur visuel discret entre le compteur de caractères et le nombre de SMS
   (remplace l'ancien "|" brut collé au texte). */
.message-counter-sms {
  padding-left: 0.75rem;
  border-left: 1px solid var(--separator-color);
  white-space: nowrap;
}

/* Petite bulle d'info au survol/focus (icône seule visible par défaut) — remplace un
   paragraphe d'explication toujours affiché en dur, qui alourdissait visuellement la
   zone du compteur pour une info secondaire. */
.info-tooltip {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  /* Légèrement plus bas que le reste de la ligne du compteur, comme demandé, plutôt
     qu'aligné pile sur le texte. */
  align-self: flex-end;
  margin-top: 6px;
  color: var(--text-tertiary, #8e8e93);
  cursor: help;
  outline: none;
}

.info-tooltip i {
  font-size: 15px;
}

.info-tooltip:hover,
.info-tooltip:focus {
  color: var(--primary-color);
}

.info-tooltip-bubble {
  position: absolute;
  bottom: calc(100% + 8px);
  right: 0;
  width: 260px;
  max-width: min(80vw, 260px);
  background: var(--bg-secondary);
  color: var(--text-primary);
  font-size: 0.8rem;
  line-height: 1.5;
  text-align: left;
  padding: 0.65rem 0.85rem;
  border-radius: var(--border-radius-medium, 8px);
  border: 1px solid var(--separator-color);
  box-shadow: var(--shadow-md);
  opacity: 0;
  visibility: hidden;
  transform: translateY(4px);
  transition: opacity 0.15s ease, transform 0.15s ease;
  z-index: 20;
  pointer-events: none;
}

.info-tooltip:hover .info-tooltip-bubble,
.info-tooltip:focus .info-tooltip-bubble,
.info-tooltip:focus-within .info-tooltip-bubble {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}

/* Puces "Prénom"/"Nom"/"Points"/"Nom du magasin" au-dessus du message de campagne :
   insèrent la variable ({prenom}, {nom}...) directement dans le texte au clic, pour ne
   plus avoir à se souvenir/taper la syntaxe à la main. */
.message-variable-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-sm);
  margin-bottom: var(--spacing-sm);
}

.variable-chip,
.reward-variable-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: rgba(0, 122, 255, 0.08);
  color: var(--primary-color);
  border: 1px solid rgba(0, 122, 255, 0.25);
  border-radius: var(--border-radius-xl);
  padding: 6px 14px;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: var(--transition-quick);
  touch-action: manipulation;
}

.variable-chip i,
.reward-variable-chip i {
  font-size: 11px;
}

.variable-chip:hover,
.reward-variable-chip:hover {
  background: var(--primary-color);
  color: white;
  border-color: var(--primary-color);
}

.variable-chip:active,
.reward-variable-chip:active {
  transform: scale(0.96);
}

/* Bulle d'aperçu du SMS anniversaire (rendu purement local, jamais envoyé) — imite un
   message texte pour que l'aperçu se lise comme le vrai résultat plutôt que comme un
   bloc de texte brut. */
.sms-preview-bubble {
  background: var(--bg-secondary);
  border: 1px solid var(--separator-color);
  border-radius: var(--border-radius-large);
  padding: 1rem 1.25rem;
  margin: 1rem 0;
  white-space: pre-wrap;
  word-break: break-word;
  line-height: 1.5;
}

.campaign-preview {
  background: var(--bg-tertiary);
  border: 1px solid var(--separator-color);
  border-radius: var(--border-radius-large);
  padding: var(--spacing-xl);
  margin: var(--spacing-lg) 0;
}

.campaign-preview h4 {
  color: var(--text-primary);
  font-size: 1.1rem;
  font-weight: 600;
  margin-bottom: var(--spacing-md);
}

.preview-content {
  background: var(--bg-secondary);
  border: 1px solid var(--separator-color);
  border-radius: var(--border-radius-medium);
  padding: var(--spacing-lg);
  font-size: 0.9rem;
  line-height: 1.5;
  color: var(--text-secondary);
  min-height: 80px;
}

/* Objet en haut à gauche, message en dessous — deux blocs visuellement distincts au
   lieu d'un paragraphe unique à la suite. */
.preview-subject {
  text-align: left;
  font-weight: 700;
  font-size: 0.95rem;
  color: var(--text-primary);
  padding-bottom: 0.6rem;
  margin-bottom: 0.6rem;
  border-bottom: 1px solid var(--separator-color);
}

.preview-message {
  font-family: var(--font-mono, "Courier New", monospace);
  white-space: pre-wrap;
  word-break: break-word;
}

.settings-section {
  background: var(--bg-tertiary);
  border: 1px solid var(--separator-color);
  border-radius: var(--border-radius-large);
  padding: var(--spacing-xl);
  margin: var(--spacing-xl) 0;
}

.settings-section h3 {
  color: var(--text-primary);
  font-size: 1.2rem;
  font-weight: 600;
  margin-bottom: var(--spacing-md);
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
}

.settings-section h3 i {
  color: var(--primary-color);
}

.settings-description {
  color: var(--text-secondary);
  margin-bottom: var(--spacing-lg);
  line-height: 1.5;
}

.settings-description a {
  color: var(--primary-color);
  text-decoration: none;
}

.settings-description a:hover {
  text-decoration: underline;
}

.api-status {
  display: flex;
  align-items: center;
  gap: var(--spacing-md);
  margin-top: var(--spacing-lg);
}

.webhook-url-row {
  display: flex;
  gap: var(--spacing-sm);
}

.webhook-url-row input {
  flex: 1;
  min-width: 0;
  font-size: 0.85rem;
  color: var(--text-secondary);
  background: var(--bg-tertiary, rgba(0, 0, 0, 0.03));
}

.connection-status {
  font-size: 0.9rem;
  padding: var(--spacing-sm) var(--spacing-md);
  border-radius: var(--border-radius-small);
  font-weight: 500;
}

.connection-status.success {
  background: rgba(52, 199, 89, 0.1);
  color: var(--success-color);
  border: 1px solid rgba(52, 199, 89, 0.2);
}

.connection-status.error {
  background: rgba(255, 59, 48, 0.1);
  color: var(--danger-color);
  border: 1px solid rgba(255, 59, 48, 0.2);
}

.connection-status.loading {
  background: rgba(0, 122, 255, 0.1);
  color: var(--primary-color);
  border: 1px solid rgba(0, 122, 255, 0.2);
}

@media (max-width: 768px) {
  .sidebar {
    transform: translateX(-100%);
    transition: transform 0.3s ease;
    z-index: 2000;
  }

  .sidebar.mobile-open {
    transform: translateX(0);
  }

  .main-content {
    margin-left: 0;
    width: 100%;
  }

  /* Pages de connexion/inscription responsive */
  .login-container {
    padding: var(--spacing-md);
    align-items: flex-start;
    padding-top: 10vh;
  }

  .login-box {
    max-width: 95%;
    padding: var(--spacing-xl);
    margin: 0;
    max-height: 85vh;
  }

  #registerPage .login-box {
    max-width: 95%;
    padding: var(--spacing-lg) var(--spacing-xl);
    max-height: 90vh;
    overflow-y: auto;
  }

  .login-box h1 {
    font-size: 28px;
    margin-bottom: var(--spacing-md);
  }

  .login-box p {
    font-size: 16px;
    margin-bottom: var(--spacing-lg);
  }

  #registerPage .form-group {
    margin-bottom: var(--spacing-md);
  }

  .main-header {
    padding: 1rem;
    position: relative;
  }

  .hamburger-menu {
    display: block;
    position: absolute;
    left: 1rem;
    top: 50%;
    transform: translateY(-50%);
    background: none;
    border: none;
    font-size: 1.3rem;
    color: var(--secondary-color);
    cursor: pointer;
    padding: 0.5rem;
    border-radius: 4px;
    transition: var(--transition);
    z-index: 1001;
  }

  .hamburger-menu:hover {
    background-color: rgba(52, 152, 219, 0.1);
    color: var(--primary-color);
  }

  .hamburger-menu:active {
    transform: translateY(-50%) scale(0.95);
  }

  .main-header h1 {
    margin-left: 2.5rem;
    font-size: 1.4rem;
  }

  .content-section {
    padding: 1rem;
  }

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

  .section-header .btn-primary {
    width: 100%;
    max-width: 300px;
    margin: 0 auto;
  }

  .section-header .sort-controls {
    justify-content: center;
    flex-wrap: wrap;
    gap: var(--spacing-sm);
  }

  .section-header .sort-controls select {
    min-width: 180px;
    flex: 1;
    max-width: 300px;
  }

  .stats-grid {
    grid-template-columns: 1fr;
    gap: 1rem;
  }

  .stat-card {
    padding: 1rem;
  }

  .stats-charts {
    grid-template-columns: 1fr;
    gap: 1rem;
  }

  .stats-card {
    padding: 1.1rem;
  }

  /* Le plancher de 300px (voir .stats-charts .stats-card:not(.stats-card-wide) plus
     haut) sert à uniformiser des cartes côte à côte sur plusieurs colonnes — seules
     l'une en dessous de l'autre en 1 colonne mobile, il n'apporte rien et gaspille
     de l'espace vertical précieux sur petit écran. */
  .stats-charts .stats-card:not(.stats-card-wide) {
    min-height: 0;
  }

  .stats-card-wide {
    grid-column: span 1;
  }

  .stats-donut-wrap {
    width: 110px;
    height: 110px;
    min-width: 110px;
  }

  .stats-donut-center-value {
    font-size: 1.05rem;
  }

  .stats-chart-wrap {
    gap: 1rem;
  }

  .stat-icon {
    width: 50px;
    height: 50px;
    font-size: 1.2rem;
  }

  /* Le tableau clients (.table-container) est déjà remplacé par les cards
     (.mobile-table) sous 1280px — voir plus haut — donc rien à faire ici pour lui. */

  /* Le style des cards clients (.client-card...) n'est plus scopé à 768px : voir plus
     haut, juste après le bloc qui bascule vers .mobile-table, pour rester actif sur
     toute la plage où les cards sont affichées (jusqu'à 1280px, iPad inclus dans tous
     les cas de figure Safari). */

  /* Grilles responsives */
  .rewards-grid {
    grid-template-columns: 1fr;
    gap: 1rem;
  }

  .reward-card {
    padding: 1rem;
  }

  .reward-actions {
    flex-direction: column;
    gap: 0.5rem;
  }

  .reward-actions button {
    width: 100%;
  }

  /* Campagnes responsives */
  .campaign-header {
    flex-direction: column;
    align-items: stretch;
    gap: 1rem;
  }

  .campaign-actions {
    justify-content: center;
    gap: 0.75rem;
  }

  /* Les boutons portent maintenant un libellé ("Modifier"/"Supprimer", retour du
     20/08/2026 — avant, icône seule) : un carré fixe de 48px les aurait fait casser le
     texte au milieu d'un mot. flex + nowrap, comme .contest-card-actions. */
  .campaign-actions button {
    flex: 1 1 auto;
    min-height: 48px;
    padding: 0.75rem 1rem;
    font-size: 0.95rem;
    white-space: nowrap;
  }

  .campaign-stats {
    flex-direction: column;
    gap: 0.5rem;
    text-align: center;
  }

  /* Modales responsives */
  .modal-content {
    width: 95%;
    margin: 1rem;
    max-height: 85vh;
  }

  .modal-header {
    padding: 1rem;
    position: sticky;
    top: 0;
    background: white;
    z-index: 10;
    border-bottom: 1px solid var(--border-color);
  }

  .modal form {
    padding: 1rem;
    max-height: calc(85vh - 80px);
    overflow-y: auto;
  }

  .modal-actions {
    flex-direction: column;
    gap: 0.5rem;
    position: sticky;
    bottom: 0;
    background: white;
    padding-top: 1rem;
    margin-top: 1rem;
    border-top: 1px solid var(--border-color);
  }

  .modal-actions .btn-primary,
  .modal-actions .btn-secondary,
  .modal-actions .btn-danger {
    width: 100%;
    padding: 0.875rem;
    font-size: 1rem;
  }

  /* Formulaires responsifs */
  .account-form,
  .settings-form {
    padding: var(--spacing-xl);
    margin: 0;
    width: 100%;
    box-sizing: border-box;
  }

  .checkbox-group {
    flex-direction: column;
    gap: 0.5rem;
  }

  /* Tablette/mobile : case un peu plus grande pour une cible tactile confortable
     (même logique 44px que les autres boutons de l'app), et respiration verticale
     supplémentaire par rapport au champ mot de passe juste au-dessus. */
  .form-group .checkbox-label {
    gap: 12px;
    margin-top: var(--spacing-xs, 0.5rem);
  }

  .checkbox-label .checkbox-box {
    width: 24px;
    height: 24px;
  }

  /* Search bar responsive */
  .search-bar input {
    max-width: 100%;
  }

  /* User info responsive */
  .user-info {
    display: none;
  }
}

@media (max-width: 480px) {
  /* Cards déjà actives depuis 1280px (voir plus haut) — ici uniquement les ajustements
     propres aux très petits écrans. */

  /* Sur très petits écrans, garder les 3 boutons d'action des cartes clients
     sur une seule ligne : icônes seules, texte masqué (reste en tooltip via
     l'attribut title). */
  .client-actions .btn-label {
    display: none;
  }

  .main-header h1 {
    font-size: 1.2rem;
    margin-left: 2rem;
  }

  .content-section {
    padding: 0.75rem;
  }

  .stat-card {
    flex-direction: column;
    text-align: center;
    padding: 1rem 0.75rem;
  }

  .stat-icon {
    margin-right: 0;
    margin-bottom: 0.75rem;
    width: 40px;
    height: 40px;
    font-size: 1rem;
  }

  .stat-info h3 {
    font-size: 1.5rem;
  }

  .stat-info p {
    font-size: 0.8rem;
  }

  /* Tables très petits écrans */
  table th,
  table td {
    padding: 0.5rem 0.25rem;
    font-size: 0.8rem;
  }

  .badge {
    font-size: 0.7rem;
    padding: 0.2rem 0.5rem;
  }

  /* Boutons d'action plus petits */
  .reward-actions button {
    padding: 0.5rem;
    font-size: 0.8rem;
  }

  .campaign-actions button {
    min-width: 48px;
    min-height: 48px;
    padding: 0.75rem;
    font-size: 1.1rem;
  }

  /* Modal très petits écrans */
  .modal-content {
    width: 98%;
    margin: 0.5rem;
  }

  .modal-header h3 {
    font-size: 1.1rem;
  }

  .form-group label {
    font-size: 0.9rem;
  }

  .form-group input,
  .form-group textarea,
  .form-group select {
    font-size: 0.9rem;
    padding: 0.6rem;
  }
}

/* Overlay pour mobile sidebar */
@media (max-width: 768px) {
  .mobile-overlay {
    /* Plus de voile gris (l'apparition instantanée du fond créait une
           démarcation visible avec le menu qui glisse, effet pas net). Reste
           uniquement une zone invisible cliquable pour fermer le menu en
           tapant à côté — pas d'assombrissement du fond. */
    display: block;
    position: fixed;
    top: 0;
    left: 280px;
    right: 0;
    height: 100%;
    background-color: transparent;
    z-index: 1500;
    opacity: 0;
    pointer-events: none;
  }

  .mobile-overlay.active {
    pointer-events: auto;
  }
}

/* Améliorations pour les touches */
@media (max-width: 768px) {
  button,
  .btn-primary,
  .btn-secondary,
  .btn-danger {
    min-height: 44px;
    touch-action: manipulation;
  }

  .menu-item {
    min-height: 48px;
    display: flex;
    align-items: center;
  }
}

/* Scroll amélioré pour mobile */
@media (max-width: 768px) {
  .activity-list {
    max-height: 200px;
  }

  .campaigns-list {
    gap: 0.75rem;
  }

  .campaign-card {
    padding: 1rem;
  }
}

/* Responsive pour modal détails client */
@media (max-width: 768px) {
  .client-info-section {
    flex-direction: column;
    text-align: center;
    gap: 1.5rem;
    padding: 1.5rem;
  }

  .client-avatar {
    width: 70px;
    height: 70px;
    font-size: 1.8rem;
    margin: 0 auto;
  }

  .client-main-info h3 {
    font-size: 1.4rem;
  }

  .contact-item {
    justify-content: center;
    margin-bottom: 1rem;
  }

  .client-points-display {
    flex-direction: column;
    gap: 0.75rem;
    text-align: center;
    padding: 1.25rem;
  }

  .points-value {
    font-size: 1.4rem;
    padding: 0.75rem 1.25rem;
  }

  .rewards-section {
    padding: 1.5rem;
  }

  .rewards-section h4 {
    font-size: 1.2rem;
    justify-content: center;
    text-align: center;
  }


  .available-reward-item {
    flex-direction: column;
    align-items: stretch;
    gap: 1rem;
    text-align: center;
    padding: 1.5rem;
  }

  .reward-info {
    padding-right: 0;
  }

  .reward-points-required {
    align-items: center;
    flex-direction: row;
    justify-content: center;
    gap: 1rem;
  }

  .reward-points-badge {
    min-width: auto;
  }

  #clientDetailsModal .modal-actions {
    padding: 1.25rem 1.5rem;
    grid-template-columns: 1fr;
    gap: 1rem;
  }

  #clientDetailsModal .modal-actions .btn-primary,
  #clientDetailsModal .modal-actions .btn-secondary {
    width: 100%;
    padding: 0.875rem;
    font-size: 1rem;
  }
}

@media (max-width: 480px) {
  .client-info-section {
    padding: 1.25rem;
  }

  .client-avatar {
    width: 60px;
    height: 60px;
    font-size: 1.5rem;
  }

  .client-main-info h3 {
    font-size: 1.2rem;
  }

  .rewards-section {
    padding: 1.25rem;
  }

  .rewards-section h4 {
    font-size: 1.1rem;
  }

  .available-reward-item {
    padding: 1.25rem;
  }

  .reward-name {
    font-size: 1rem;
  }

  .no-rewards-message {
    padding: 2rem 1rem;
    font-size: 0.9rem;
  }

  .no-rewards-message::before {
    font-size: 2.5rem;
  }
}

/* États et messages */
.success-message {
  background-color: var(--success-color);
  color: white;
  padding: 1rem;
  border-radius: 5px;
  margin-bottom: 1rem;
  text-align: center;
}

.error-message {
  background-color: var(--danger-color);
  color: white;
  padding: 1rem;
  border-radius: 5px;
  margin-bottom: 1rem;
  text-align: center;
}

.loading {
  opacity: 0.6;
  pointer-events: none;
}

/* Tooltips */
.tooltip {
  position: relative;
  cursor: help;
}

.tooltip::after {
  content: attr(data-tooltip);
  position: absolute;
  bottom: 100%;
  left: 50%;
  transform: translateX(-50%);
  background-color: var(--dark-color);
  color: white;
  padding: 0.5rem;
  border-radius: 5px;
  font-size: 0.8rem;
  white-space: nowrap;
  opacity: 0;
  visibility: hidden;
  transition: var(--transition);
}

.tooltip:hover::after {
  opacity: 1;
  visibility: visible;
}

/* Sélecteur de thèmes */
.theme-selector {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 1rem;
  margin-top: 0.5rem;
}

.theme-option {
  position: relative;
  cursor: pointer;
  border: 3px solid transparent;
  border-radius: 12px;
  padding: 1rem;
  text-align: center;
  transition: var(--transition);
  background: white;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.theme-option:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 15px rgba(0, 0, 0, 0.15);
}

.theme-option.active {
  border-color: var(--primary-color);
  box-shadow: 0 4px 15px rgba(52, 152, 219, 0.3);
}

.theme-preview {
  width: 60px;
  height: 40px;
  border-radius: 8px;
  margin: 0 auto 0.5rem;
  position: relative;
  overflow: hidden;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.theme-preview::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 60%;
  background: var(--theme-primary);
}

.theme-preview::after {
  content: "";
  position: absolute;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 40%;
  background: var(--theme-secondary);
}

.theme-name {
  font-size: 0.9rem;
  font-weight: 500;
  color: var(--dark-color);
  margin-bottom: 0.25rem;
}

.theme-description {
  font-size: 0.75rem;
  color: #666;
}

/* Couleurs des thèmes pour les previews */
.theme-option[data-theme="blue"] {
  --theme-primary: #007aff;
  --theme-secondary: #0051d5;
}

.theme-option[data-theme="pink"] {
  --theme-primary: #ff2d92;
  --theme-secondary: #c7005c;
}

.theme-option[data-theme="green"] {
  --theme-primary: #34c759;
  --theme-secondary: #248a3d;
}

.theme-option[data-theme="orange"] {
  --theme-primary: #ff9500;
  --theme-secondary: #cc7700;
}

.theme-option[data-theme="beige"] {
  --theme-primary: #a2845e;
  --theme-secondary: #8b6914;
}

.theme-option[data-theme="gray"] {
  --theme-primary: #6d6d6d;
  --theme-secondary: #48484a;
}

.theme-option[data-theme="purple"] {
  --theme-primary: #af52de;
  --theme-secondary: #8e44ad;
}

.theme-option[data-theme="indigo"] {
  --theme-primary: #5856d6;
  --theme-secondary: #4b49c4;
}

/* Responsive pour les thèmes */
@media (max-width: 768px) {
  .theme-selector {
    grid-template-columns: repeat(2, 1fr);
    gap: 0.75rem;
  }

  .theme-option {
    padding: 0.75rem;
  }

  .theme-preview {
    width: 50px;
    height: 35px;
  }

  .theme-name {
    font-size: 0.8rem;
  }

  .theme-description {
    font-size: 0.7rem;
  }
}

@media (max-width: 480px) {
  .theme-selector {
    grid-template-columns: 1fr;
    gap: 0.5rem;
  }

  .theme-option {
    display: flex;
    align-items: center;
    text-align: left;
    padding: 0.75rem;
  }

  .theme-preview {
    width: 40px;
    height: 30px;
    margin: 0 1rem 0 0;
  }

  .theme-info {
    flex: 1;
  }
}

/* Badges */
.badge {
  display: inline-block;
  padding: 0.25rem 0.75rem;
  font-size: 0.8rem;
  font-weight: bold;
  border-radius: 15px;
  text-transform: uppercase;
}

.badge-primary {
  background-color: var(--primary-color);
  color: white;
}

.badge-success {
  background-color: var(--success-color);
  color: white;
}

.badge-warning {
  background-color: var(--warning-color);
  color: white;
}

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

/* Responsive pour la modal détails client */
@media (max-width: 768px) {
  .confirmation-modal {
    max-width: 90vw;
    margin: 1rem;
  }

  /* Ajustements minimaux pour que la popup s'affiche correctement sur mobile */
  #clientDetailsModal .modal-content {
    max-width: 95vw;
    width: 95vw;
    margin: 1rem auto;
    max-height: 90vh;
    overflow: hidden;
    box-sizing: border-box;
  }

  .client-details-content {
    max-height: 85vh;
    overflow-y: auto;
    overflow-x: hidden;
    box-sizing: border-box;
  }

  .client-info-section {
    padding: 1.5rem;
    gap: 1rem;
    display: flex !important;
    flex-direction: row !important;
    align-items: flex-start !important;
    position: relative !important;
  }

  .client-avatar {
    width: 70px;
    height: 70px;
    font-size: 1.8rem;
    display: flex !important;
    align-items: center !important;
    justify-content: center !important;
    flex-shrink: 0 !important;
  }

  .client-main-info {
    flex: 1 !important;
    display: block !important;
  }

  .client-main-info h3 {
    font-size: 1.4rem;
    display: block !important;
  }

  .contact-item {
    font-size: 0.9rem;
    display: flex !important;
    align-items: center !important;
  }

  .contact-item i {
    width: 28px;
    height: 28px;
    font-size: 0.7rem;
    display: flex !important;
    align-items: center !important;
    justify-content: center !important;
  }

  .client-points-display {
    display: flex !important;
    align-items: center !important;
    justify-content: center !important;
    flex-direction: column !important;
    gap: 0.5rem !important;
    text-align: center !important;
  }

  .points-label {
    display: block !important;
  }

  .points-value {
    display: inline-block !important;
  }

  .rewards-section {
    padding: 1.5rem;
    display: block !important;
  }

  .available-rewards-list {
    display: flex !important;
    flex-direction: column !important;
  }

  .available-reward-item {
    padding: 1.5rem;
    font-size: 1rem;
    display: flex !important;
    justify-content: space-between !important;
    align-items: center !important;
    min-height: 220px !important;
  }

  .reward-info {
    flex: 1 !important;
    display: block !important;
    padding-right: 1.5rem !important;
  }

  .reward-name {
    font-size: 1.1rem;
    display: block !important;
    font-weight: 600 !important;
    color: var(--secondary-color) !important;
    margin-bottom: 0.5rem !important;
  }

  .reward-description {
    font-size: 0.9rem;
    display: block !important;
    color: #6c757d !important;
  }

  .reward-points-required {
    display: flex !important;
    flex-direction: column !important;
    align-items: flex-end !important;
    gap: 0.75rem !important;
    flex-shrink: 0 !important;
    min-width: 140px !important;
  }

  .reward-points-badge {
    font-size: 1rem;
    padding: 0.8rem 1.5rem;
    display: inline-block !important;
    background: var(--primary-color) !important;
    color: white !important;
    border-radius: 25px !important;
    font-weight: bold !important;
    box-shadow: 0 3px 12px rgba(52, 152, 219, 0.4) !important;
    white-space: nowrap !important;
    min-width: 80px !important;
    text-align: center !important;
  }

  .reward-points-badge.unavailable {
    background: #6c757d !important;
  }

  .reward-status {
    font-size: 0.85rem;
    padding: 0.5rem 1rem;
    display: inline-block !important;
    border-radius: 18px !important;
    text-transform: uppercase !important;
    letter-spacing: 0.5px !important;
    font-weight: 600 !important;
    white-space: nowrap !important;
    min-width: 120px !important;
    text-align: center !important;
  }

  .reward-status.available {
    color: var(--success-color) !important;
    background: rgba(39, 174, 96, 0.1) !important;
  }

  .reward-status.unavailable {
    color: #6c757d !important;
    background: rgba(108, 117, 125, 0.1) !important;
  }

  .client-info-section .close {
    top: 0.75rem;
    right: 0.75rem;
    width: 32px;
    height: 32px;
    font-size: 0;
    position: absolute !important;
    display: flex !important;
    align-items: center !important;
    justify-content: center !important;
  }

  /* !important nécessaire : cette règle doit gagner sur le display:grid de base
     (#clientDetailsModal .modal-actions plus haut dans ce fichier) — 3 boutons
     (Fermer/Historique/Modifier) empilés pleine largeur, jamais un rendu en ligne qui
     pourrait déborder ou wrapper n'importe comment sur petit écran. */
  #clientDetailsModal .modal-actions {
    padding: 1rem 1.5rem;
    gap: 0.75rem;
    display: flex !important;
    flex-direction: column !important;
  }

  #clientDetailsModal .modal-actions .btn-primary,
  #clientDetailsModal .modal-actions .btn-secondary {
    width: 100% !important;
    padding: 0.75rem 1.5rem;
    font-size: 0.9rem;
    display: inline-block !important;
  }
}

/* Force absolue sur tous les écrans - règles globales */
.available-reward-item .reward-points-badge,
.available-reward-item .reward-status {
  display: inline-block !important;
  visibility: visible !important;
  opacity: 1 !important;
  position: static !important;
  width: auto !important;
  height: auto !important;
  overflow: visible !important;
  clip: auto !important;
  transform: none !important;
  z-index: auto !important;
}

.available-reward-item .reward-points-required {
  display: flex !important;
  flex-direction: column !important;
  visibility: visible !important;
  opacity: 1 !important;
}

/* Sélecteur de date à 3 molettes (jour/mois/année) — remplace l'input date natif, dont
   le rendu et l'ergonomie varient trop entre navigateurs desktop et Safari/iPadOS. Chaque
   colonne est une liste scrollable avec scroll-snap : on fait défiler au doigt (tactile)
   ou à la molette/trackpad, la valeur centrée dans le bandeau surligné est sélectionnée.
   Cliquer une colonne puis taper un chiffre au clavier saute directement à la valeur. */
.date-wheel-picker {
  position: relative;
  display: flex;
  justify-content: center;
  gap: 2px;
  height: 160px;
  background: var(--bg-tertiary);
  border: 2px solid var(--separator-color);
  border-radius: var(--border-radius-medium);
  padding: 0 8px;
  overflow: hidden;
}

.date-wheel-highlight {
  position: absolute;
  top: 50%;
  left: 8px;
  right: 8px;
  transform: translateY(-50%);
  height: 40px;
  border-top: 2px solid var(--primary-color);
  border-bottom: 2px solid var(--primary-color);
  border-radius: 6px;
  background: color-mix(in srgb, var(--primary-color) 6%, transparent);
  pointer-events: none;
  z-index: 1;
}

.date-wheel-col {
  flex: 1;
  min-width: 0;
  height: 100%;
  overflow-y: scroll;
  scroll-snap-type: y mandatory;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  outline: none;
  /* Supprime le flash de surbrillance bleu par défaut au tap sur mobile/tablette. */
  -webkit-tap-highlight-color: transparent;
}

.date-wheel-col::-webkit-scrollbar {
  display: none;
}

.date-wheel-col:focus {
  background: color-mix(in srgb, var(--primary-color) 4%, transparent);
}

.date-wheel-item {
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  scroll-snap-align: center;
  font-size: 1rem;
  color: var(--text-tertiary, #8e8e93);
  cursor: pointer;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
  transition: color 0.15s ease, font-weight 0.15s ease;
}

.date-wheel-item.selected {
  color: var(--text-primary);
  font-weight: 700;
  font-size: 1.15rem;
}

/* PC (souris précise) : 3 champs cliquables JJ / MM / AAAA au lieu de la molette —
   scroller à la souris sur une molette n'est ni intuitif ni précis sur un écran non
   tactile ; cliquer un champ et taper au clavier l'est. Affiché/masqué en JS via
   DateWheelPicker/DateSimplePicker selon matchMedia('(hover: hover) and (pointer: fine)'). */
.date-simple-picker {
  display: none;
  align-items: center;
  justify-content: center;
  gap: 6px;
}

.date-simple-input {
  width: 3.8rem;
  height: 44px;
  padding: 0 8px;
  text-align: center;
  font-size: 1.05rem;
  font-weight: 600;
  color: var(--text-primary);
  background: var(--bg-tertiary);
  border: 2px solid var(--separator-color);
  border-radius: var(--border-radius-medium);
  outline: none;
  box-sizing: border-box;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.date-simple-input:nth-child(5) {
  width: 5rem;
}

.date-simple-input:focus {
  border-color: var(--primary-color);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary-color) 20%, transparent);
}

.date-simple-input::selection {
  background: color-mix(in srgb, var(--primary-color) 30%, transparent);
}

#clientBirthDateHint {
  margin-top: 0.5rem;
}

/* ===== Groupes de clients ===== */

.groups-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 1.5rem;
}

.group-card {
  background: var(--bg-secondary);
  padding: 1.5rem;
  border-radius: 10px;
  box-shadow: var(--shadow);
  transition: var(--transition);
  cursor: pointer;
  border-left: 5px solid var(--group-color, var(--primary-color));
}

.group-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 15px rgba(0, 0, 0, 0.15);
}

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

.group-card-header h3 {
  color: var(--secondary-color);
  min-width: 0;
  overflow-wrap: break-word;
}

.group-type-badge {
  font-size: 0.72rem;
  font-weight: 700;
  padding: 3px 8px;
  border-radius: 999px;
  text-transform: uppercase;
  letter-spacing: 0.02em;
}

.group-type-badge.manual {
  background: rgba(0, 122, 255, 0.12);
  color: var(--primary-color);
}

.group-type-badge.dynamic {
  background: rgba(255, 149, 0, 0.15);
  color: var(--warning-color);
}

.group-member-count {
  color: #666;
  font-size: 0.9rem;
}

/* Bascule Manuel / Intelligent dans la modale de création — neutre (pas les couleurs
   vert/rouge de .consent-toggle-btn, qui n'ont pas de sens ici). */
.type-toggle-group {
  display: flex;
  gap: 8px;
  margin: 6px 0;
}

.type-toggle-btn {
  flex: 1;
  padding: 10px;
  border: 2px solid var(--separator-color);
  border-radius: var(--border-radius-medium);
  background: var(--bg-tertiary);
  color: var(--text-secondary);
  font-weight: 600;
  cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
}

.type-toggle-btn.active {
  border-color: var(--primary-color);
  color: var(--primary-color);
  background: color-mix(in srgb, var(--primary-color) 8%, transparent);
}

/* Ensembles de critères (OU entre eux, ET à l'intérieur) d'un groupe dynamique */
.condition-set {
  border: 1px solid var(--separator-color);
  border-radius: var(--border-radius-medium);
  padding: 1rem;
  margin-bottom: 1rem;
  background: var(--bg-tertiary);
}

.condition-set-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-weight: 700;
  margin-bottom: 0.5rem;
  color: var(--text-secondary);
}

.condition-set-or-label {
  text-align: center;
  color: var(--text-tertiary, #8e8e93);
  font-size: 0.8rem;
  font-weight: 700;
  margin: -0.5rem 0 0.5rem;
}

.condition-set-remove {
  background: none;
  border: none;
  color: var(--danger-color);
  cursor: pointer;
  font-size: 1rem;
  padding: 4px;
}

.condition-set-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 0.75rem;
}

.condition-set-grid .form-group {
  margin-bottom: 0;
}

.condition-set-grid label {
  font-size: 0.78rem;
}

/* Section "Jeux-concours" dans l'onglet Campagnes : encadrée pour bien la distinguer
   des campagnes Email/SMS au-dessus, plutôt qu'un simple titre qui s'y confondait
   visuellement (retour utilisateur du 20/08/2026 — "on s'y perd"). */
/* Encadre les campagnes Email/SMS dans une carte blanche, symétrique au traitement de
   la section "Jeux-concours" juste en dessous (retour du 20/08/2026 : les deux
   sections doivent être visuellement au même niveau, chacune dans son propre cadre). */
.campaigns-section,
.contest-section {
  /* Les deux au même traitement (retour du 20/08/2026 : contraste entre les deux
     blocs cassait la cohérence de l'onglet) — passés en verre le 01/09/2026 avec le
     reste des grandes surfaces de l'app, toujours symétriques entre eux. */
  backdrop-filter: blur(20px) saturate(160%);
  -webkit-backdrop-filter: blur(20px) saturate(160%);
}

.campaigns-section {
  background: var(--bg-secondary);
  border: 1px solid var(--separator-color);
  border-radius: var(--border-radius-medium);
  padding: 1.5rem;
}

.contest-section {
  background: var(--bg-secondary);
  border: 1px solid var(--separator-color);
  border-radius: var(--border-radius-medium);
  padding: 1.5rem;
  /* margin-top: 2rem retiré (retour du 24/08/2026) : datait de l'époque où Jeux
     concours vivait DANS l'onglet Campagnes, sous .campaigns-section (voir le
     commentaire au-dessus, "juste au-dessus"). Depuis le 21/08/2026, Jeux concours a
     son propre onglet (#contestsContent) — .contest-section est désormais la
     première section de la page, comme .campaigns-section sur son propre onglet
     (sans margin-top). Sans ce retrait, l'écart sous l'en-tête de page était bien
     plus grand ici que sur les autres onglets. */
}

.contest-section .section-header {
  margin-bottom: 1.25rem;
  /* Empêche le texte de s'étirer sur toute la largeur en desktop large et de passer
     sous/derrière le bouton "Créer un jeu concours" (retour du 24/08/2026, "le texte
     est trop long"). align-items:flex-start (pas center, hérité de .section-header
     de base) : le bouton doit rester aligné en haut du bloc même si le texte fait
     plusieurs lignes, pas centré verticalement sur toute sa hauteur. */
  align-items: flex-start;
}

/* La "case" texte de présentation des jeux-concours — remplace le paragraphe brut
   qui s'étirait sur toute la largeur disponible. Largeur plafonnée + fond distinct +
   justification pour un bloc de texte propre, lisible, qui ne pousse jamais le
   bouton hors de la vue. */
.contest-section-intro {
  flex: 1 1 auto;
  min-width: 0; /* autorise le retour à la ligne dans un conteneur flex, sinon le texte peut forcer la largeur du parent au lieu de wrapper */
  max-width: 640px;
  /* Fond retiré (retour du 24/08/2026 : le gris plein faisait une case ignoble sur la
     carte déjà blanche) — juste une bordure fine pour délimiter la case sans pavé de
     couleur. */
  background: transparent;
  border: 1px solid var(--separator-color);
  border-radius: var(--border-radius-medium);
  padding: 1rem 1.25rem;
}

.contest-section-intro .settings-description {
  text-align: justify;
  margin: 0;
}

.contest-section .section-header .btn-primary {
  /* Ne doit jamais rétrécir pour laisser de la place au texte, même long — c'est
     l'inverse qui doit se produire (le texte wrap dans sa case plafonnée ci-dessus). */
  flex-shrink: 0;
}

/* Colonne pleine largeur comme .campaigns-list, pas une grille de cartes étroites
   (.groups-grid) — retour du 20/08/2026 : les concours doivent occuper toute la
   largeur disponible comme les campagnes juste au-dessus, pas se retrouver limités et
   côte à côte. */
.contests-list {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.contest-card {
  /* Pas de border-left bleu (retiré le 20/08/2026) — .campaign-card juste au-dessus
     n'en a pas non plus, et la barre verticale accrochait mal l'œil au ras du menu. */
  background: var(--bg-secondary);
  padding: 1.5rem;
  border-radius: 10px;
  box-shadow: var(--shadow);
  transition: var(--transition);
  cursor: pointer;
}

.contest-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 15px rgba(0, 0, 0, 0.15);
}

.contest-status-badge {
  font-size: 0.72rem;
  font-weight: 700;
  padding: 3px 8px;
  border-radius: 999px;
  text-transform: uppercase;
  letter-spacing: 0.02em;
}

.contest-status-badge.active {
  background: rgba(52, 199, 89, 0.15);
  color: var(--success-color, #34c759);
}

.contest-status-badge.inactive {
  background: rgba(142, 142, 147, 0.15);
  color: #8e8e93;
}

.contest-card-meta {
  color: #666;
  font-size: 0.9rem;
  margin-bottom: 0.25rem;
}

/* Sélecteur des 3 types de jeu-concours (#contestTypeModal) — boutons pleine largeur
   empilés plutôt que côte à côte : lisible sans réglage particulier du mobile jusqu'à
   l'iPad, pas besoin de media query dédiée. */
.contest-type-choices {
  padding: 0.5rem 1.5rem 1.5rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.contest-type-choice {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  width: 100%;
  padding: 1rem 1.1rem;
  background: var(--bg-secondary);
  border: 1px solid var(--separator-color, #d1d1d6);
  border-radius: var(--border-radius-medium, 10px);
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--secondary-color, #1c1c1e);
  cursor: pointer;
  text-align: left;
  transition: var(--transition);
}

.contest-type-choice:hover {
  border-color: var(--primary-color);
}

.contest-type-choice i {
  font-size: 1.2rem;
  color: var(--primary-color);
  width: 1.4rem;
  text-align: center;
  flex-shrink: 0;
}

/* Badge du type sur chaque carte concours (.contest-card) — même gabarit que
   .contest-status-badge juste à côté, couleur neutre (le type n'est ni bon ni mauvais,
   contrairement à actif/inactif). */
.contest-type-badge {
  font-size: 0.72rem;
  font-weight: 700;
  padding: 3px 8px;
  border-radius: 999px;
  background: var(--bg-secondary);
  color: var(--primary-color);
  border: 1px solid var(--separator-color, #d1d1d6);
}

/* Vignettes de choix du thème de fond (page publique concours.html, voir
   FideliProApp.CONTEST_THEMES) — pastilles rondes cliquables, comme un sélecteur de
   couleur classique. Taille assez grande (44px) pour rester confortable au doigt sur
   iPad, pas seulement à la souris. */
/* 1 seule rangée, garantie par flex-wrap:nowrap (un grid en 1fr peut quand
   même passer à la ligne si un enfant a un min-width intrinsèque trop grand —
   nowrap l'interdit purement et simplement). Chaque swatch a flex:1 avec
   min-width:0 pour pouvoir rétrécir sous sa taille de base plutôt que de
   forcer un retour à la ligne ou un débordement horizontal. */
.contest-theme-choices {
  display: flex;
  flex-wrap: nowrap;
  gap: 0.6rem;
  margin-bottom: 1.25rem;
}

.contest-theme-swatch {
  flex: 1 1 0;
  min-width: 0;
  aspect-ratio: 1;
  border-radius: 50%;
  border: 2px solid var(--separator-color, #d1d1d6);
  cursor: pointer;
  padding: 0;
  transition: var(--transition);
}

.contest-theme-swatch:hover {
  transform: scale(1.08);
}

.contest-theme-swatch.selected {
  border-color: var(--primary-color);
  box-shadow: 0 0 0 2px var(--bg-secondary), 0 0 0 4px var(--primary-color);
}

/* Petite pastille de thème sur la carte concours (.contest-card), à côté du badge de
   type — juste un repère visuel rapide, pas cliquable. */
.contest-theme-dot {
  display: inline-block;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  border: 1px solid var(--separator-color, #d1d1d6);
  vertical-align: middle;
}

.contest-lot-row {
  display: flex;
  gap: 0.75rem;
  align-items: flex-end;
  margin-bottom: 0.75rem;
}

.contest-lot-row .form-group {
  margin-bottom: 0;
}

.contest-lot-row .contest-lot-label {
  flex: 3;
  min-width: 0;
}

.contest-lot-row .contest-lot-weight {
  flex: 1;
  min-width: 0;
}

.contest-lot-remove {
  background: none;
  border: none;
  color: var(--danger-color);
  cursor: pointer;
  font-size: 1rem;
  padding: 0 0 10px;
}

/* Total des % de chance des lots — vert quand ça fait exactement 100 (condition de
   validité du concours, voir ContestController.validateLotsInput côté serveur), rouge
   sinon, pour que l'écart soit visible avant même de tenter de sauvegarder. */
.contest-lots-total {
  font-weight: 700;
  margin: 0.5rem 0 1rem;
  color: var(--danger-color);
}

.contest-lots-total.valid {
  color: var(--success-color, #34c759);
}

/* Modale détail d'un groupe — pas de <form> ici (contrairement aux autres modales),
   donc pas de padding hérité de `.modal form` : on le pose explicitement. */
.group-details-body {
  padding: var(--spacing-xl);
  max-height: calc(90vh - 140px);
  overflow-y: auto;
  box-sizing: border-box;
}

.group-details-meta {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 1rem;
  color: #666;
  font-size: 0.9rem;
}

.group-member-search-results {
  max-height: 180px;
  overflow-y: auto;
  border: 1px solid var(--separator-color);
  border-radius: var(--border-radius-medium);
  margin-top: 4px;
}

.group-member-search-results:empty {
  border: none;
}

.group-member-search-result {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 8px 12px;
  cursor: pointer;
}

.group-member-search-result:hover {
  background: var(--bg-tertiary);
}

.group-member-search-result-dynamic {
  cursor: default;
}

.group-member-search-actions {
  display: flex;
  gap: 6px;
  flex-shrink: 0;
}

.group-member-search-actions button {
  width: 32px;
  height: 32px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

.group-members-list,
.group-exceptions-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 1rem;
  max-height: 260px;
  overflow-y: auto;
}

.group-member-row,
.group-exception-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 8px 12px;
  background: var(--bg-tertiary);
  border-radius: var(--border-radius-medium);
}

.group-exception-row .exception-badge {
  font-size: 0.72rem;
  font-weight: 700;
  padding: 2px 8px;
  border-radius: 999px;
  margin-left: 8px;
}

.exception-badge.exclude {
  background: rgba(255, 59, 48, 0.12);
  color: var(--danger-color);
}

.exception-badge.include {
  background: rgba(52, 199, 89, 0.12);
  color: var(--success-color);
}

/* Chips de groupe sur la fiche client */
.client-groups-section {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin: 0.75rem 0;
}

.client-group-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border-radius: 999px;
  font-size: 0.8rem;
  font-weight: 600;
  color: white;
  background: var(--chip-color, var(--primary-color));
}

.client-group-chip .chip-remove {
  background: none;
  border: none;
  color: white;
  opacity: 0.85;
  cursor: pointer;
  font-size: 0.85rem;
  padding: 0;
}

.client-group-chip.dynamic {
  opacity: 0.85;
}

/* position:fixed + repositionné en JS (voir setupClientGroupAddMenuPositioning) plutôt
   qu'absolute/top/left figés en CSS : ce menu vit dans .client-details-content, qui a
   `transform: translateZ(0)` (fix de repaint Safari) — un transform sur un ancêtre
   devient le "containing block" de tout descendant position:fixed, donc un simple
   position:fixed ici resterait piégé dans le cadre scrollable au lieu de flotter
   par-dessus toute la modale. Sur petit écran/résolution serrée, ce cadre est parfois
   trop court pour laisser la place au menu sous le bouton "+", qui se retrouvait alors
   coupé/inatteignable sous "Points disponibles" (bug remonté par l'utilisateur, visible
   seulement sur certains appareils/résolutions). Le JS déplace l'élément dans
   document.body à l'ouverture pour échapper complètement à ce containing block, et
   calcule top/left dynamiquement (avec bascule vers le haut si pas la place en bas). */
.client-group-add-menu {
  position: fixed;
  z-index: 3000;
  min-width: 220px;
  max-width: min(280px, calc(100vw - 24px));
  max-height: 220px;
  overflow-y: auto;
  background: var(--bg-secondary);
  border: 1px solid var(--separator-color);
  border-radius: var(--border-radius-medium);
  box-shadow: var(--shadow-xl);
}

.client-group-add-menu .group-member-search-result {
  color: var(--text-primary);
}

.group-name-with-dot {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

.group-color-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  flex-shrink: 0;
}

.client-group-add-btn {
  background: none;
  border: 2px dashed var(--separator-color);
  border-radius: 999px;
  width: 26px;
  height: 26px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--text-tertiary, #8e8e93);
  cursor: pointer;
}

/* Chiffres animés du dashboard : largeur fixe par chiffre (tabular-nums) pour que
   l'animation ne "tremble" pas latéralement à chaque frame — sans ça, chaque chiffre
   ayant une largeur différente (1 plus étroit que 8, etc.), le nombre entier se
   redimensionne en permanence pendant le comptage, ce qui casse l'impression de
   fluidité surtout sur les gros nombres qui changent beaucoup de chiffres à la fois. */
/* Ciblage des campagnes : 3 boutons de mode (manuel / groupes / tous), remplace
   l'ancien <select multiple>. flex-wrap + padding pour ne jamais coller aux bords de
   la modale sur tablette (retour d'expérience régulier sur ce point). */
.target-mode-group {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 6px 0;
}

.target-mode-btn {
  flex: 1;
  min-width: 150px;
  padding: 10px 12px;
  border: 2px solid var(--separator-color);
  border-radius: var(--border-radius-medium);
  background: var(--bg-tertiary);
  color: var(--text-secondary);
  font-weight: 600;
  font-size: 0.85rem;
  cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
}

.target-mode-btn.active {
  border-color: var(--primary-color);
  color: var(--primary-color);
  background: color-mix(in srgb, var(--primary-color) 8%, transparent);
}

.target-mode-panel {
  margin-top: 0.75rem;
  padding: 1.25rem;
  border: 1px solid var(--separator-color);
  border-radius: var(--border-radius-medium);
  background: var(--bg-tertiary);
  box-sizing: border-box;
}

.target-mode-panel > small {
  display: block;
  margin-top: 0.6rem;
}

.target-chips-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 1rem;
  padding: 0 0.1rem;
}

.target-chips-row:empty {
  margin-top: 0;
}

/* Réutilise visuellement les chips de groupe de la fiche client (encadré arrondi +
   croix pour désélectionner), mais neutres (pas de couleur de groupe) puisqu'il s'agit
   ici de clients, pas de groupes. */
.target-client-chip,
.target-group-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 12px;
  border-radius: 999px;
  font-size: 0.8rem;
  font-weight: 600;
  color: white;
  background: var(--primary-color);
}

.target-group-chip {
  background: var(--chip-color, var(--primary-color));
}

.target-client-chip .chip-remove,
.target-group-chip .chip-remove {
  background: none;
  border: none;
  color: white;
  opacity: 0.85;
  cursor: pointer;
  font-size: 0.85rem;
  padding: 0;
}

.target-groups-list {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

/* Bloc "Exclure des destinataires" — même principe que les exceptions des groupes
   intelligents, toujours visible sous les puces sélectionnées quel que soit le mode
   actif au-dessus (manuel/groupes/tous). Séparé visuellement par une bordure fine
   plutôt qu'un fond plein, pour rester secondaire par rapport au ciblage principal. */
.target-exclude-block {
  margin-top: 1.25rem;
  padding-top: 1rem;
  border-top: 1px dashed var(--separator-color);
}

.target-exclude-label {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-weight: 600;
  color: var(--danger-color);
  font-size: 0.95rem;
  margin-bottom: 0.25rem;
}

.target-exclude-block .settings-description {
  margin-bottom: 0.6rem;
}

/* Puce d'exclusion — rouge (danger), pas la couleur primaire des puces d'inclusion
   normales (.target-client-chip), pour que l'effet "ce client ne recevra RIEN" soit
   visuellement sans ambiguïté avec une puce de ciblage classique. */
.target-exclude-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 12px;
  border-radius: 999px;
  font-size: 0.8rem;
  font-weight: 600;
  color: white;
  background: var(--danger-color);
}

.target-exclude-chip .chip-remove {
  background: none;
  border: none;
  color: white;
  opacity: 0.85;
  cursor: pointer;
  font-size: 0.85rem;
  padding: 0;
}

.target-group-option {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
  border: 2px solid var(--separator-color);
  border-radius: var(--border-radius-medium);
  background: var(--bg-secondary);
  cursor: pointer;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text-secondary);
  transition: border-color 0.15s ease, background 0.15s ease;
}

.target-group-option.selected {
  border-color: var(--group-color, var(--primary-color));
  background: color-mix(in srgb, var(--group-color, var(--primary-color)) 10%, transparent);
  color: var(--text-primary);
}

.target-group-option .group-color-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--group-color, var(--primary-color));
  flex-shrink: 0;
}

.target-all-confirm {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--text-secondary);
  font-size: 0.85rem;
  margin: 0;
}

.scroll-to-top-btn {
  position: fixed;
  right: var(--spacing-xl, 1.5rem);
  bottom: var(--spacing-xl, 1.5rem);
  /* Au-dessus du header sticky (z-index: 100) et des modales, mais peu importe ici
     puisqu'il est masqué (hidden) tant qu'une modale est ouverte n'est pas géré —
     en pratique une modale couvre tout l'écran donc le bouton reste sous elle. */
  z-index: 90;
  width: 48px;
  height: 48px;
  border: none;
  border-radius: 50%;
  background: var(--primary-color, #007aff);
  color: #fff;
  font-size: 1.1rem;
  box-shadow: var(--shadow-md, 0 4px 12px rgba(0, 0, 0, 0.2));
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  transform: translateY(10px);
  transition: opacity 0.2s ease, transform 0.2s ease;
  pointer-events: none;
}

.scroll-to-top-btn.visible {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}

.scroll-to-top-btn:hover {
  filter: brightness(1.1);
}

.clients-pagination {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: center;
  gap: 0.75rem;
  margin-top: 1rem;
}

.clients-pagination-size {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.85rem;
  color: var(--text-secondary);
}

.clients-pagination-size select {
  padding: 0.35rem 0.5rem;
}

.clients-pagination-nav {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  font-size: 0.85rem;
  color: var(--text-secondary);
}

.clients-pagination-nav button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.clients-footer-actions {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 0.5rem;
  margin-top: 1rem;
}

.clients-footer-separator {
  color: var(--text-tertiary, #8e8e93);
  font-size: 0.8rem;
}

.clients-footer-link {
  font-size: 0.8rem;
  color: var(--text-tertiary, #8e8e93);
  text-decoration: underline;
}

.clients-footer-link:hover {
  color: var(--text-secondary);
}

.clients-footer-link-danger {
  color: #ff3b30;
}

.clients-footer-link-danger:hover {
  color: #d70015;
}

.stat-counter {
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum';
}

.date-simple-sep {
  color: var(--text-tertiary, #8e8e93);
  font-weight: 600;
}
