/* ClientBase, In-app dashboard (fully functional) */

// Per-module accent colors (OKLCH), used in sidebar, topbar title, and module-specific highlights
const MODULE_TONES = {
  accueil:      { bg: "oklch(95% 0.06 268)",  ink: "oklch(38% 0.18 268)", solid: "oklch(55% 0.22 268)" }, // violet
  agenda:       { bg: "oklch(95% 0.05 30)",   ink: "oklch(42% 0.14 30)",  solid: "oklch(60% 0.17 30)"  }, // orange/coral
  bookings:     { bg: "oklch(94% 0.06 200)",  ink: "oklch(38% 0.12 200)", solid: "oklch(55% 0.15 200)" }, // teal
  clients:      { bg: "oklch(95% 0.05 340)",  ink: "oklch(42% 0.14 340)", solid: "oklch(60% 0.17 340)" }, // pink
  fidelite:     { bg: "oklch(95% 0.06 85)",   ink: "oklch(42% 0.14 85)",  solid: "oklch(62% 0.14 85)"  }, // gold
  stats:        { bg: "oklch(94% 0.05 160)",  ink: "oklch(38% 0.1 160)",  solid: "oklch(55% 0.12 160)" }, // sage/green
  factures:     { bg: "oklch(94% 0.05 240)",  ink: "oklch(40% 0.14 240)", solid: "oklch(55% 0.16 240)" }, // blue
  stock:        { bg: "oklch(95% 0.06 50)",   ink: "oklch(42% 0.14 50)",  solid: "oklch(60% 0.15 50)"  }, // amber
  page:         { bg: "oklch(95% 0.06 200)",  ink: "oklch(38% 0.12 200)", solid: "oklch(55% 0.15 200)" }, // teal (cousin du bookings)
  giftcards:    { bg: "oklch(95% 0.06 350)",  ink: "oklch(42% 0.14 350)", solid: "oklch(60% 0.17 350)" }, // rose-cadeau
  subscription: { bg: "oklch(95% 0.06 300)",  ink: "oklch(40% 0.18 300)", solid: "oklch(55% 0.22 300)" }, // magenta
  settings:     { bg: "oklch(94% 0.008 260)", ink: "oklch(38% 0.012 260)",solid: "oklch(50% 0.012 260)" }, // neutral
};

const APP_MODULES = [
  { id: "accueil",      icon: "sparkle",  label: "Accueil" },
  { id: "agenda",       icon: "calendar", label: "Agenda" },
  { id: "services",     icon: "tag",      label: "Prestations" },
  { id: "bookings",     icon: "link",     label: "Prise de RDV" },
  { id: "page",         icon: "eye",      label: "Ma page" },
  { id: "clients",      icon: "users",    label: "Clients" },
  { id: "fidelite",     icon: "heart",    label: "Carte de fidélité" },
  { id: "giftcards",    icon: "gift",     label: "Cartes cadeaux" },
  { id: "relances",     icon: "bell",     label: "Relances" },
  { id: "avis",         icon: "star",     label: "Avis" },
  { id: "stats",        icon: "chart",    label: "Statistiques" },
  { id: "factures",     icon: "invoice",  label: "Facturation" },
  { id: "acomptes",     icon: "lock",     label: "Acomptes" },
  { id: "stock",        icon: "box",      label: "Stock" },
  { id: "subscription", icon: "zap",      label: "Abonnement" },
  // Modules de RÔLE : pas dans les groupes (affichés à part selon le profil).
  { id: "ambassadrice", icon: "heart",    label: "Espace Ambassadrice" },
  { id: "cofondatrice", icon: "chart",    label: "Espace Co-fondatrice" },
  { id: "admin",        icon: "shield",   label: "Administration" },
];

// Code couleur discret, juste assez pour se repérer.
// `dot` : pastille colorée à côté du label de groupe (~6 px)
// `label` : couleur du texte du label (CAPS), version douce du dot
// Le reste de l'UI (cartes, fonds) reste blanc/gris neutre.
const CB_TONE = {
  activity: { dot: "var(--accent)",       label: "var(--accent-ink)"     }, // indigo
  clients:  { dot: "oklch(60% 0.16 340)", label: "oklch(45% 0.13 340)"   }, // rose
  business: { dot: "oklch(60% 0.13 160)", label: "oklch(40% 0.10 160)"   }, // sage
  account:  { dot: "oklch(58% 0.16 290)", label: "oklch(45% 0.13 290)"   }, // lavande
  legal:    { dot: "oklch(58% 0.16 240)", label: "oklch(42% 0.13 240)"   }, // bleu
  contact:  { dot: "oklch(58% 0.13 200)", label: "oklch(42% 0.10 200)"   }, // teal
  data:     { dot: "oklch(60% 0.15 50)",  label: "oklch(45% 0.12 50)"    }, // peach
};

// === Palettes par catégorie (modifiables par l'utilisateur via paramètres) ===
const CB_GROUP_PALETTES = {
  indigo:    { dot: "oklch(55% 0.22 278)", label: "oklch(38% 0.18 278)", bg: "oklch(95% 0.06 278)", ink: "oklch(38% 0.18 278)", solid: "oklch(55% 0.22 278)" },
  rose:      { dot: "oklch(60% 0.16 340)", label: "oklch(45% 0.13 340)", bg: "oklch(95% 0.05 340)", ink: "oklch(42% 0.14 340)", solid: "oklch(60% 0.17 340)" },
  sage:      { dot: "oklch(55% 0.12 160)", label: "oklch(40% 0.10 160)", bg: "oklch(94% 0.05 160)", ink: "oklch(38% 0.10 160)", solid: "oklch(55% 0.12 160)" },
  lavande:   { dot: "oklch(58% 0.16 290)", label: "oklch(45% 0.13 290)", bg: "oklch(95% 0.06 300)", ink: "oklch(40% 0.18 300)", solid: "oklch(55% 0.22 300)" },
  bleu:      { dot: "oklch(58% 0.16 240)", label: "oklch(42% 0.13 240)", bg: "oklch(94% 0.05 240)", ink: "oklch(40% 0.14 240)", solid: "oklch(55% 0.16 240)" },
  orange:    { dot: "oklch(60% 0.17 30)",  label: "oklch(45% 0.14 30)",  bg: "oklch(95% 0.05 30)",  ink: "oklch(42% 0.14 30)",  solid: "oklch(60% 0.17 30)"  },
  teal:      { dot: "oklch(55% 0.15 200)", label: "oklch(38% 0.12 200)", bg: "oklch(94% 0.06 200)", ink: "oklch(38% 0.12 200)", solid: "oklch(55% 0.15 200)" },
  ambre:     { dot: "oklch(60% 0.15 50)",  label: "oklch(45% 0.12 50)",  bg: "oklch(95% 0.06 50)",  ink: "oklch(42% 0.14 50)",  solid: "oklch(60% 0.15 50)"  },
};
const CB_PALETTE_LABELS = {
  indigo: "Indigo", rose: "Rose", sage: "Sage", lavande: "Lavande",
  bleu: "Bleu", orange: "Orange", teal: "Teal", ambre: "Ambre",
};

// Espace pro : indigo (= bleu du site) domine partout, seul le groupe
// « clientèle » garde une touche colorée (rose) pour signaler le métier
// relationnel. Le reste (activité, business, compte) reste sur l'indigo
// de marque pour une identité visuelle plus serrée.
const APP_MODULE_GROUPS = [
  { id: "activity", label: "Mon activité",  icon: "sparkle", defaultPalette: "indigo", ids: ["accueil", "agenda", "services", "bookings", "page"] },
  { id: "clients",  label: "Ma clientèle",  icon: "heart",   defaultPalette: "rose",   ids: ["clients", "fidelite", "giftcards"] },
  { id: "marketing",label: "Marketing",     icon: "chat",    defaultPalette: "ambre",  ids: ["relances", "avis"] },
  { id: "business", label: "Mon business",  icon: "chart",   defaultPalette: "sage",   ids: ["stats", "factures", "acomptes", "stock"] },
  { id: "account",  label: "Mon compte",    icon: "zap",     defaultPalette: "indigo", ids: ["subscription"] },
];

// Palettes des modules, figées sur la palette canonique (defaultPalette).
// La customisation utilisateur a été retirée pour garantir la cohérence
// visuelle de chaque module entre site vitrine, démo et espace pro.
const getGroupPalettes = () => {
  const out = {};
  APP_MODULE_GROUPS.forEach(g => { out[g.id] = CB_GROUP_PALETTES[g.defaultPalette]; });
  return out;
};

// Helper : trouve le groupe d'un module donné
const getGroupOfModule = (moduleId) => {
  for (const g of APP_MODULE_GROUPS) {
    if (g.ids.includes(moduleId)) return g;
  }
  // settings : pas dans les groupes → lavande
  return APP_MODULE_GROUPS[APP_MODULE_GROUPS.length - 1];
};

// Hook : tones du module (utilise la palette du groupe)
const useGroupTones = () => {
  const [palettes, setPalettes] = React.useState(getGroupPalettes);
  React.useEffect(() => {
    const onStorage = () => setPalettes(getGroupPalettes());
    window.addEventListener("storage", onStorage);
    window.addEventListener("cb-palette-change", onStorage);
    return () => {
      window.removeEventListener("storage", onStorage);
      window.removeEventListener("cb-palette-change", onStorage);
    };
  }, []);
  return palettes;
};

// Retourne le tone d'un module (= palette de son groupe)
const getModuleTone = (moduleId, palettes) => {
  const g = getGroupOfModule(moduleId);
  return palettes[g.id] || CB_GROUP_PALETTES.indigo;
};

// Badge vérifié inspiré de Meta/Instagram, sunburst scalloped + check blanc
const VerifiedBadge = ({ size = 16, title = "Compte vérifié" }) => (
  <svg width={size} height={size} viewBox="0 0 24 24" style={{ flexShrink: 0 }}
       role="img" aria-label={title}>
    <title>{title}</title>
    <path
      fill="var(--accent)"
      d="M22.25 12L20.86 10.41L21.43 8.36L19.39 7.74L18.95 5.66L16.83 5.5L15.69 3.71L13.66 4.47L12 3.16L10.34 4.47L8.31 3.71L7.17 5.5L5.05 5.66L4.61 7.74L2.57 8.36L3.14 10.41L1.75 12L3.14 13.59L2.57 15.64L4.61 16.26L5.05 18.34L7.17 18.5L8.31 20.29L10.34 19.53L12 20.84L13.66 19.53L15.69 20.29L16.83 18.5L18.95 18.34L19.39 16.26L21.43 15.64L20.86 13.59L22.25 12Z"
    />
    <path
      d="M8.5 12L11 14.5L15.5 10"
      stroke="white" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" fill="none"
    />
  </svg>
);

// Variante "non vérifié", même forme, gris-warn
const UnverifiedBadge = ({ size = 16, title = "Email non vérifié" }) => (
  <svg width={size} height={size} viewBox="0 0 24 24" style={{ flexShrink: 0 }}
       role="img" aria-label={title}>
    <title>{title}</title>
    <path
      fill="var(--warn)"
      d="M22.25 12L20.86 10.41L21.43 8.36L19.39 7.74L18.95 5.66L16.83 5.5L15.69 3.71L13.66 4.47L12 3.16L10.34 4.47L8.31 3.71L7.17 5.5L5.05 5.66L4.61 7.74L2.57 8.36L3.14 10.41L1.75 12L3.14 13.59L2.57 15.64L4.61 16.26L5.05 18.34L7.17 18.5L8.31 20.29L10.34 19.53L12 20.84L13.66 19.53L15.69 20.29L16.83 18.5L18.95 18.34L19.39 16.26L21.43 15.64L20.86 13.59L22.25 12Z"
    />
    <path
      d="M12 8v5M12 16.5v0.5"
      stroke="white" strokeWidth="2.5" strokeLinecap="round" fill="none"
    />
  </svg>
);

// Lundi de la semaine en cours (offset absolu, sert d'ancre pour l'agenda)
const _AGENDA_MONDAY_OFFSET = cbMondayOffset();
const DAY_LABELS = ["Lun", "Mar", "Mer", "Jeu", "Ven", "Sam"].map((lbl, i) => {
  const d = cbDateForOffset(_AGENDA_MONDAY_OFFSET + i);
  return `${lbl} ${d.getDate()}`;
});
const COLOR_MAP = {
  accent: { bg: "var(--accent-soft)", border: "var(--accent)", ink: "var(--accent-ink)" },
  sage:   { bg: "var(--sage-soft)",   border: "var(--sage)",   ink: "oklch(38% 0.08 160)" },
  warn:   { bg: "oklch(97% 0.025 30)",border: "oklch(70% 0.14 30)", ink: "oklch(42% 0.14 30)" },
};
const COLOR_CHOICES = [["accent", "Violet"], ["sage", "Vert"], ["warn", "Orange"]];

const fmtEUR = (n) => {
  if (n == null || isNaN(n)) return "0 €";
  return n.toLocaleString("fr-FR", { minimumFractionDigits: (n % 1 === 0 ? 0 : 2), maximumFractionDigits: 2 }) + " €";
};
const fmtH = (h) => {
  const hh = Math.floor(h);
  const mm = Math.round((h - hh) * 60);
  return `${hh.toString().padStart(2,"0")}:${mm.toString().padStart(2,"0")}`;
};

/* Dynamic, time- & context-aware greeting line.
   Picks a short friendly phrase + emoji based on hour, weekday, and today's load. */
const cbDynamicGreeting = (firstName, todayCount = 0) => {
  const now = new Date();
  const h = now.getHours();
  const m = now.getMinutes();
  const dow = now.getDay(); // 0 dim … 6 sam
  const minutes = h * 60 + m;
  const name = firstName || "";

  // Specific weekday flourishes
  if (dow === 1 && h >= 6 && h < 11) return { msg: `Bonne semaine, ${name}`, emoji: "✨" };
  if (dow === 5 && h >= 16 && h < 20) return { msg: `Bon vendredi, ${name}`, emoji: "🎉" };
  if ((dow === 0 || dow === 6) && h >= 8 && h < 20) return { msg: `Bon week-end, ${name}`, emoji: "🌿" };

  // Time-of-day buckets
  if (minutes < 6 * 60)            return { msg: `Couche-tôt, ${name} ?`,         emoji: "🌙" };
  if (minutes < 9 * 60)            return { msg: `Bonjour, ${name}`,              emoji: "☀️" };
  if (minutes < 11 * 60 + 30)      return { msg: `Belle matinée, ${name}`,        emoji: "☕" };
  if (minutes < 12 * 60 + 30)      return { msg: `Presque midi, ${name}`,         emoji: "🍽️" };
  if (minutes < 14 * 60)           return { msg: `Bonne pause déjeuner, ${name}`, emoji: "🥗" };
  if (minutes < 16 * 60)           return { msg: `Bel après-midi, ${name}`,       emoji: "🌤️" };
  if (minutes < 18 * 60)           return { msg: `Bonne fin d'après-midi, ${name}`, emoji: "🍵" };
  if (minutes < 20 * 60)           return todayCount > 0
    ? { msg: `Belle journée bouclée, ${name}`, emoji: "✅" }
    : { msg: `Bonne fin de journée, ${name}`,  emoji: "🌇" };
  if (minutes < 22 * 60 + 30)      return { msg: `Bonne soirée, ${name}`,         emoji: "🌙" };
  return { msg: `Bonne nuit, ${name}`,                                            emoji: "✨" };
};

/* Fixed-date holidays/events + a curated saints calendar.
   Returns null when nothing notable for that MM-DD. */
const _CB_EVENTS = {
  "01-01": { kind: "fete",   label: "Jour de l'An",            emoji: "🎊" },
  "01-06": { kind: "fete",   label: "Épiphanie",               emoji: "👑" },
  "02-02": { kind: "fete",   label: "Chandeleur",              emoji: "🥞" },
  "02-14": { kind: "fete",   label: "Saint-Valentin",          emoji: "💌" },
  "03-08": { kind: "fete",   label: "Journée des droits des femmes", emoji: "💜" },
  "03-17": { kind: "saint",  label: "Saint Patrick",           emoji: "🍀" },
  "03-19": { kind: "saint",  label: "Saint Joseph",            emoji: "🌿" },
  "03-21": { kind: "fete",   label: "Premier jour du printemps", emoji: "🌸" },
  "04-01": { kind: "fete",   label: "1er avril",               emoji: "🐟" },
  "04-22": { kind: "fete",   label: "Jour de la Terre",        emoji: "🌍" },
  "05-01": { kind: "ferie",  label: "Fête du Travail",         emoji: "🌷" },
  "05-08": { kind: "ferie",  label: "Victoire 1945",           emoji: "🇫🇷" },
  "06-21": { kind: "fete",   label: "Fête de la Musique",      emoji: "🎶" },
  "07-14": { kind: "ferie",  label: "Fête nationale",          emoji: "🎆" },
  "08-15": { kind: "ferie",  label: "Assomption",              emoji: "✨" },
  "09-23": { kind: "fete",   label: "Premier jour de l'automne", emoji: "🍂" },
  "10-31": { kind: "fete",   label: "Halloween",               emoji: "🎃" },
  "11-01": { kind: "ferie",  label: "Toussaint",               emoji: "🕯️" },
  "11-11": { kind: "ferie",  label: "Armistice 1918",          emoji: "🌹" },
  "11-25": { kind: "saint",  label: "Sainte Catherine",        emoji: "💜" },
  "12-06": { kind: "saint",  label: "Saint Nicolas",           emoji: "🎁" },
  "12-21": { kind: "fete",   label: "Premier jour de l'hiver", emoji: "❄️" },
  "12-24": { kind: "fete",   label: "Réveillon de Noël",       emoji: "🎄" },
  "12-25": { kind: "ferie",  label: "Noël",                    emoji: "🎄" },
  "12-31": { kind: "fete",   label: "Saint-Sylvestre",         emoji: "🥂" },
  // Saints familiers (jour de la fête)
  "01-21": { kind: "saint", label: "Sainte Agnès",       emoji: "🌼" },
  "02-05": { kind: "saint", label: "Sainte Agathe",      emoji: "🌼" },
  "03-09": { kind: "saint", label: "Sainte Françoise",   emoji: "🌼" },
  "04-29": { kind: "saint", label: "Sainte Catherine de Sienne", emoji: "🌼" },
  "05-30": { kind: "saint", label: "Sainte Jeanne d'Arc", emoji: "⚔️" },
  "06-13": { kind: "saint", label: "Saint Antoine",      emoji: "🌿" },
  "06-24": { kind: "saint", label: "Saint Jean",         emoji: "🔥" },
  "07-22": { kind: "saint", label: "Sainte Marie-Madeleine", emoji: "🌼" },
  "07-26": { kind: "saint", label: "Sainte Anne",        emoji: "🌼" },
  "08-10": { kind: "saint", label: "Saint Laurent",      emoji: "🌿" },
  "08-28": { kind: "saint", label: "Saint Augustin",     emoji: "🌿" },
  "09-08": { kind: "saint", label: "Nativité de Marie",  emoji: "🌼" },
  "09-29": { kind: "saint", label: "Saint Michel",       emoji: "🌿" },
  "10-04": { kind: "saint", label: "Saint François",     emoji: "🌿" },
  "10-15": { kind: "saint", label: "Sainte Thérèse d'Avila", emoji: "🌼" },
  "12-04": { kind: "saint", label: "Sainte Barbe",       emoji: "🌼" },
  "12-08": { kind: "fete",  label: "Immaculée Conception", emoji: "✨" },
  "12-13": { kind: "saint", label: "Sainte Lucie",       emoji: "🕯️" },
  "12-26": { kind: "saint", label: "Saint Étienne",      emoji: "🌿" },
};
const cbEventOfDay = (d = new Date()) => {
  const mm = String(d.getMonth() + 1).padStart(2, "0");
  const dd = String(d.getDate()).padStart(2, "0");
  return _CB_EVENTS[`${mm}-${dd}`] || null;
};

/* "DD/MM" → "MM-DD" key, or null if invalid. */
const _bdayKey = (s) => {
  if (!s) return null;
  const m = String(s).match(/^(\d{1,2})[\/\-\.](\d{1,2})$/);
  if (!m) return null;
  const dd = +m[1], mm = +m[2];
  if (mm < 1 || mm > 12 || dd < 1 || dd > 31) return null;
  return `${String(mm).padStart(2,"0")}-${String(dd).padStart(2,"0")}`;
};
const cbBirthdaysToday = (clients = []) => {
  const today = new Date();
  const key = `${String(today.getMonth()+1).padStart(2,"0")}-${String(today.getDate()).padStart(2,"0")}`;
  return clients.filter(c => _bdayKey(c.birthday) === key);
};

/* Réseaux sociaux, labels, placeholders, lien profil pour un pseudo donné. */
const CB_SOCIAL_LABEL = {
  instagram: "Instagram",
  facebook:  "Facebook",
  tiktok:    "TikTok",
  snapchat:  "Snapchat",
  whatsapp:  "WhatsApp",
};
const CB_SOCIAL_PLACEHOLDER = {
  instagram: "@pseudo",
  facebook:  "Pseudo ou prénom.nom",
  tiktok:    "@pseudo",
  snapchat:  "pseudo",
  whatsapp:  "06 12 34 56 78",
};
const cbSocialUrl = (network, handle) => {
  if (!handle) return null;
  const h = String(handle).trim().replace(/^@/, "");
  if (!h) return null;
  switch (network) {
    case "instagram": return `https://instagram.com/${encodeURIComponent(h)}`;
    case "tiktok":    return `https://tiktok.com/@${encodeURIComponent(h)}`;
    case "facebook":  return `https://facebook.com/${encodeURIComponent(h)}`;
    case "snapchat":  return `https://snapchat.com/add/${encodeURIComponent(h)}`;
    case "whatsapp":  return `https://wa.me/${h.replace(/[^0-9+]/g, "")}`;
    default:          return null;
  }
};

/* Returns the active vacation (covering today), or null. */
const cbActivePause = (bs) => {
  const vacs = (bs && bs.vacations) || [];
  const today = new Date();
  const ymd = `${today.getFullYear()}-${String(today.getMonth()+1).padStart(2,"0")}-${String(today.getDate()).padStart(2,"0")}`;
  return vacs.find(v => ymd >= v.start && ymd <= v.end) || null;
};

/* Animated number, eases from 0 to target over duration ms, then stays. */
const useAnimatedNumber = (target, duration = 700) => {
  const [val, setVal] = React.useState(0);
  React.useEffect(() => {
    const start = performance.now();
    const from = 0;
    let raf;
    const tick = (t) => {
      const k = Math.min(1, (t - start) / duration);
      const eased = 1 - Math.pow(1 - k, 3); // easeOutCubic
      setVal(from + (target - from) * eased);
      if (k < 1) raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [target, duration]);
  return val;
};

/* ================================================================
   APP SHELL
================================================================ */
const AppSplash = () => {
  const firstName = (() => {
    try {
      const u = window.cbAuth && window.cbAuth.getCurrentUser && window.cbAuth.getCurrentUser();
      return u ? (u.ownerName || "").split(" ")[0] : "";
    } catch { return ""; }
  })();
  const hour = new Date().getHours();
  const greeting = hour < 6 ? "Bonne nuit" : hour < 12 ? "Bonjour" : hour < 18 ? "Bon après-midi" : "Bonsoir";

  return (
    <div style={{
      position: "fixed", inset: 0, zIndex: 200,
      background: "#FFFFFF",
      display: "flex", alignItems: "center", justifyContent: "center",
      flexDirection: "column", gap: 12,
    }}>
      <style>{`
        @keyframes cbSplashLogoIn {
          0%   { opacity: 0; transform: scale(0.85); }
          100% { opacity: 1; transform: scale(1); }
        }
        @keyframes cbSplashFadeUp {
          from { opacity: 0; transform: translateY(6px); }
          to   { opacity: 1; transform: translateY(0); }
        }
        @keyframes cbSplashProgress {
          from { transform: scaleX(0); }
          to   { transform: scaleX(1); }
        }
      `}</style>
      <div style={{ animation: "cbSplashLogoIn 0.4s cubic-bezier(0.22, 1, 0.36, 1)" }}>
        <svg width="44" height="44" viewBox="0 0 32 32" style={{ display: "block" }}>
          <rect x="0" y="0" width="32" height="32" rx="8.5" fill="#3C2FBD"/>
          <path d="M11.5 20.5 L20.5 11.5 M13.5 11.5 L20.5 11.5 L20.5 18.5"
            stroke="white" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" fill="none"/>
        </svg>
      </div>
      {firstName ? (
        <div style={{
          fontFamily: "var(--ff-display)", fontSize: 17, fontWeight: 560,
          letterSpacing: "-0.015em", color: "var(--ink)",
          animation: "cbSplashFadeUp 0.4s ease 0.1s both",
        }}>
          {greeting}, {firstName}
        </div>
      ) : (
        <div style={{
          fontFamily: "var(--ff-display)", fontSize: 16, fontWeight: 560,
          letterSpacing: "-0.015em", color: "var(--ink)",
          animation: "cbSplashFadeUp 0.4s ease 0.1s both",
        }}>
          ClientBase
        </div>
      )}
      <div style={{
        width: 120, height: 2, borderRadius: 999,
        background: "color-mix(in oklab, var(--accent) 15%, transparent)",
        overflow: "hidden",
        animation: "cbSplashFadeUp 0.4s ease 0.2s both",
      }}>
        <div style={{
          width: "100%", height: "100%",
          background: "var(--accent)",
          transformOrigin: "left center",
          animation: "cbSplashProgress 0.8s cubic-bezier(0.22, 1, 0.36, 1) 0.15s both",
        }}/>
      </div>
    </div>
  );
};

/* Modal pop-up : à chaque entrée dans l'app pro avec un email NON vérifié,
   on affiche une modale qui rappelle de cliquer sur le lien de
   confirmation reçu par mail. Plus visible qu'une bannière silencieuse,
   surtout pour les comptes anciens créés avant la vérif obligatoire.
   Dismiss possible (cooldown 24 h en localStorage) pour ne pas harceler. */
const EmailVerifyModal = () => {
  const user = useCurrentUser();
  const [busy, setBusy] = React.useState(false);
  const [sent, setSent] = React.useState(false);
  const [open, setOpen] = React.useState(false);

  // Décide à l'ouverture si on doit afficher la modale.
  React.useEffect(() => {
    if (!user || user.emailVerified) return;
    if (!user.email) return;
    // Cooldown 24 h après dismiss explicite — sinon la modale s'affiche
    // à chaque navigation, harcèlement.
    let dismissedAt = 0;
    try { dismissedAt = parseInt(localStorage.getItem("cb_email_verify_dismissed_" + user.email) || "0", 10); } catch {}
    const COOLDOWN_MS = 24 * 60 * 60 * 1000;
    if (dismissedAt && Date.now() - dismissedAt < COOLDOWN_MS) return;
    // Petit délai pour laisser l'app finir son boot avant de bloquer
    const t = setTimeout(() => setOpen(true), 700);
    return () => clearTimeout(t);
  }, [user]);

  const dismiss = () => {
    setOpen(false);
    try {
      if (user && user.email) {
        localStorage.setItem("cb_email_verify_dismissed_" + user.email, String(Date.now()));
      }
    } catch {}
  };

  const resend = async () => {
    setBusy(true);
    const res = await cbAuth.resendVerification();
    setBusy(false);
    if (res && res.error) {
      showToast(res.error, "warn");
      return;
    }
    setSent(true);
    showToast("Lien de vérification renvoyé ✉️");
  };

  if (!open || !user || user.emailVerified) return null;

  return (
    <div onClick={dismiss} style={{
      position: "fixed", inset: 0, zIndex: 9999,
      background: "rgba(15,18,30,0.55)",
      backdropFilter: "blur(6px)", WebkitBackdropFilter: "blur(6px)",
      display: "flex", alignItems: "center", justifyContent: "center",
      padding: 20,
      animation: "cbEvBackdropIn .22s ease-out",
    }}>
      <div onClick={e => e.stopPropagation()} style={{
        background: "var(--surface)", borderRadius: 20,
        maxWidth: 480, width: "100%",
        padding: 32,
        boxShadow: "0 40px 80px -20px rgba(0,0,0,0.5)",
        animation: "cbEvModalIn .3s cubic-bezier(.22,1,.36,1)",
        textAlign: "center",
      }}>
        <div style={{
          width: 64, height: 64, borderRadius: 18, margin: "0 auto 16px",
          background: "linear-gradient(135deg, var(--warn) 0%, oklch(58% 0.17 35) 100%)",
          color: "white",
          display: "flex", alignItems: "center", justifyContent: "center",
          fontSize: 30,
          boxShadow: "0 12px 28px -10px var(--warn)",
          animation: "cbEvIconBounce 1.6s ease-in-out infinite",
        }}>✉️</div>
        <h2 style={{
          fontFamily: "var(--ff-display)", fontSize: 22, fontWeight: 600,
          letterSpacing: "-0.02em", margin: "0 0 8px",
        }}>
          Vérifiez votre email
        </h2>
        <p style={{
          fontSize: 14, color: "var(--ink-2)", lineHeight: 1.55,
          margin: "0 0 18px",
        }}>
          Cliquez sur le lien envoyé à <strong style={{ color: "var(--ink)" }}>{user.email}</strong> pour
          activer la synchronisation cloud, le badge ✓ Vérifié sur votre page de RDV publique,
          et sécuriser votre compte.
        </p>
        <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
          <button onClick={resend} disabled={busy || sent}
            className="btn btn-accent btn-lg"
            style={{
              width: "100%",
              opacity: busy ? 0.6 : 1,
              cursor: (busy || sent) ? "default" : "pointer",
            }}>
            {sent ? "✓ Lien renvoyé !" : busy ? "Envoi…" : "Renvoyer le lien"}
          </button>
          <button onClick={dismiss}
            style={{
              width: "100%", padding: "11px 14px",
              background: "transparent", border: "1px solid var(--line)",
              borderRadius: 999, fontSize: 13.5, fontWeight: 540,
              color: "var(--ink-3)", cursor: "pointer", fontFamily: "inherit",
            }}>
            Plus tard
          </button>
        </div>
        <p style={{
          fontSize: 11.5, color: "var(--ink-4)", lineHeight: 1.5,
          margin: "16px 0 0",
        }}>
          Pas reçu ? Vérifiez vos spams. L'app fonctionne en attendant.
        </p>
      </div>
      <style>{`
        @keyframes cbEvBackdropIn { from { opacity: 0; } to { opacity: 1; } }
        @keyframes cbEvModalIn {
          from { opacity: 0; transform: scale(0.92) translateY(10px); }
          to   { opacity: 1; transform: scale(1) translateY(0); }
        }
        @keyframes cbEvIconBounce {
          0%, 100% { transform: translateY(0) rotate(0); }
          50%      { transform: translateY(-3px) rotate(-3deg); }
        }
      `}</style>
    </div>
  );
};

/* Bannière persistante en haut de l'espace pro tant que l'email n'est pas
   vérifié. Non bloquante, l'utilisateur peut continuer à utiliser l'app
   et cliquer le lien email plus tard. */
const UnverifiedBanner = () => {
  const user = useCurrentUser();
  const [busy, setBusy] = React.useState(false);
  const [sent, setSent] = React.useState(false);
  const [dismissed, setDismissed] = React.useState(false);

  if (!user) return null;
  if (user.emailVerified) return null;     // session cloud déjà vérifiée
  if (!user.unverified && !user.email) return null;
  if (dismissed) return null;

  const resend = async () => {
    setBusy(true);
    const res = await cbAuth.resendVerification();
    setBusy(false);
    if (res && res.error) {
      showToast(res.error, "warn");
      return;
    }
    setSent(true);
    showToast("Lien de vérification renvoyé ✉️");
  };

  return (
    <div style={{
      padding: "10px 16px",
      background: "var(--warn-soft)",
      borderBottom: "1px solid var(--warn-soft-2)",
      color: "var(--warn-ink)",
      display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap",
      fontSize: 13.5,
    }}>
      <span style={{
        width: 22, height: 22, borderRadius: 50, flexShrink: 0,
        background: "var(--warn)", color: "white",
        display: "inline-flex", alignItems: "center", justifyContent: "center",
        fontWeight: 700, fontSize: 13,
      }}>!</span>
      <div style={{ flex: 1, minWidth: 200, lineHeight: 1.4 }}>
        <strong>Email non vérifié.</strong> Cliquez sur le lien envoyé à <strong>{user.email}</strong> pour
        activer la synchronisation cloud. Vous pouvez le faire plus tard, l'app fonctionne déjà.
      </div>
      <button onClick={resend} disabled={busy || sent} style={{
        padding: "6px 12px", fontSize: 12.5, fontWeight: 580,
        background: sent ? "var(--sage)" : "var(--warn-ink)",
        color: "white", border: "none", borderRadius: 999,
        cursor: (busy || sent) ? "default" : "pointer", fontFamily: "inherit",
        opacity: busy ? 0.6 : 1, whiteSpace: "nowrap",
      }}>
        {sent ? "✓ Renvoyé" : busy ? "Envoi…" : "Renvoyer le lien"}
      </button>
      <button onClick={() => setDismissed(true)} style={{
        padding: "6px 10px", fontSize: 12.5, fontWeight: 500,
        background: "transparent", color: "var(--warn-ink)", border: "1px solid var(--warn-soft-2)",
        borderRadius: 999, cursor: "pointer", fontFamily: "inherit", whiteSpace: "nowrap",
      }}>
        Plus tard
      </button>
    </div>
  );
};

const AppShell = (props) => {
  const [splashDone, setSplashDone] = React.useState(() => sessionStorage.getItem("cb_splash_shown") === "1");
  React.useEffect(() => {
    if (splashDone) return;
    const t = setTimeout(() => {
      setSplashDone(true);
      sessionStorage.setItem("cb_splash_shown", "1");
    }, 900);
    return () => clearTimeout(t);
  }, [splashDone]);
  // Hardening sécurité — au montage, on valide que la session Supabase est
  // toujours active. Si on a un user "cloud" (avec id) mais que Supabase ne
  // retourne plus de session (token expiré, révoqué, etc.), on logout
  // proprement et on retombe sur la page d'accueil au lieu de laisser
  // l'utilisateur dans un état semi-cassé où les queries renvoient 401.
  // Mode démo exclu — il n'a volontairement pas de session Supabase.
  React.useEffect(() => {
    if (!window.cbSupabase) return;
    const isDemo = localStorage.getItem("cb_demo_mode") === "1";
    if (isDemo) return;
    let cancelled = false;
    window.cbSupabase.auth.getSession().then(({ data }) => {
      if (cancelled) return;
      const u = window.cbAuth && window.cbAuth.getCurrentUser && window.cbAuth.getCurrentUser();
      // user.id présent = c'était un compte Supabase. Session absente = expirée.
      if (u && u.id && !data.session) {
        try { window.cbAuth.logout(); } catch {}
      }
    }).catch(() => {});
    return () => { cancelled = true; };
  }, []);
  if (splashDone) return <AppShellInner {...props}/>;
  // Démo pro : splash dédié (badge DÉMO, indigo). Sinon splash habituel.
  const isDemo = (() => { try { return localStorage.getItem("cb_demo_mode") === "1"; } catch { return false; } })();
  if (isDemo && window.CbDemoSplash) return React.createElement(window.CbDemoSplash, { variant: "pro" });
  return <AppSplash/>;
};

// Modules accessibles via URL, limite l'écriture du hash aux valeurs connues
// pour ne pas polluer l'historique avec des hashes parasites (#anchors d'une
// modale, etc.).
// Modules réellement rendus par AppShellInner (cf. switch ~ ligne 1158).
// Le hash de l'URL n'est honoré que pour ces valeurs, évite la pollution
// d'historique par des #anchors parasites (modales, scroll, etc.).
const APP_MODULE_IDS = new Set([
  "accueil", "agenda", "services", "bookings", "page",
  "clients", "fidelite", "giftcards", "relances", "avis", "stats", "factures",
  "stock", "acomptes", "settings", "subscription",
  "ambassadrice", "cofondatrice", "admin",
]);
const _moduleFromHash = () => {
  try {
    const h = (window.location.hash || "").replace(/^#/, "").trim();
    if (h && APP_MODULE_IDS.has(h)) return h;
  } catch {}
  return null;
};

const AppShellInner = ({ go }) => {
  const [data, setData, resetData] = useAppData();
  // Module initial, priorité :
  //   1. hash de l'URL (`/app#agenda`), vrai refresh-safe
  //   2. cible d'une démo qui vient de nous déposer ici
  //   3. dernière préférence utilisateur
  //   4. "accueil"
  const [module, setModule] = React.useState(() => {
    const fromHash = _moduleFromHash();
    if (fromHash) return fromHash;
    try {
      const target = localStorage.getItem("cb_demo_target");
      if (target) {
        localStorage.removeItem("cb_demo_target"); // ne s'applique qu'une fois
        return target;
      }
      const prefs = JSON.parse(localStorage.getItem("cb_display_prefs") || "null");
      if (prefs && prefs.startModule) return prefs.startModule;
    } catch {}
    return "accueil";
  });
  const [modal, setModal] = React.useState(null);

  // Reflète le module courant dans l'URL pour que F5/partage marchent.
  // On utilise replaceState pour ne pas créer une nouvelle entrée d'historique
  // à chaque clic dans la nav latérale (sinon le bouton Back fait toute la
  // chronologie au lieu de quitter le dashboard).
  React.useEffect(() => {
    if (!APP_MODULE_IDS.has(module)) return;
    try {
      const wanted = `#${module}`;
      if (window.location.hash !== wanted) {
        const url = window.location.pathname + window.location.search + wanted;
        window.history.replaceState({ ...(window.history.state || {}), cbModule: module }, "", url);
      }
    } catch {}
  }, [module]);

  // Bouton retour/avant : si l'utilisateur navigue dans son historique
  // intra-dashboard, on s'aligne sur le hash demandé.
  React.useEffect(() => {
    const onPop = () => {
      const m = _moduleFromHash();
      if (m && m !== module) setModule(m);
    };
    window.addEventListener("hashchange", onPop);
    return () => window.removeEventListener("hashchange", onPop);
  }, [module]);

  // === Notifications in-app : toast quand une nouvelle notif non-lue arrive ===
  const _notifSeenIds = React.useRef(null);
  React.useEffect(() => {
    const notifs = data.notifications || [];
    if (_notifSeenIds.current === null) {
      _notifSeenIds.current = new Set(notifs.map(n => n.id));
      return;
    }
    const seen = _notifSeenIds.current;
    const fresh = notifs.filter(n => !seen.has(n.id) && !n.read);
    fresh.forEach(n => {
      seen.add(n.id);
      if (typeof window.showNotifToast === "function") {
        window.showNotifToast(n);
      }
    });
    notifs.forEach(n => seen.add(n.id));
  }, [data.notifications]);

  const setMod = (m) => {
    setModule(m);
    try { localStorage.removeItem("cb_app_mod"); } catch {}
  };

  // Applique les préférences d'affichage (densité, animations) au boot
  React.useEffect(() => {
    try {
      const prefs = JSON.parse(localStorage.getItem("cb_display_prefs") || "null");
      if (prefs) {
        document.documentElement.setAttribute("data-cb-density", prefs.density || "comfortable");
        document.documentElement.setAttribute("data-cb-anim", prefs.animations || "full");
      }
    } catch {}
  }, []);
  const openModal = (type, ctx = {}) => setModal({ type, ctx });
  const closeModal = () => setModal(null);

  /* ---------- mutations ---------- */
  const newId = () => (window.crypto && window.crypto.randomUUID) ? window.crypto.randomUUID() : "local_" + uid();
  const cloud = (label, fn) => {
    if (!window.cbCloud || !window.cbCloud.isActive()) return;
    Promise.resolve().then(fn).catch(err => {
      console.error("[cloud]", label, err);
      showToast("Échec synchro, " + label, "warn");
    });
  };
  // Variante avec 1 réessai automatique : pour les saves « Ma page » qui
  // peuvent échouer de façon transitoire (réseau, payload lourd avec images).
  // On ne montre l'erreur qu'après l'échec du réessai.
  const cloudRetry = (label, fn) => {
    if (!window.cbCloud || !window.cbCloud.isActive()) return;
    Promise.resolve().then(fn).catch(() =>
      new Promise(r => setTimeout(r, 900)).then(fn)
    ).catch(err => {
      console.error("[cloud]", label, err);
      showToast("Échec synchro, " + label + " — réessayez dans un instant (vos modifs restent enregistrées localement).", "warn");
    });
  };
  // Sauvegarde « Ma page » / booking_settings débattue : on coalesce les
  // édits rapides (frappe, toggles) en un seul upsert après une courte pause.
  const _bsTimer = React.useRef(null);
  const _bsPending = React.useRef(null);
  const saveBookingSettingsDebounced = (rest) => {
    _bsPending.current = { ...(_bsPending.current || {}), ...rest };
    if (_bsTimer.current) clearTimeout(_bsTimer.current);
    _bsTimer.current = setTimeout(() => {
      const payload = _bsPending.current;
      _bsPending.current = null;
      if (payload && Object.keys(payload).length) {
        cloudRetry("réservation", () => window.cbCloud.updateBookingSettings(payload));
      }
    }, 700);
  };

  const actions = React.useMemo(() => ({
    addClient: (c) => {
      const id = newId();
      const client = {
        id, visits: 0, spent: 0, fid: 0, last: "—",
        tags: c.tags && c.tags.length ? c.tags : ["Nouvelle"],
        hue: (Math.random() * 360) | 0,
        notes: "",
        phone: "",
        email: "",
        ...c,
      };
      setData(d => ({ ...d, clients: [client, ...d.clients] }));
      cloud("ajout client", () => window.cbCloud.upsertClient(client, true));
      showToast(`${client.name} ajoutée`);
      return id;
    },
    updateClient: (id, patch) => {
      setData(d => ({ ...d, clients: d.clients.map(c => c.id === id ? { ...c, ...patch } : c) }));
      cloud("modif client", () => window.cbCloud.upsertClient({ id, ...patch }, false));
      showToast("Cliente modifiée");
    },
    deleteClient: (id) => {
      if (!window.confirm("Supprimer ce client ? Cette action est définitive.")) return;
      setData(d => ({ ...d, clients: d.clients.filter(c => c.id !== id) }));
      cloud("suppression client", () => window.cbCloud.removeClient(id));
      showToast("Cliente supprimée", "warn");
    },
    /* ---- Services CRUD (prestations proposées) ---- */
    addService: (s) => {
      const svc = {
        id: newId(),
        name: s.name || "Nouvelle prestation",
        price: Number(s.price) || 0,
        duration: Number(s.duration) || 1,
        description: s.description || "",
        active: s.active !== false,
      };
      setData(d => ({ ...d, services: [svc, ...(d.services || [])] }));
      cloud("ajout prestation", () => window.cbCloud.upsertService(svc, true));
      showToast(`${svc.name} ajoutée`);
      return svc.id;
    },
    updateService: (id, patch) => {
      setData(d => ({
        ...d,
        services: (d.services || []).map(s => s.id === id ? { ...s, ...patch } : s),
      }));
      cloud("modif prestation", () => window.cbCloud.upsertService({ id, ...patch }, false));
      showToast("Prestation mise à jour");
    },
    deleteService: (id) => {
      const used = (data.appointments || []).some(a => a.serviceId === id);
      const msg = used
        ? "Cette prestation est utilisée dans des RDV existants. Supprimer quand même ? Les RDV concernés garderont juste le nom."
        : "Supprimer cette prestation ?";
      if (!window.confirm(msg)) return;
      setData(d => ({ ...d, services: (d.services || []).filter(s => s.id !== id) }));
      cloud("suppression prestation", () => window.cbCloud.removeService(id));
      showToast("Prestation supprimée", "warn");
    },
    toggleServiceActive: (id) => {
      const cur = (data.services || []).find(s => s.id === id);
      const nextActive = !(cur && cur.active !== false);
      setData(d => ({
        ...d,
        services: (d.services || []).map(s => s.id === id ? { ...s, active: nextActive } : s),
      }));
      cloud("visibilité prestation", () => window.cbCloud.upsertService({ id, active: nextActive }, false));
    },
    // === Catégories de prestations ===
    addServiceCategory: (input) => {
      // Accepte une string (legacy) ou un objet { name, colorId, description }
      const payload = typeof input === "string" ? { name: input } : (input || {});
      const cat = {
        id: newId(),
        name: (payload.name || "Nouvelle catégorie").trim(),
        colorId: payload.colorId != null ? payload.colorId : null,
        description: (payload.description || "").trim(),
        order: ((data.serviceCategories || []).reduce((m, c) => Math.max(m, c.order || 0), 0)) + 1,
      };
      setData(d => ({ ...d, serviceCategories: [...(d.serviceCategories || []), cat] }));
      cloud("ajout catégorie", () => window.cbCloud.upsertServiceCategory && window.cbCloud.upsertServiceCategory(cat, true));
      showToast(`Catégorie "${cat.name}" créée`);
      return cat.id;
    },
    updateServiceCategory: (id, patch) => {
      setData(d => ({
        ...d,
        serviceCategories: (d.serviceCategories || []).map(c => c.id === id ? { ...c, ...patch } : c),
      }));
      cloud("modif catégorie", () => window.cbCloud.upsertServiceCategory && window.cbCloud.upsertServiceCategory({ id, ...patch }, false));
    },
    deleteServiceCategory: (id) => {
      const hasServices = (data.services || []).some(s => s.categoryId === id);
      if (hasServices && !window.confirm("Cette catégorie contient des prestations. Elles passeront en « sans catégorie ». Continuer ?")) return;
      setData(d => ({
        ...d,
        serviceCategories: (d.serviceCategories || []).filter(c => c.id !== id),
        services: (d.services || []).map(s => s.categoryId === id ? { ...s, categoryId: null } : s),
      }));
      cloud("suppression catégorie", () => window.cbCloud.removeServiceCategory && window.cbCloud.removeServiceCategory(id));
      showToast("Catégorie supprimée");
    },
    // Déplacer une prestation (haut/bas dans sa catégorie)
    moveService: (id, direction) => {
      setData(d => {
        const list = d.services || [];
        const target = list.find(s => s.id === id);
        if (!target) return d;
        // Liste des prestations de la même catégorie, triée par order
        const sib = list
          .filter(s => (s.categoryId || null) === (target.categoryId || null))
          .sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
        const idx = sib.findIndex(s => s.id === id);
        const newIdx = direction === "up" ? idx - 1 : idx + 1;
        if (newIdx < 0 || newIdx >= sib.length) return d;
        // Échange l'ordre avec le voisin
        const a = sib[idx], b = sib[newIdx];
        const aOrder = a.order ?? idx;
        const bOrder = b.order ?? newIdx;
        const services = list.map(s => {
          if (s.id === a.id) return { ...s, order: bOrder };
          if (s.id === b.id) return { ...s, order: aOrder };
          return s;
        });
        cloud("reorder prestation", () => {
          window.cbCloud.upsertService({ id: a.id, order: bOrder }, false);
          window.cbCloud.upsertService({ id: b.id, order: aOrder }, false);
        });
        return { ...d, services };
      });
    },
    // === Cartes cadeaux ===
    addGiftCard: (card) => {
      const id = newId();
      const code = (card.code || _generateGiftCode()).toUpperCase();
      const created = new Date().toISOString().slice(0, 10);
      const expires = card.expiresAt || _addMonthsToYmd(created, 12);
      const gc = {
        id, code,
        amount: Number(card.amount) || 0,
        recipientName: (card.recipientName || "").trim(),
        recipientEmail: (card.recipientEmail || "").trim(),
        senderName: (card.senderName || "").trim(),
        message: (card.message || "").trim(),
        createdAt: created,
        expiresAt: expires,
        used: false,
      };
      setData(d => ({ ...d, giftCards: [gc, ...(d.giftCards || [])] }));
      cloud("ajout carte cadeau", () => window.cbCloud.upsertGiftCard && window.cbCloud.upsertGiftCard(gc, true));
      showToast(`Carte ${gc.code} créée, ${gc.amount}€`);
      return id;
    },
    updateGiftCard: (id, patch) => {
      setData(d => ({
        ...d,
        giftCards: (d.giftCards || []).map(g => g.id === id ? { ...g, ...patch } : g),
      }));
      cloud("modif carte cadeau", () => window.cbCloud.upsertGiftCard && window.cbCloud.upsertGiftCard({ id, ...patch }, false));
    },
    deleteGiftCard: (id) => {
      if (!window.confirm("Supprimer cette carte cadeau ? Le code ne pourra plus être utilisé.")) return;
      setData(d => ({ ...d, giftCards: (d.giftCards || []).filter(g => g.id !== id) }));
      cloud("suppression carte cadeau", () => window.cbCloud.removeGiftCard && window.cbCloud.removeGiftCard(id));
      showToast("Carte cadeau supprimée", "warn");
    },
    markGiftCardUsed: (id) => {
      const now = new Date().toISOString().slice(0, 10);
      setData(d => ({
        ...d,
        giftCards: (d.giftCards || []).map(g => g.id === id ? { ...g, used: true, usedAt: now } : g),
      }));
      cloud("utiliser carte cadeau", () => window.cbCloud.upsertGiftCard && window.cbCloud.upsertGiftCard({ id, used: true, usedAt: now }, false));
      showToast("Carte cadeau marquée comme utilisée");
    },
    // Déplacer une catégorie (haut/bas)
    moveServiceCategory: (id, direction) => {
      setData(d => {
        const cats = (d.serviceCategories || []).slice().sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
        const idx = cats.findIndex(c => c.id === id);
        const newIdx = direction === "up" ? idx - 1 : idx + 1;
        if (newIdx < 0 || newIdx >= cats.length) return d;
        const a = cats[idx], b = cats[newIdx];
        const aOrder = a.order ?? idx;
        const bOrder = b.order ?? newIdx;
        const serviceCategories = (d.serviceCategories || []).map(c => {
          if (c.id === a.id) return { ...c, order: bOrder };
          if (c.id === b.id) return { ...c, order: aOrder };
          return c;
        });
        cloud("reorder catégorie", () => {
          window.cbCloud.upsertServiceCategory && window.cbCloud.upsertServiceCategory({ id: a.id, order: bOrder }, false);
          window.cbCloud.upsertServiceCategory && window.cbCloud.upsertServiceCategory({ id: b.id, order: aOrder }, false);
        });
        return { ...d, serviceCategories };
      });
    },
    addAppointment: (a) => {
      const ap = { id: newId(), color: "accent", ...a };
      setData(d => ({ ...d, appointments: [...d.appointments, ap] }));
      cloud("ajout RDV", () => window.cbCloud.upsertAppointment(ap, true));
      showToast("Rendez-vous ajouté");
    },
    deleteAppointment: (id) => {
      setData(d => ({ ...d, appointments: d.appointments.filter(a => a.id !== id) }));
      cloud("suppression RDV", () => window.cbCloud.removeAppointment(id));
      showToast("Rendez-vous supprimé", "warn");
    },
    markAppointmentDone: (id) => {
      const ap = data.appointments.find(a => a.id === id);
      if (!ap || ap.done) return;
      const svc = findService(data, ap.serviceId);
      const cli = findClient(data, ap.clientId);
      if (!svc || !cli) return;
      const nextNum = data.invoiceCounter + 1;
      const invoiceNumber = `F-${new Date().getFullYear()}-${String(nextNum).padStart(4, "0")}`;
      const invoiceRowId = newId();
      const today = new Date();
      const todayFR = today.toLocaleDateString("fr-FR", { day: "numeric", month: "long", year: "numeric" });
      const business = data.business || {};
      const dueDays = Number(business.paymentTermsDays || 0);
      const dueDateObj = new Date(today.getTime() + dueDays * 86400000);
      const dueDateFR = dueDateObj.toLocaleDateString("fr-FR", { day: "numeric", month: "long", year: "numeric" });
      // Date de prestation = date du RDV si calculable, sinon aujourd'hui
      const apptDate = (() => {
        try {
          if (typeof ap.day === "number" && typeof ap.h === "number") {
            const base = new Date(); base.setHours(0, 0, 0, 0);
            const cbToday = (typeof cbTodayOffset === "function") ? cbTodayOffset() : 0;
            const dayOffset = ap.day - cbToday;
            const d = new Date(base.getTime() + dayOffset * 86400000);
            return d.toLocaleDateString("fr-FR", { day: "numeric", month: "long", year: "numeric" });
          }
        } catch {}
        return todayFR;
      })();
      const invoice = {
        id: invoiceRowId,
        number: invoiceNumber,
        clientId: cli.id,
        date: todayFR,
        amount: svc.price,
        paid: true,
        designation: svc.name,
        prestationDate: apptDate,
        dueDate: dueDays > 0 ? dueDateFR : todayFR,
        vatRate: business.vatStatus === "applicable" ? Number(business.vatRate || 0) : 0,
      };
      const newFid = Math.min((cli.fid || 0) + 1, data.fideliteRules.visits);
      const newVisits = cli.visits + 1;
      const newSpent = cli.spent + svc.price;
      setData(d => ({
        ...d,
        invoiceCounter: nextNum,
        invoices: [invoice, ...d.invoices],
        appointments: d.appointments.map(x => x.id === id
          ? { ...x, done: true, invoiceId: invoiceRowId, completedAt: Date.now() }
          : x),
        clients: d.clients.map(c => c.id === cli.id ? {
          ...c, visits: newVisits, spent: newSpent, fid: newFid, last: "aujourd'hui",
        } : c),
      }));
      cloud("facture auto", async () => {
        await window.cbCloud.upsertInvoice(invoice, true);
        await window.cbCloud.upsertAppointment({ id, done: true, invoiceId: invoiceRowId, completedAt: Date.now() }, false);
        await window.cbCloud.upsertClient({ id: cli.id, visits: newVisits, spent: newSpent, fid: newFid, last: "aujourd'hui" }, false);
        await window.cbCloud.incrementInvoiceCounter(nextNum);
      });
      showToast(`Prestation facturée, ${fmtEUR(svc.price)}`, "success", {
        action: { label: "Annuler", onClick: () => actions.undoAppointmentDone(id) },
      });
    },
    undoAppointmentDone: (id) => {
      const ap = data.appointments.find(a => a.id === id);
      if (!ap || !ap.done) return;
      const svc = findService(data, ap.serviceId);
      const cli = findClient(data, ap.clientId);
      const deletedInvoiceId = ap.invoiceId;
      const newVisits = cli ? Math.max(0, cli.visits - 1) : 0;
      const newSpent = cli ? Math.max(0, cli.spent - (svc ? svc.price : 0)) : 0;
      const newFid = cli ? Math.max(0, (cli.fid || 0) - 1) : 0;
      setData(d => ({
        ...d,
        invoices: d.invoices.filter(inv => inv.id !== deletedInvoiceId),
        appointments: d.appointments.map(x => x.id === id
          ? { ...x, done: false, invoiceId: null, completedAt: null }
          : x),
        clients: cli ? d.clients.map(c => c.id === cli.id
          ? { ...c, visits: newVisits, spent: newSpent, fid: newFid }
          : c) : d.clients,
      }));
      cloud("annulation facture", async () => {
        if (deletedInvoiceId) await window.cbCloud.removeInvoice(deletedInvoiceId);
        await window.cbCloud.upsertAppointment({ id, done: false, invoiceId: null, completedAt: null }, false);
        if (cli) await window.cbCloud.upsertClient({ id: cli.id, visits: newVisits, spent: newSpent, fid: newFid }, false);
      });
      showToast("Annulé, facture supprimée", "warn");
    },
    sendMessage: (convId, text) => {
      if (!text.trim()) return;
      const now = new Date();
      const time = `${now.getHours()}:${String(now.getMinutes()).padStart(2,"0")}`;
      const msg = { id: "msg_" + uid(), from: "me", text: text.trim(), time };
      setData(d => {
        const existing = d.messages[convId] || [];
        const convos = d.conversations.some(c => c.clientId === convId)
          ? d.conversations
          : [{ clientId: convId, last: text.trim(), time, unread: 0 }, ...d.conversations];
        return {
          ...d,
          messages: { ...d.messages, [convId]: [...existing, msg] },
          conversations: convos.map(c => c.clientId === convId
            ? { ...c, last: text.trim(), time, unread: 0 }
            : c),
        };
      });
    },
    openConv: (convId) => {
      setData(d => ({
        ...d,
        activeConv: convId,
        conversations: d.conversations.map(c => c.clientId === convId ? { ...c, unread: 0 } : c),
      }));
    },
    addInvoice: (inv) => {
      const nextNum = data.invoiceCounter + 1;
      const invoiceRowId = newId();
      const invoiceNumber = `F-${new Date().getFullYear()}-${String(nextNum).padStart(4, "0")}`;
      const today = new Date();
      const todayFR = today.toLocaleDateString("fr-FR", { day: "numeric", month: "long", year: "numeric" });
      const business = data.business || {};
      const dueDays = Number(business.paymentTermsDays || 0);
      const dueDateFR = dueDays > 0
        ? new Date(today.getTime() + dueDays * 86400000)
            .toLocaleDateString("fr-FR", { day: "numeric", month: "long", year: "numeric" })
        : todayFR;
      const invoice = {
        id: invoiceRowId,
        number: invoiceNumber,
        paid: false,
        date: todayFR,
        prestationDate: inv.prestationDate || todayFR,
        dueDate: dueDateFR,
        vatRate: business.vatStatus === "applicable" ? Number(business.vatRate || 0) : 0,
        designation: "Prestation",
        ...inv,
      };
      setData(d => ({ ...d, invoiceCounter: nextNum, invoices: [invoice, ...d.invoices] }));
      cloud("création facture", async () => {
        await window.cbCloud.upsertInvoice(invoice, true);
        await window.cbCloud.incrementInvoiceCounter(nextNum);
      });
      showToast("Facture créée");
    },
    toggleInvoicePaid: (id) => {
      const current = data.invoices.find(i => i.id === id);
      const newPaid = current ? !current.paid : true;
      setData(d => ({ ...d, invoices: d.invoices.map(i => i.id === id ? { ...i, paid: newPaid } : i) }));
      cloud("statut facture", () => window.cbCloud.upsertInvoice({ id, paid: newPaid }, false));
      if (newPaid) {
        showToast("Paiement encaissé", "success", {
          action: { label: "Annuler", onClick: () => actions.toggleInvoicePaid(id) },
        });
      }
    },
    deleteInvoice: (id) => {
      // Une facture émise ne peut PAS être supprimée (obligation comptable).
      // On crée à la place un avoir (facture de montant négatif) qui annule la facture.
      const orig = data.invoices.find(i => i.id === id);
      if (!orig) return;
      if (!window.confirm(
        `Émettre un avoir d'annulation pour la facture ${orig.number} ?\n\n` +
        `Une facture émise ne peut pas être supprimée (obligation comptable). ` +
        `Un avoir sera créé pour le même montant en négatif.`
      )) return;
      const nextNum = data.invoiceCounter + 1;
      const avoirRowId = newId();
      const avoirNumber = `A-${new Date().getFullYear()}-${String(nextNum).padStart(4, "0")}`;
      const avoir = {
        id: avoirRowId,
        number: avoirNumber,
        clientId: orig.clientId,
        date: new Date().toLocaleDateString("fr-FR", { day: "numeric", month: "long", year: "numeric" }),
        amount: -Math.abs(orig.amount),
        paid: !!orig.paid,
        designation: `Avoir d'annulation, facture ${orig.number}`,
        creditOf: orig.number,
      };
      setData(d => ({ ...d, invoiceCounter: nextNum, invoices: [avoir, ...d.invoices] }));
      cloud("création avoir", async () => {
        await window.cbCloud.upsertInvoice(avoir, true);
        await window.cbCloud.incrementInvoiceCounter(nextNum);
      });
      showToast(`Avoir ${avoirNumber} créé, la facture ${orig.number} est annulée comptablement.`, "ok");
    },
    addStockItem: (item) => {
      const it = { id: newId(), qty: 0, min: 0, price: 0, ...item };
      setData(d => ({ ...d, stock: [...d.stock, it] }));
      cloud("ajout stock", () => window.cbCloud.upsertStock(it, true));
      showToast("Produit ajouté");
    },
    adjustStock: (id, delta) => {
      const cur = data.stock.find(s => s.id === id);
      const newQty = Math.max(0, (cur ? cur.qty : 0) + delta);
      setData(d => ({
        ...d,
        stock: d.stock.map(s => s.id === id ? { ...s, qty: newQty } : s),
      }));
      cloud("ajustement stock", () => window.cbCloud.upsertStock({ id, qty: newQty }, false));
    },
    setStockQty: (id, qty) => {
      const q = Math.max(0, qty);
      setData(d => ({ ...d, stock: d.stock.map(s => s.id === id ? { ...s, qty: q } : s) }));
      cloud("stock", () => window.cbCloud.upsertStock({ id, qty: q }, false));
    },
    deleteStockItem: (id) => {
      setData(d => ({ ...d, stock: d.stock.filter(s => s.id !== id) }));
      cloud("suppression stock", () => window.cbCloud.removeStock(id));
      showToast("Produit retiré", "warn");
    },
    updateStockItem: (id, patch) => {
      setData(d => ({ ...d, stock: d.stock.map(s => s.id === id ? { ...s, ...patch } : s) }));
      cloud("modif stock", () => window.cbCloud.upsertStock({ id, ...patch }, false));
      showToast("Produit mis à jour");
    },
    addPromo: (p) => {
      setData(d => ({ ...d, promos: [{ id: "pr_" + uid(), status: "Brouillon", ...p }, ...d.promos] }));
      showToast("Promotion créée");
    },
    togglePromoStatus: (id) => {
      const order = ["Brouillon", "Programmé", "Actif"];
      setData(d => ({
        ...d,
        promos: d.promos.map(p => p.id === id
          ? { ...p, status: order[(order.indexOf(p.status) + 1) % order.length] }
          : p),
      }));
    },
    deletePromo: (id) => {
      setData(d => ({ ...d, promos: d.promos.filter(p => p.id !== id) }));
      showToast("Promotion supprimée", "warn");
    },
    updateFideliteRules: (rules) => {
      setData(d => ({ ...d, fideliteRules: { ...d.fideliteRules, ...rules } }));
      cloud("règles fidélité", () => window.cbCloud.setFideliteRules(rules));
      showToast("Règles de fidélité enregistrées");
    },
    addVisit: (clientId) => {
      const cli = data.clients.find(c => c.id === clientId);
      if (!cli) return;
      const visits = cli.visits + 1;
      const fid = Math.min((cli.fid || 0) + 1, data.fideliteRules.visits);
      setData(d => ({
        ...d,
        clients: d.clients.map(c => c.id === clientId
          ? { ...c, visits, fid, last: "aujourd'hui" }
          : c),
      }));
      cloud("visite", () => window.cbCloud.upsertClient({ id: clientId, visits, fid, last: "aujourd'hui" }, false));
      showToast("Visite enregistrée");
    },
    adjustFidelite: (clientId, delta) => {
      const cli = data.clients.find(c => c.id === clientId);
      if (!cli) return;
      const fid = Math.max(0, Math.min((cli.fid || 0) + delta, data.fideliteRules.visits));
      setData(d => ({
        ...d,
        clients: d.clients.map(c => c.id === clientId ? { ...c, fid } : c),
      }));
      cloud("fidélité", () => window.cbCloud.upsertClient({ id: clientId, fid }, false));
    },
    resetFidelite: (clientId) => {
      setData(d => ({
        ...d,
        clients: d.clients.map(c => c.id === clientId ? { ...c, fid: 0 } : c),
      }));
      cloud("reset fidélité", () => window.cbCloud.upsertClient({ id: clientId, fid: 0 }, false));
      showToast("Carte remise à zéro");
    },
    updateBusiness: (patch) => {
      // Pas de toast ici : trop bruyant (chaque clic toggle / preset / hue
      // déclencherait un "Paramètres sauvegardés"). Les sauvegardes
      // explicites (bouton « Enregistrer » d'un formulaire) restent
      // toastées via leur propre saveSection.
      setData(d => ({ ...d, business: { ...d.business, ...patch } }));
      cloud("paramètres", () => window.cbCloud.updateBusiness(patch));
    },
    updateBookingSettings: (patch) => {
      // Vacations ont leur propre table : on les gère séparément
      if (patch.vacations !== undefined) {
        const prev = (data.bookingSettings && data.bookingSettings.vacations) || [];
        const next = patch.vacations;
        const added   = next.filter(v => !prev.find(p => p.id === v.id));
        const removed = prev.filter(v => !next.find(p => p.id === v.id));
        added.forEach(v => cloud("vacation+", () => window.cbCloud.addVacation(v)));
        removed.forEach(v => cloud("vacation-", () => window.cbCloud.removeVacation(v.id)));
      }
      setData(d => ({ ...d, bookingSettings: { ...(d.bookingSettings || {}), ...patch } }));
      // Les autres clés (schedule/slotDuration…) vont sur booking_settings.
      // Sauvegarde débattue + réessai : évite les « échec synchro » quand on
      // enchaîne les édits (toggles réseaux, frappe) avec un pageCustom lourd.
      const rest = { ...patch }; delete rest.vacations;
      if (Object.keys(rest).length) {
        saveBookingSettingsDebounced(rest);
      }
      // Pas de toast à chaque frappe, l'utilisateur tape lettre par lettre dans
      // les inputs de Ma page et ça spamait. Les actions « structurantes »
      // (ajouter/retirer une fermeture) ont leur propre toast en aval.
    },
    markAllNotificationsRead: () => {
      setData(d => ({
        ...d,
        notifications: (d.notifications || []).map(n => ({ ...n, read: true })),
      }));
      cloud("notifications", () => window.cbCloud.markAllNotificationsRead());
    },
    deleteNotification: (id) => {
      setData(d => ({
        ...d,
        notifications: (d.notifications || []).filter(n => n.id !== id),
      }));
      cloud("notification-", () => window.cbCloud.removeNotification(id));
    },
    reset: async () => {
      try {
        await resetData();
        showToast("Toutes les données ont été supprimées", "warn");
      } catch (e) {
        showToast("Échec de la réinitialisation : " + (e && e.message ? e.message : "erreur inconnue"), "warn");
      }
    },
    deleteAccount: async () => {
      try {
        if (window.cbCloud && window.cbCloud.isActive()) {
          await window.cbCloud.deleteMyAccount();
        }
        // Le delete cascade côté Supabase invalide la session côté serveur,
        // mais on force aussi un signOut local + on vide le localStorage.
        try { await cbAuth.logout(); } catch {}
        try {
          localStorage.removeItem("cb_data_v1");
          localStorage.removeItem("cb_data_v1_cloud");
        } catch {}
        // Hard reload pour repartir de zéro côté UI
        window.location.href = "/";
      } catch (e) {
        showToast("Échec de la suppression : " + (e && e.message ? e.message : "erreur inconnue"), "warn");
      }
    },
    /* Force un re-fetch de TOUT depuis Supabase et écrase le state local.
       Utile dans Ma page si l'aperçu semble désynchronisé avec ce que voit
       le visiteur sur la vraie page publique. */
    refreshFromCloud: async () => {
      if (!window.cbCloud || !window.cbCloud.isActive()) {
        showToast("Connexion Supabase indisponible", "warn");
        return;
      }
      try {
        const cloud = await window.cbCloud.loadAll();
        if (cloud) {
          setData(prev => ({ ...prev, ...cloud }));
          showToast("Synchronisé avec la page publique");
        } else {
          showToast("Aucune donnée renvoyée par le serveur", "warn");
        }
      } catch (e) {
        showToast("Échec de la synchronisation : " + (e && e.message ? e.message : "erreur"), "warn");
      }
    },
  }), [data, setData, resetData]);

  return (
    <div style={{
      height: "100vh",
      display: "grid",
      gridTemplateColumns: "288px 1fr",
      background: "var(--bg)",
    }} className="app-grid">
      <MagicSidebar module={module} setMod={setMod} data={data}/>
      <main style={{ overflow: "auto", minWidth: 0 }} className="app-main">
        <DemoBanner/>
        <EmailVerifyModal/>
        <AppTopBar module={module} openModal={openModal} data={data} actions={actions} go={go} setMod={setMod}/>
        <div className="app-content" style={{ padding: "28px 36px" }}>
          <div key={module} className="cb-page-anim" style={{
            animation: "cbPageIn .35s cubic-bezier(.22,1,.36,1)",
          }}>
          {module === "accueil"  && <AppHome data={data} actions={actions} openModal={openModal} setMod={setMod}/>}
          {module === "agenda"   && <AppAgenda data={data} actions={actions} openModal={openModal}/>}
          {module === "services" && <AppServices data={data} actions={actions}/>}
          {module === "bookings" && <AppBookings data={data} actions={actions} setMod={setMod}/>}
          {module === "page"     && <AppPage data={data} actions={actions}/>}
          {module === "clients"  && <AppClients data={data} actions={actions} openModal={openModal}/>}
          {module === "fidelite"  && <AppFidelite data={data} actions={actions} openModal={openModal}/>}
          {module === "giftcards" && <AppGiftCards data={data} actions={actions}/>}
          {module === "relances"   && <MktRelances data={data}/>}
          {module === "avis"       && <MktReviews data={data}/>}
          {module === "stats"    && <AppStats data={data}/>}
          {module === "factures" && <AppFactures data={data} actions={actions} openModal={openModal} setMod={setMod}/>}
          {module === "stock"    && <AppStock data={data} actions={actions} openModal={openModal} setMod={setMod}/>}
          {module === "acomptes" && <AppAcomptes data={data} actions={actions} openModal={openModal} setMod={setMod}/>}
          {module === "settings"     && <AppSettings data={data} actions={actions} go={go}/>}
          {module === "subscription" && <AppSubscription data={data} go={go} setMod={setMod}/>}
          {module === "ambassadrice" && <AppAmbassadrice data={data}/>}
          {module === "cofondatrice" && <AppCofondatrice data={data}/>}
          {module === "admin"        && <AppAdminPanel data={data}/>}
          </div>
          <style>{`
            @keyframes cbPageIn {
              0%   { opacity: 0; transform: translateY(14px) scale(0.985); filter: blur(4px); }
              60%  { filter: blur(0); }
              100% { opacity: 1; transform: translateY(0) scale(1); filter: blur(0); }
            }
            .cb-page-anim { animation: cbPageIn .42s cubic-bezier(.22,1,.36,1); }
            .cb-page-anim > * { animation: cbPageChildIn .55s cubic-bezier(.22,1,.36,1) both; }
            @keyframes cbPageChildIn {
              from { opacity: 0; transform: translateY(10px); }
              to   { opacity: 1; transform: translateY(0); }
            }
            /* Click feedback global : tous les boutons cliquables se "tapent" légèrement */
            .cb-press-feedback {
              transition: transform .12s cubic-bezier(.4,1.6,.5,1),
                          background .2s ease, color .2s ease, box-shadow .2s ease !important;
            }
            .cb-press-feedback:active {
              transform: scale(0.96) !important;
            }
            /* Préférence "Animations réduites" : coupe les transitions de page */
            [data-cb-anim="reduced"] .cb-page-anim,
            [data-cb-anim="reduced"] .cb-page-anim > * {
              animation: none !important;
            }
          `}</style>
        </div>
      </main>

      {/* Modals */}
      <ClientFormModal modal={modal} onClose={closeModal} data={data} actions={actions}/>
      <ClientDetailModal modal={modal} onClose={closeModal} data={data} actions={actions} openModal={openModal}/>
      <AppointmentFormModal modal={modal} onClose={closeModal} data={data} actions={actions}/>
      <InvoiceFormModal modal={modal} onClose={closeModal} data={data} actions={actions}/>
      <StockFormModal modal={modal} onClose={closeModal} actions={actions}/>
      <PromoFormModal modal={modal} onClose={closeModal} actions={actions}/>
      <FideliteRulesModal modal={modal} onClose={closeModal} data={data} actions={actions}/>

      <Toasts/>
      <QuestWidget data={data} setMod={setMod}/>
      <DemoWelcomeModal setMod={setMod}/>

      {/* Dock flottant universel, mobile + desktop */}
      <AppDock module={module} setMod={setMod}/>
    </div>
  );
};

/* ============================================================
   MagicSidebar, desktop only, magic indicator qui glisse entre les modules
   - 230px de large, labels + icônes
   - En-tête : avatar + nom + ⚙️
   - Catégories : icône + label en petit caps
   - Modules : icône tone + label
   - Pill colorée "magique" qui SLIDE entre les modules actifs (CSS transition)
   - Hover : background subtle + icône tone-soft
============================================================ */
const MagicSidebar = ({ module, setMod, data }) => {
  const biz = data.business || {};
  const palettes = useGroupTones();
  const itemRefs = React.useRef({});
  const containerRef = React.useRef(null);
  const [indicator, setIndicator] = React.useState({ top: 0, height: 0, color: "var(--accent)", visible: false });

  // Recalcule la position de l'indicateur quand le module change
  const updateIndicator = React.useCallback(() => {
    const el = itemRefs.current[module];
    const container = containerRef.current;
    if (!el || !container) return;
    const containerRect = container.getBoundingClientRect();
    const elRect = el.getBoundingClientRect();
    const tone = getModuleTone(module, palettes);
    setIndicator({
      top: elRect.top - containerRect.top + container.scrollTop,
      height: elRect.height,
      color: tone.solid,
      bg: `color-mix(in oklab, ${tone.solid} 16%, var(--surface))`,
      ink: tone.ink,
      visible: true,
    });
  }, [module, palettes]);

  React.useEffect(() => {
    // Update au mount + à chaque changement de module
    updateIndicator();
    // Update aussi au resize
    window.addEventListener("resize", updateIndicator);
    return () => window.removeEventListener("resize", updateIndicator);
  }, [updateIndicator]);

  return (
    <aside className="app-sidebar" style={{
      background: "var(--surface)",
      borderRight: "1px solid var(--line)",
      padding: "22px 14px 28px",
      display: "flex", flexDirection: "column", gap: 16,
      overflow: "hidden auto",
      position: "relative",
    }}>
      {/* === Header workspace === */}
      <div style={{
        padding: "4px 10px 20px",
        borderBottom: "1px solid var(--line)",
        display: "flex", alignItems: "center", gap: 12,
      }}>
        <ProAvatar
          url={biz.avatar}
          initials={(biz.name || "CB").split(" ").map(x => x[0]).slice(0,2).join("").toUpperCase()}
          hue={biz.hue} size={38}
        />
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{
            fontFamily: "var(--ff-display)", fontSize: 15.5, fontWeight: 620,
            color: "var(--ink)", letterSpacing: "-0.018em",
            display: "flex", alignItems: "center", gap: 5,
          }}>
            <span style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
              {biz.name || "Votre activité"}
            </span>
            {(() => {
              const u = window.cbAuth && window.cbAuth.getCurrentUser && window.cbAuth.getCurrentUser();
              if (!u) return null;
              return u.emailVerified ? <VerifiedBadge size={14}/> : <UnverifiedBadge size={14}/>;
            })()}
          </div>
          <div style={{
            fontSize: 12, color: "var(--ink-4)", marginTop: 1,
            overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
          }}>
            {biz.owner || "ClientBase"}
          </div>
        </div>
        <button onClick={() => setMod("settings")}
          aria-label="Paramètres" title="Paramètres"
          style={{
            width: 36, height: 36, borderRadius: 9, flexShrink: 0,
            background: module === "settings" ? "var(--ink)" : "transparent",
            color: module === "settings" ? "white" : "var(--ink-4)",
            border: module === "settings" ? "none" : "1px solid var(--line)",
            cursor: "pointer", padding: 0,
            display: "flex", alignItems: "center", justifyContent: "center",
            fontFamily: "inherit",
            transition: "background .15s, color .15s, border-color .15s",
          }}>
          <Icon name="settings" size={15}/>
        </button>
      </div>

      {/* === Magic indicator : pill colorée qui slide entre les modules actifs === */}
      <div ref={containerRef} style={{ position: "relative", flex: 1 }}>
        <div aria-hidden style={{
          position: "absolute", left: 0, right: 0,
          top: indicator.top, height: indicator.height,
          background: indicator.bg || "transparent",
          borderRadius: 11,
          opacity: indicator.visible ? 1 : 0,
          transition: "top .35s cubic-bezier(.55,1.4,.35,1), height .25s ease, background .3s ease",
          pointerEvents: "none",
        }}/>

        {/* === Groupes — chaque catégorie a un petit titre sobre avec un
            point tonal coloré aligné à gauche. Pas de uppercase agressif,
            pas de séparateur lourd, pas de cliquable. Le point coloré
            rappelle la palette du groupe. */}
        <div style={{ display: "flex", flexDirection: "column" }}>
          {APP_MODULE_GROUPS.map((group, gi) => {
            const groupTone = palettes[group.id];
            return (
              <React.Fragment key={group.label}>
                <div aria-hidden style={{
                  display: "flex", alignItems: "center", gap: 8,
                  padding: gi === 0 ? "4px 11px 8px" : "18px 11px 8px",
                  userSelect: "none",
                }}>
                  <span style={{
                    fontSize: 11, fontWeight: 660,
                    color: "var(--ink-4)",
                    letterSpacing: "0.08em",
                    textTransform: "uppercase",
                  }}>
                    {group.label}
                  </span>
                </div>
                <div style={{ display: "flex", flexDirection: "column", gap: 1 }}>
                  {group.ids.map(id => {
                    const m = APP_MODULES.find(x => x.id === id);
                    if (!m) return null;
                    const isActive = module === m.id;
                    return (
                      <MagicItem key={m.id}
                        m={m} tone={groupTone} active={isActive}
                        ref={el => { if (el) itemRefs.current[m.id] = el; else delete itemRefs.current[m.id]; }}
                        onClick={() => setMod(m.id)}
                      />
                    );
                  })}
                </div>
              </React.Fragment>
            );
          })}

          {/* Onglet de RÔLE (Ambassadrice / Co-fondatrice / Admin), en plus
              du compte pro, affiché seulement si le profil le justifie. */}
          {(() => {
            const role = window.cbCurrentRole ? window.cbCurrentRole() : "pro";
            if (role === "pro") return null;
            const m = APP_MODULES.find(x => x.id === role);
            if (!m) return null;
            const tone = CB_GROUP_PALETTES.ambre;
            return (
              <React.Fragment>
                <div aria-hidden style={{ display: "flex", alignItems: "center", gap: 8, padding: "18px 11px 8px", userSelect: "none" }}>
                  <span style={{ fontSize: 11, fontWeight: 660, color: "var(--ink-4)", letterSpacing: "0.08em", textTransform: "uppercase" }}>Mon rôle</span>
                </div>
                <div style={{ display: "flex", flexDirection: "column", gap: 1 }}>
                  <MagicItem m={m} tone={tone} active={module === m.id}
                    ref={el => { if (el) itemRefs.current[m.id] = el; else delete itemRefs.current[m.id]; }}
                    onClick={() => setMod(m.id)}/>
                </div>
              </React.Fragment>
            );
          })()}
        </div>
      </div>
    </aside>
  );
};

const MagicItem = React.forwardRef(({ m, tone, active, onClick }, ref) => {
  const [hover, setHover] = React.useState(false);
  const [pressed, setPressed] = React.useState(false);
  return (
    <button ref={ref} onClick={onClick}
      onMouseEnter={() => setHover(true)}
      onMouseLeave={() => { setHover(false); setPressed(false); }}
      onMouseDown={() => setPressed(true)}
      onMouseUp={() => setPressed(false)}
      style={{
        display: "flex", alignItems: "center", gap: 11,
        padding: "9px 11px", border: "none", width: "100%",
        borderRadius: 9, cursor: "pointer",
        fontSize: 14, fontWeight: active ? 620 : 520,
        background: active ? "transparent" : (hover ? "var(--bg-alt)" : "transparent"),
        color: active ? tone.ink : (hover ? "var(--ink)" : "var(--ink-3)"),
        textAlign: "left", fontFamily: "inherit",
        transition: "color .2s ease, background .15s ease, transform .14s cubic-bezier(.22,1,.36,1)",
        position: "relative", zIndex: 1,
        letterSpacing: "-0.005em",
        transform: pressed ? "scale(0.97)" : "scale(1)",
      }}>
      <span style={{
        display: "flex", alignItems: "center", justifyContent: "center",
        flexShrink: 0, color: "inherit",
        transition: "transform .2s cubic-bezier(.22,1,.36,1)",
        transform: hover && !active ? "scale(1.08)" : "scale(1)",
      }}>
        <Icon name={m.icon} size={18} stroke={active ? 2 : 1.8}/>
      </span>
      <span style={{ flex: 1 }}>{m.label}</span>
    </button>
  );
});

/* ============================================================
   RailSidebar, desktop only, rail vertical compact (style Slack/Discord)
   - 76px de large, juste icônes + indicateur de groupe
   - Tooltip au hover (à droite de l'icône)
   - Icône active : tone-color filled + glow ring
   - Séparateurs colorés entre groupes
============================================================ */
const RailSidebar = ({ module, setMod, data }) => {
  const biz = data.business || {};
  return (
    <aside className="app-sidebar" style={{
      background: "var(--surface)",
      borderRight: "1px solid var(--line)",
      padding: "14px 0 18px",
      display: "flex", flexDirection: "column", alignItems: "center", gap: 4,
      overflow: "hidden auto",
    }}>
      {/* Avatar workspace en haut */}
      <div title={biz.name || "Votre activité"} style={{
        marginBottom: 12,
        position: "relative",
      }}>
        <ProAvatar
          url={biz.avatar}
          initials={(biz.name || "CB").split(" ").map(x => x[0]).slice(0,2).join("").toUpperCase()}
          hue={biz.hue} size={44}
        />
        {(() => {
          const u = window.cbAuth && window.cbAuth.getCurrentUser && window.cbAuth.getCurrentUser();
          if (!u || !u.emailVerified) return null;
          return (
            <span style={{
              position: "absolute", right: -3, bottom: -3,
              background: "var(--surface)", borderRadius: "50%", padding: 1,
              display: "flex",
            }}>
              <VerifiedBadge size={14}/>
            </span>
          );
        })()}
      </div>

      {/* Modules par groupe avec séparateurs colorés */}
      <div style={{ flex: 1, display: "flex", flexDirection: "column", alignItems: "center", gap: 4, width: "100%" }}>
        {APP_MODULE_GROUPS.map((group, gi) => (
          <React.Fragment key={group.label}>
            {gi > 0 && (
              <div style={{
                width: 28, height: 1, margin: "8px 0 4px",
                background: `color-mix(in oklab, ${group.tone.dot} 35%, var(--line))`,
                borderRadius: 1,
              }}/>
            )}
            {group.ids.map(id => {
              const m = APP_MODULES.find(x => x.id === id);
              if (!m) return null;
              const tone = MODULE_TONES[m.id] || MODULE_TONES.accueil;
              const isActive = module === m.id;
              return (
                <RailItem key={m.id}
                  m={m} tone={tone} active={isActive}
                  onClick={() => setMod(m.id)}
                />
              );
            })}
          </React.Fragment>
        ))}
      </div>

      {/* Paramètres en bas */}
      <div style={{
        marginTop: 14, paddingTop: 12, width: "100%",
        borderTop: "1px solid var(--line)",
        display: "flex", justifyContent: "center",
      }}>
        <RailItem
          m={{ id: "settings", icon: "settings", label: "Paramètres" }}
          tone={MODULE_TONES.settings}
          active={module === "settings"}
          onClick={() => setMod("settings")}
        />
      </div>
    </aside>
  );
};

const RailItem = ({ m, tone, active, onClick }) => {
  const [hover, setHover] = React.useState(false);
  return (
    <div style={{ position: "relative" }}
      onMouseEnter={() => setHover(true)}
      onMouseLeave={() => setHover(false)}>
      {/* Indicateur actif : barre verticale colorée à gauche du rail */}
      <span aria-hidden style={{
        position: "absolute", left: -22, top: 8, bottom: 8,
        width: 3, borderRadius: 99,
        background: tone.solid,
        transform: active ? "scaleY(1)" : "scaleY(0)",
        transformOrigin: "center",
        transition: "transform .3s cubic-bezier(.22,1,.36,1)",
      }}/>
      <button onClick={onClick}
        title={m.label}
        style={{
          width: 44, height: 44, borderRadius: 13,
          background: active ? tone.solid : (hover ? tone.bg : "transparent"),
          color: active ? "#fff" : (hover ? tone.ink : "var(--ink-3)"),
          border: "none", cursor: "pointer", fontFamily: "inherit",
          display: "flex", alignItems: "center", justifyContent: "center",
          transition: "background .2s ease, color .2s ease, transform .15s cubic-bezier(.22,1,.36,1)",
          transform: active ? "scale(1.04)" : (hover ? "scale(1.05)" : "scale(1)"),
          boxShadow: active ? `0 8px 18px -6px ${tone.solid}` : "none",
          position: "relative",
        }}>
        <Icon name={m.icon} size={18} stroke={active ? 2 : 1.7}/>
      </button>

      {/* Tooltip à droite au hover */}
      {hover && (
        <div style={{
          position: "absolute", left: "calc(100% + 10px)", top: "50%",
          transform: "translateY(-50%)",
          padding: "6px 12px",
          background: "var(--ink)", color: "var(--bg)",
          borderRadius: 8, fontSize: 12, fontWeight: 540,
          whiteSpace: "nowrap", pointerEvents: "none",
          letterSpacing: "-0.005em",
          boxShadow: "0 10px 20px -6px rgba(0,0,0,0.2)",
          animation: "railTipIn .15s cubic-bezier(.22,1,.36,1) forwards",
          opacity: 0,
          zIndex: 100,
        }}>
          {m.label}
          {/* Flèche pointant vers l'icône */}
          <span style={{
            position: "absolute", left: -4, top: "50%",
            transform: "translateY(-50%)",
            width: 0, height: 0,
            borderTop: "4px solid transparent",
            borderBottom: "4px solid transparent",
            borderRight: "4px solid var(--ink)",
          }}/>
        </div>
      )}
      <style>{`@keyframes railTipIn { to { opacity: 1; } }`}</style>
    </div>
  );
};

/* ============================================================
   BentoSidebar, desktop only, 2-col bento grid de tuiles modules
   - Avatar + nom business en haut
   - Groupes : chip de couleur + tuiles 2-col
   - Tuile active : background tone solid + icône blanche + glow
   - Hover : tuile lift + scale
============================================================ */
const BentoSidebar = ({ module, setMod, data }) => {
  const biz = data.business || {};
  return (
    <aside className="app-sidebar" style={{
      borderRight: "1px solid var(--line)",
      background: "var(--surface)",
      padding: "16px 12px 24px",
      display: "flex", flexDirection: "column", gap: 16,
      overflow: "auto",
    }}>
      {/* Header workspace */}
      <div style={{
        padding: "6px 6px 12px",
        borderBottom: "1px solid var(--line)",
        display: "flex", alignItems: "center", gap: 10,
      }}>
        <ProAvatar
          url={biz.avatar}
          initials={(biz.name || "CB").split(" ").map(x => x[0]).slice(0,2).join("").toUpperCase()}
          hue={biz.hue} size={36}
        />
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{
            fontFamily: "var(--ff-display)", fontSize: 14, fontWeight: 600,
            color: "var(--ink)", letterSpacing: "-0.015em",
            overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
          }}>
            {biz.name || "Votre activité"}
          </div>
          <div style={{
            fontSize: 11, color: "var(--ink-4)",
            overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
          }}>
            {biz.owner || "ClientBase"}
          </div>
        </div>
        <button onClick={() => setMod("settings")}
          aria-label="Paramètres" title="Paramètres"
          style={{
            width: 28, height: 28, borderRadius: 7, flexShrink: 0,
            background: module === "settings" ? "var(--ink)" : "transparent",
            color: module === "settings" ? "white" : "var(--ink-4)",
            border: module === "settings" ? "none" : "1px solid var(--line)",
            cursor: "pointer", padding: 0,
            display: "flex", alignItems: "center", justifyContent: "center",
            transition: "background .15s, color .15s, border-color .15s",
            fontFamily: "inherit",
          }}>
          <Icon name="settings" size={13}/>
        </button>
      </div>

      {/* Groupes bento */}
      {APP_MODULE_GROUPS.map(group => (
        <div key={group.label}>
          <div style={{
            display: "inline-flex", alignItems: "center", gap: 6,
            padding: "3px 9px", margin: "0 0 8px 4px",
            fontSize: 9.5, fontWeight: 600, color: group.tone.label,
            letterSpacing: "0.08em", textTransform: "uppercase",
            background: `color-mix(in oklab, ${group.tone.dot} 10%, transparent)`,
            borderRadius: 999,
          }}>
            <span aria-hidden style={{
              width: 5, height: 5, borderRadius: 50,
              background: group.tone.dot, flexShrink: 0,
            }}/>
            {group.label}
          </div>
          <div style={{
            display: "grid",
            gridTemplateColumns: "1fr 1fr",
            gap: 6,
          }}>
            {group.ids.map(id => {
              const m = APP_MODULES.find(x => x.id === id);
              if (!m) return null;
              const tone = MODULE_TONES[m.id] || MODULE_TONES.accueil;
              const isActive = module === m.id;
              return (
                <BentoTile key={m.id}
                  m={m} tone={tone} active={isActive}
                  onClick={() => setMod(m.id)}
                />
              );
            })}
          </div>
        </div>
      ))}
    </aside>
  );
};

const BentoTile = ({ m, tone, active, onClick }) => {
  const [hover, setHover] = React.useState(false);
  return (
    <button onClick={onClick}
      onMouseEnter={() => setHover(true)}
      onMouseLeave={() => setHover(false)}
      title={m.label}
      style={{
        position: "relative",
        display: "flex", flexDirection: "column",
        alignItems: "flex-start", justifyContent: "space-between",
        gap: 12, padding: "11px 11px 10px",
        background: active ? tone.solid : (hover ? tone.bg : "var(--bg-alt)"),
        border: active ? "none" : "1px solid var(--line)",
        borderRadius: 13,
        cursor: "pointer", fontFamily: "inherit", textAlign: "left",
        minHeight: 76,
        boxShadow: active
          ? `0 8px 20px -8px ${tone.solid}, 0 0 0 1px color-mix(in oklab, ${tone.solid} 20%, transparent)`
          : (hover ? "0 4px 10px -4px rgba(15,18,30,0.08)" : "none"),
        transform: hover && !active ? "translateY(-1px)" : "translateY(0)",
        transition: "background .2s ease, transform .15s ease, box-shadow .2s ease",
        overflow: "hidden",
      }}>
      {/* Icône en haut à gauche */}
      <span style={{
        width: 30, height: 30, borderRadius: 8,
        background: active ? "rgba(255,255,255,0.18)" : tone.bg,
        color: active ? "#fff" : tone.ink,
        display: "flex", alignItems: "center", justifyContent: "center",
        flexShrink: 0,
        transition: "background .2s ease, color .2s ease",
      }}>
        <Icon name={m.icon} size={15} stroke={active ? 2 : 1.7}/>
      </span>
      {/* Label */}
      <span style={{
        fontSize: 12, fontWeight: active ? 580 : 540,
        color: active ? "#fff" : "var(--ink)",
        letterSpacing: "-0.005em",
        lineHeight: 1.2,
      }}>
        {m.label}
      </span>
      {/* Petit indicateur actif en haut-droite */}
      {active && (
        <span aria-hidden style={{
          position: "absolute", top: 8, right: 8,
          width: 6, height: 6, borderRadius: "50%",
          background: "#fff", opacity: 0.85,
        }}/>
      )}
    </button>
  );
};

/* ============================================================
   AppDock, mobile only : drawer latéral plein-écran depuis la gauche
   - Hamburger dans la topbar ouvre le drawer
   - Drawer = sidebar mobile-friendly avec tous les modules groupés
   - Une seule interaction pour accéder à n'importe quel module
============================================================ */
const AppDock = ({ module, setMod }) => {
  const [open, setOpen] = React.useState(false);
  const palettes = useGroupTones();

  // Ouvre le drawer via custom event depuis la topbar (mobile hamburger)
  React.useEffect(() => {
    const onOpen = () => setOpen(true);
    window.addEventListener("cb-mobile-menu-open", onOpen);
    return () => window.removeEventListener("cb-mobile-menu-open", onOpen);
  }, []);

  // Bloque le scroll du body quand le drawer est ouvert
  React.useEffect(() => {
    if (open) document.body.style.overflow = "hidden";
    else document.body.style.overflow = "";
    return () => { document.body.style.overflow = ""; };
  }, [open]);

  return (
    <div className="app-dock" style={{
      position: "fixed", inset: 0, zIndex: 200,
      pointerEvents: open ? "auto" : "none",
    }}>
      {/* Backdrop */}
      <div onClick={() => setOpen(false)} style={{
        position: "absolute", inset: 0,
        background: open ? "rgba(15,18,30,0.5)" : "transparent",
        backdropFilter: open ? "blur(10px)" : "none",
        WebkitBackdropFilter: open ? "blur(10px)" : "none",
        transition: "background .3s ease, backdrop-filter .3s ease",
      }}/>

      {/* Drawer panel, slides from left */}
      <aside style={{
        position: "absolute", left: 0, top: 0, bottom: 0,
        width: "min(86vw, 340px)",
        background: "var(--surface)",
        boxShadow: "12px 0 40px -8px rgba(15,18,30,0.3)",
        display: "flex", flexDirection: "column",
        transform: open ? "translateX(0)" : "translateX(-100%)",
        transition: "transform .35s cubic-bezier(.22,1,.36,1)",
        paddingTop: "env(safe-area-inset-top)",
        paddingBottom: "env(safe-area-inset-bottom)",
      }}>
        {/* Header drawer */}
        <div style={{
          padding: "18px 18px 14px",
          borderBottom: "1px solid var(--line)",
          display: "flex", alignItems: "center", gap: 12,
        }}>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{
              fontFamily: "var(--ff-text)", fontSize: 10,
              color: "var(--ink-4)", textTransform: "uppercase", letterSpacing: "0.08em", fontWeight: 540,
              marginBottom: 3,
            }}>
              Navigation
            </div>
            <div style={{
              fontFamily: "var(--ff-display)", fontSize: 18, fontWeight: 600,
              color: "var(--ink)", letterSpacing: "-0.022em",
            }}>
              Mes modules
            </div>
          </div>
          <button onClick={() => setOpen(false)} aria-label="Fermer"
            style={{
              width: 36, height: 36, borderRadius: 10,
              background: "var(--bg-alt)", color: "var(--ink-2)",
              border: "none", cursor: "pointer", fontFamily: "inherit",
              display: "flex", alignItems: "center", justifyContent: "center",
              flexShrink: 0,
            }}>
            <Icon name="close" size={16} stroke={2}/>
          </button>
        </div>

        {/* Liste des modules par groupe */}
        <div style={{
          flex: 1, overflow: "auto", padding: "16px 12px 24px",
        }}>
          <div style={{ display: "flex", flexDirection: "column", gap: 18 }}>
            {APP_MODULE_GROUPS.map(group => {
              const tone = palettes[group.id];
              return (
                <div key={group.id}>
                  {/* Header de catégorie — parité visuelle avec MagicSidebar
                      desktop : label UPPERCASE TRACKED sobre, sans point coloré
                      (style Stripe/Linear, harmonisé avec la sidebar desktop). */}
                  <div aria-hidden style={{
                    display: "flex", alignItems: "center", gap: 8,
                    padding: "8px 12px 8px",
                    cursor: "default", userSelect: "none",
                  }}>
                    <span style={{
                      fontSize: 11, fontWeight: 660,
                      color: "var(--ink-4)",
                      letterSpacing: "0.08em",
                      textTransform: "uppercase",
                    }}>
                      {group.label}
                    </span>
                  </div>
                  {/* Modules du groupe */}
                  <div style={{ display: "flex", flexDirection: "column", gap: 2 }}>
                    {group.ids.map(id => {
                      const m = APP_MODULES.find(x => x.id === id);
                      if (!m) return null;
                      const active = module === m.id;
                      return (
                        <button key={m.id}
                          className="cb-press-feedback"
                          onClick={() => { setMod(m.id); setOpen(false); }}
                          style={{
                            display: "flex", alignItems: "center", gap: 12,
                            padding: "12px 12px", width: "100%",
                            background: active
                              ? `color-mix(in oklab, ${tone.solid} 16%, var(--surface))`
                              : "transparent",
                            border: "none", borderRadius: 11,
                            cursor: "pointer", fontFamily: "inherit",
                            textAlign: "left",
                            color: active ? tone.ink : "var(--ink-3)",
                            fontSize: 14.5, fontWeight: active ? 620 : 520,
                            letterSpacing: "-0.005em",
                          }}>
                          <span style={{
                            display: "inline-flex", alignItems: "center", justifyContent: "center",
                            flexShrink: 0, color: "inherit",
                          }}>
                            <Icon name={m.icon} size={18} stroke={active ? 2 : 1.8}/>
                          </span>
                          <span style={{ flex: 1 }}>{m.label}</span>
                        </button>
                      );
                    })}
                  </div>
                </div>
              );
            })}
          </div>

          {/* Onglet de rôle (mobile), si profil spécial */}
          {(() => {
            const role = window.cbCurrentRole ? window.cbCurrentRole() : "pro";
            if (role === "pro") return null;
            const m = APP_MODULES.find(x => x.id === role);
            if (!m) return null;
            const active = module === m.id;
            return (
              <button className="cb-press-feedback" onClick={() => { setMod(m.id); setOpen(false); }}
                style={{
                  display: "flex", alignItems: "center", gap: 12, padding: "12px 12px", width: "100%",
                  marginTop: 6, background: active ? "var(--accent-soft)" : "transparent",
                  border: "none", borderRadius: 11, cursor: "pointer", fontFamily: "inherit", textAlign: "left",
                  color: active ? "var(--accent-ink)" : "var(--ink-3)",
                  fontSize: 14.5, fontWeight: active ? 620 : 520,
                }}>
                <span style={{ display: "inline-flex", alignItems: "center", justifyContent: "center", flexShrink: 0, color: "inherit" }}>
                  <Icon name={m.icon} size={18} stroke={active ? 2 : 1.8}/>
                </span>
                <span style={{ flex: 1 }}>{m.label}</span>
              </button>
            );
          })()}

          {/* Paramètres tout en bas */}
          <div style={{
            marginTop: 18, paddingTop: 14,
            borderTop: "1px solid var(--line)",
          }}>
            <button
              onClick={() => { setMod("settings"); setOpen(false); }}
              style={{
                display: "flex", alignItems: "center", gap: 12,
                padding: "12px 12px", width: "100%",
                background: module === "settings" ? "var(--bg-alt)" : "transparent",
                border: "none", borderRadius: 11,
                cursor: "pointer", fontFamily: "inherit",
                textAlign: "left",
                color: module === "settings" ? "var(--ink)" : "var(--ink-3)",
                fontSize: 14.5, fontWeight: module === "settings" ? 620 : 520,
              }}>
              <span style={{
                display: "inline-flex", alignItems: "center", justifyContent: "center",
                flexShrink: 0, color: "inherit",
              }}>
                <Icon name="settings" size={18}/>
              </span>
              <span>Paramètres</span>
            </button>
          </div>
        </div>
      </aside>
    </div>
  );
};

const AppMoreSheet = ({ open, onClose, module, setMod }) => {
  const items = DOCK_MORE_IDS.map(id => APP_MODULES.find(m => m.id === id)).filter(Boolean);
  return (
    <div style={{
      position: "fixed", inset: 0, zIndex: 200,
      pointerEvents: open ? "auto" : "none",
    }}>
      {/* Backdrop */}
      <div onClick={onClose} style={{
        position: "absolute", inset: 0,
        background: open ? "rgba(15,18,30,0.45)" : "transparent",
        backdropFilter: open ? "blur(8px)" : "none",
        transition: "background .25s ease, backdrop-filter .25s ease",
      }}/>
      {/* Sheet content */}
      <div style={{
        position: "absolute", left: 0, right: 0, bottom: 0,
        background: "var(--surface)",
        borderTopLeftRadius: 24, borderTopRightRadius: 24,
        boxShadow: "0 -24px 60px -16px rgba(15,18,30,0.25)",
        padding: "10px 18px calc(24px + env(safe-area-inset-bottom))",
        maxHeight: "85vh", overflow: "auto",
        transform: open ? "translateY(0)" : "translateY(100%)",
        transition: "transform .35s cubic-bezier(.22,1,.36,1)",
      }}>
        {/* Drag handle */}
        <div style={{
          width: 38, height: 4, borderRadius: 999,
          background: "var(--line-strong)", margin: "0 auto 14px",
        }}/>
        <div style={{
          display: "flex", justifyContent: "space-between", alignItems: "baseline",
          marginBottom: 14,
        }}>
          <h3 style={{
            margin: 0, fontFamily: "var(--ff-display)", fontSize: 20, fontWeight: 580,
            letterSpacing: "-0.022em",
          }}>
            Tous les modules
          </h3>
          <button onClick={onClose} style={{
            padding: "4px 11px", background: "var(--bg-alt)", color: "var(--ink-2)",
            border: "none", borderRadius: 999, fontSize: 12, fontWeight: 540,
            cursor: "pointer", fontFamily: "inherit",
          }}>
            Fermer
          </button>
        </div>

        {/* Grid 2 colonnes */}
        <div style={{
          display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10,
        }}>
          {items.map(m => {
            const tone = MODULE_TONES[m.id] || MODULE_TONES.accueil;
            const active = module === m.id;
            return (
              <button key={m.id} onClick={() => setMod(m.id)}
                style={{
                  display: "flex", alignItems: "center", gap: 12,
                  padding: "14px 14px",
                  background: active ? tone.bg : "var(--bg-alt)",
                  border: active ? `1px solid ${tone.solid}` : "1px solid var(--line)",
                  borderRadius: 14, cursor: "pointer", fontFamily: "inherit",
                  textAlign: "left",
                }}>
                <span style={{
                  width: 38, height: 38, borderRadius: 11,
                  background: active ? tone.solid : tone.bg,
                  color: active ? "#fff" : tone.ink,
                  display: "inline-flex", alignItems: "center", justifyContent: "center",
                  flexShrink: 0,
                }}>
                  <Icon name={m.icon} size={17}/>
                </span>
                <span style={{
                  fontSize: 14, fontWeight: active ? 580 : 540,
                  color: active ? tone.ink : "var(--ink)",
                  letterSpacing: "-0.005em",
                }}>
                  {m.label}
                </span>
              </button>
            );
          })}
        </div>
      </div>
    </div>
  );
};

/* === Modal d'accueil quand on entre en mode démo === */
const DemoWelcomeModal = ({ setMod }) => {
  const [show, setShow] = React.useState(() => {
    try {
      const u = cbAuth.getCurrentUser();
      const isDemo = u && u.demo === true && localStorage.getItem("cb_demo_mode") === "1";
      const seen = sessionStorage.getItem("cb_demo_welcome_seen") === "1";
      return isDemo && !seen;
    } catch { return false; }
  });
  const close = () => {
    try { sessionStorage.setItem("cb_demo_welcome_seen", "1"); } catch {}
    setShow(false);
  };
  if (!show) return null;

  const shortcuts = [
    { icon: "calendar", title: "Voir l'agenda",      module: "agenda",   desc: "Une semaine déjà remplie de RDV" },
    { icon: "sparkle",  title: "Personnaliser ma page", module: "page",   desc: "Palette, bannière, galerie" },
    { icon: "chart",    title: "Voir les stats",     module: "stats",    desc: "CA, heatmap, top clientes" },
    { icon: "users",    title: "Mes clientes",       module: "clients",  desc: "Fiches enrichies" },
  ];

  return (
    <div style={{
      position: "fixed", inset: 0, zIndex: 9999,
      background: "rgba(15,18,30,0.55)", backdropFilter: "blur(8px)",
      display: "flex", alignItems: "center", justifyContent: "center",
      padding: 20, animation: "demoWelcomeIn .28s cubic-bezier(.22,1,.36,1)",
    }}>
      <style>{`
        @keyframes demoWelcomeIn { from { opacity: 0; backdrop-filter: blur(0); } to { opacity: 1; backdrop-filter: blur(8px); } }
        @keyframes demoCardIn { from { opacity: 0; transform: translateY(12px) scale(.97); } to { opacity: 1; transform: translateY(0) scale(1); } }
      `}</style>
      <div style={{
        background: "var(--surface)", borderRadius: 20,
        maxWidth: 520, width: "100%",
        boxShadow: "0 40px 80px -20px rgba(0,0,0,0.5)",
        overflow: "hidden",
        animation: "demoCardIn .35s cubic-bezier(.22,1,.36,1)",
      }}>
        {/* Hero header */}
        <div style={{
          padding: "26px 26px 22px",
          background: "linear-gradient(135deg, oklch(95% 0.10 145), oklch(94% 0.06 200))",
          borderBottom: "1px solid oklch(85% 0.10 145)",
        }}>
          <div style={{
            display: "inline-flex", alignItems: "center", gap: 6,
            padding: "4px 11px", background: "rgba(255,255,255,0.7)",
            borderRadius: 999, fontSize: 10.5, fontWeight: 580,
            color: "oklch(35% 0.12 145)", textTransform: "uppercase",
            letterSpacing: "0.06em",
            marginBottom: 12,
          }}>
            <span style={{
              display: "inline-block", width: 6, height: 6, borderRadius: "50%",
              background: "oklch(55% 0.18 145)",
              boxShadow: "0 0 0 3px oklch(55% 0.18 145 / 0.25)",
            }}/>
            Mode démo
          </div>
          <h2 style={{
            margin: 0, fontFamily: "var(--ff-display)", fontSize: 24, fontWeight: 580,
            letterSpacing: "-0.025em", color: "var(--ink)",
          }}>
            Bienvenue dans la démo, <em style={{ fontStyle: "italic", color: "oklch(35% 0.12 145)" }}>Marie</em>.
          </h2>
          <p style={{
            margin: "8px 0 0", fontSize: 13.5, color: "var(--ink-2)", lineHeight: 1.55,
          }}>
            Vous êtes prothésiste ongulaire à Lyon. Un compte fictif a été créé pour vous,
            explorez sans risque, vos modifications ne sont pas sauvegardées.
          </p>
        </div>

        {/* Shortcuts grid */}
        <div style={{ padding: "16px 18px 8px" }}>
          <div style={{
            fontSize: 11, color: "var(--ink-4)", textTransform: "uppercase",
            letterSpacing: "0.06em", fontWeight: 540, marginBottom: 10,
          }}>
            Par où voulez-vous commencer ?
          </div>
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8 }}>
            {shortcuts.map(s => (
              <button key={s.module}
                onClick={() => { setMod(s.module); close(); }}
                style={{
                  display: "flex", alignItems: "flex-start", gap: 10,
                  padding: "12px 13px",
                  background: "var(--bg-alt)", border: "1px solid var(--line)",
                  borderRadius: 12, cursor: "pointer", textAlign: "left",
                  fontFamily: "inherit", transition: "all .15s ease",
                }}
                onMouseEnter={e => {
                  e.currentTarget.style.borderColor = "var(--accent)";
                  e.currentTarget.style.background = "var(--accent-soft)";
                }}
                onMouseLeave={e => {
                  e.currentTarget.style.borderColor = "var(--line)";
                  e.currentTarget.style.background = "var(--bg-alt)";
                }}>
                <span style={{
                  width: 32, height: 32, borderRadius: 9,
                  background: "var(--accent-soft)", color: "var(--accent-ink)",
                  display: "inline-flex", alignItems: "center", justifyContent: "center",
                  flexShrink: 0,
                }}>
                  <Icon name={s.icon} size={15} stroke={1.8}/>
                </span>
                <div style={{ minWidth: 0 }}>
                  <div style={{ fontSize: 13, fontWeight: 540, color: "var(--ink)" }}>{s.title}</div>
                  <div style={{ fontSize: 11.5, color: "var(--ink-4)", marginTop: 1, lineHeight: 1.35 }}>{s.desc}</div>
                </div>
              </button>
            ))}
          </div>
        </div>

        {/* Footer */}
        <div style={{
          padding: "14px 18px 18px",
          display: "flex", justifyContent: "space-between", alignItems: "center", gap: 12,
        }}>
          <div style={{ fontSize: 11.5, color: "var(--ink-4)" }}>
            Quittez la démo à tout moment depuis le bandeau en haut.
          </div>
          <button onClick={close} style={{
            padding: "9px 16px", background: "var(--ink)", color: "var(--bg)",
            border: "none", borderRadius: 999, fontSize: 12.5, fontWeight: 540,
            cursor: "pointer", fontFamily: "inherit",
          }}>
            Explorer librement
          </button>
        </div>
      </div>
    </div>
  );
};

/* === Onboarding gamifié : widget flottant qui suit la progression du setup ===
   Apparait en bas-droite. 5 quêtes auto-validées selon le state de data.
   Une fois 100% : confetti puis se masque sauf si l'utilisateur le rouvre.
   Desktop only (caché < 900px).
*/
const QuestWidget = ({ data, setMod }) => {
  const [open, setOpen] = React.useState(false);
  const [hideDone, setHideDone] = React.useState(() => {
    try { return localStorage.getItem("cb_quests_done_dismissed") === "1"; } catch { return false; }
  });
  const [celebrating, setCelebrating] = React.useState(false);
  const [showOnMobile, setShowOnMobile] = React.useState(false);

  // Détection mobile à la volée (caché < 900px)
  const [isWide, setIsWide] = React.useState(() =>
    typeof window !== "undefined" ? window.innerWidth >= 900 : true);
  React.useEffect(() => {
    if (typeof window === "undefined") return;
    const onR = () => setIsWide(window.innerWidth >= 900);
    window.addEventListener("resize", onR);
    return () => window.removeEventListener("resize", onR);
  }, []);

  const quests = React.useMemo(() => {
    const services = data.services || [];
    const clients = data.clients || [];
    const appts = data.appointments || [];
    const biz = data.business || {};
    const pc = (data.bookingSettings && data.bookingSettings.pageCustom) || {};
    let sharedFlag = false;
    try { sharedFlag = localStorage.getItem("cb_book_link_shared") === "1"; } catch {}
    return [
      {
        id: "q_profile",
        title: "Compléter votre profil",
        desc: "Nom, téléphone, ville, la base.",
        done: !!(biz.name && biz.phone && biz.city),
        cta: "Aller",
        onCta: () => { setMod("settings"); setOpen(false); },
      },
      {
        id: "q_service",
        title: "Ajouter une première prestation",
        desc: "Au moins un service avec prix + durée.",
        done: services.length > 0,
        cta: "Ajouter",
        onCta: () => { setMod("services"); setOpen(false); },
      },
      {
        id: "q_client",
        title: "Ajouter une cliente",
        desc: "Votre cliente la plus fidèle ferait un bon départ.",
        done: clients.length > 0,
        cta: "Ajouter",
        onCta: () => { setMod("clients"); setOpen(false); },
      },
      {
        id: "q_page",
        title: "Personnaliser votre page de RDV",
        desc: "Palette, bannière, mini-bio.",
        done: !!(pc.bio || pc.banner || (pc.theme && pc.theme !== "indigo")),
        cta: "Personnaliser",
        onCta: () => { setMod("page"); setOpen(false); },
      },
      {
        id: "q_share",
        title: "Partager votre lien",
        desc: "Instagram, WhatsApp, carte de visite.",
        done: sharedFlag || appts.length > 0,
        cta: "Partager",
        onCta: () => {
          try { localStorage.setItem("cb_book_link_shared", "1"); } catch {}
          setMod("bookings");
          setOpen(false);
        },
      },
    ];
  }, [data, setMod]);

  const completed = quests.filter(q => q.done).length;
  const total = quests.length;
  const allDone = completed === total;

  // Auto-fête quand on passe à 100%
  const _wasAllDone = React.useRef(allDone);
  React.useEffect(() => {
    if (!_wasAllDone.current && allDone) {
      setCelebrating(true);
      setTimeout(() => setCelebrating(false), 2400);
    }
    _wasAllDone.current = allDone;
  }, [allDone]);

  if (!isWide && !showOnMobile) return null;
  if (allDone && hideDone) return null;

  const pct = Math.round((completed / total) * 100);

  return (
    <div style={{
      position: "fixed", right: 18, bottom: 18, zIndex: 250,
      pointerEvents: "auto",
    }}>
      {/* Pastille fermée */}
      {!open && (
        <button onClick={() => setOpen(true)}
          style={{
            padding: "10px 14px 10px 12px",
            background: allDone ? "linear-gradient(135deg, oklch(60% 0.18 145), oklch(55% 0.18 165))" : "var(--surface)",
            color: allDone ? "#fff" : "var(--ink)",
            border: allDone ? "none" : "1px solid var(--line)",
            borderRadius: 999,
            boxShadow: "0 12px 30px -10px rgba(15,18,30,0.25), 0 4px 10px -4px rgba(15,18,30,0.08)",
            cursor: "pointer", fontFamily: "inherit",
            display: "inline-flex", alignItems: "center", gap: 10,
            fontSize: 13, fontWeight: 540,
            animation: celebrating ? "questBounce 0.7s ease-in-out 2" : "none",
          }}>
          {/* Ring de progression */}
          <span style={{ position: "relative", width: 24, height: 24, flexShrink: 0 }}>
            <svg width="24" height="24" viewBox="0 0 24 24" style={{ transform: "rotate(-90deg)" }}>
              <circle cx="12" cy="12" r="10" fill="none"
                stroke={allDone ? "rgba(255,255,255,0.3)" : "var(--line-strong)"} strokeWidth="2.5"/>
              <circle cx="12" cy="12" r="10" fill="none"
                stroke={allDone ? "#fff" : "var(--accent)"} strokeWidth="2.5" strokeLinecap="round"
                strokeDasharray={`${(pct / 100) * 62.83} 62.83`}
                style={{ transition: "stroke-dasharray .5s ease" }}/>
            </svg>
            {allDone && (
              <span style={{
                position: "absolute", inset: 0, display: "flex",
                alignItems: "center", justifyContent: "center", fontSize: 11,
              }}>✓</span>
            )}
          </span>
          <span style={{ display: "flex", flexDirection: "column", alignItems: "flex-start", lineHeight: 1.2 }}>
            <span style={{
              fontSize: 10.5, fontFamily: "var(--ff-text)",
              opacity: allDone ? 0.85 : 0.55, textTransform: "uppercase", letterSpacing: "0.05em",
            }}>{allDone ? "Bravo" : "Mon setup"}</span>
            <span style={{ fontWeight: 580 }}>{completed} / {total} étapes</span>
          </span>
        </button>
      )}

      {/* Panneau ouvert */}
      {open && (
        <div style={{
          width: 340, background: "var(--surface)",
          border: "1px solid var(--line)", borderRadius: 16,
          boxShadow: "0 24px 56px -16px rgba(15,18,30,0.22), 0 8px 18px -8px rgba(15,18,30,0.1)",
          overflow: "hidden",
          animation: "questIn 0.24s cubic-bezier(.22,1,.36,1)",
        }}>
          {/* Header avec gradient + progress bar */}
          <div style={{
            padding: "16px 16px 14px",
            background: allDone
              ? "linear-gradient(135deg, oklch(95% 0.10 145), oklch(94% 0.06 165))"
              : "linear-gradient(135deg, var(--accent-soft), oklch(96% 0.04 200))",
          }}>
            <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: 12 }}>
              <div>
                <div style={{
                  fontSize: 10.5, fontFamily: "var(--ff-text)", color: allDone ? "oklch(35% 0.12 145)" : "var(--accent-ink)",
                  textTransform: "uppercase", letterSpacing: "0.06em", fontWeight: 540, marginBottom: 2,
                }}>
                  {allDone ? "🎉 setup terminé" : "ma progression"}
                </div>
                <div style={{
                  fontFamily: "var(--ff-display)", fontSize: 18, fontWeight: 580,
                  letterSpacing: "-0.018em",
                }}>
                  {allDone ? "Tout est en place !" : `${completed} / ${total} étapes`}
                </div>
              </div>
              <button onClick={() => setOpen(false)}
                style={{
                  width: 26, height: 26, borderRadius: "50%",
                  background: "rgba(255,255,255,0.7)", border: "none",
                  display: "flex", alignItems: "center", justifyContent: "center",
                  cursor: "pointer", color: "var(--ink-3)",
                }}>
                <Icon name="close" size={12} stroke={2.4}/>
              </button>
            </div>
            <div style={{
              height: 6, background: "rgba(255,255,255,0.55)", borderRadius: 999,
              overflow: "hidden",
            }}>
              <div style={{
                height: "100%", width: `${pct}%`,
                background: allDone ? "oklch(55% 0.18 145)" : "var(--accent)",
                borderRadius: 999,
                transition: "width .5s cubic-bezier(.22,1,.36,1)",
              }}/>
            </div>
          </div>

          {/* Liste des quêtes */}
          <div style={{ padding: "6px 0" }}>
            {quests.map(q => (
              <div key={q.id} style={{
                padding: "11px 16px",
                display: "flex", alignItems: "flex-start", gap: 10,
                opacity: q.done ? 0.65 : 1,
              }}>
                <span style={{
                  width: 22, height: 22, borderRadius: "50%", flexShrink: 0,
                  background: q.done ? "var(--sage-soft)" : "var(--bg-alt)",
                  border: q.done ? "none" : "1.5px dashed var(--line-strong)",
                  display: "inline-flex", alignItems: "center", justifyContent: "center",
                  color: q.done ? "var(--sage)" : "var(--ink-4)",
                  marginTop: 1,
                  transition: "background .25s",
                }}>
                  {q.done && <Icon name="check" size={11} stroke={3}/>}
                </span>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{
                    fontSize: 13, fontWeight: 540,
                    textDecoration: q.done ? "line-through" : "none",
                    color: q.done ? "var(--ink-3)" : "var(--ink)",
                  }}>{q.title}</div>
                  <div style={{ fontSize: 11.5, color: "var(--ink-4)", marginTop: 1 }}>{q.desc}</div>
                </div>
                {!q.done && (
                  <button onClick={q.onCta}
                    style={{
                      padding: "5px 10px",
                      background: "var(--accent)", color: "#fff",
                      border: "none", borderRadius: 999, fontSize: 11.5, fontWeight: 540,
                      cursor: "pointer", flexShrink: 0,
                    }}>
                    {q.cta}
                  </button>
                )}
              </div>
            ))}
          </div>

          {allDone && (
            <div style={{ padding: "0 16px 16px" }}>
              <button onClick={() => {
                try { localStorage.setItem("cb_quests_done_dismissed", "1"); } catch {}
                setHideDone(true);
              }} className="btn btn-ghost" style={{ width: "100%", justifyContent: "center" }}>
                Masquer le widget
              </button>
            </div>
          )}
        </div>
      )}

      <style>{`
        @keyframes questIn {
          from { opacity: 0; transform: translateY(8px) scale(0.96); }
          to   { opacity: 1; transform: translateY(0) scale(1); }
        }
        @keyframes questBounce {
          0%, 100% { transform: scale(1); }
          50%      { transform: scale(1.08); }
        }
      `}</style>
    </div>
  );
};

/* === Bandeau "Mode démo" en haut du dashboard ===
   Visible uniquement si localStorage.cb_demo_mode === "1".
   Bouton "Quitter la démo" appelle cbExitDemo() qui restaure la session originale.
*/
const DemoBanner = () => {
  const isDemo = (() => {
    try {
      const u = cbAuth.getCurrentUser();
      return u && u.demo === true && localStorage.getItem("cb_demo_mode") === "1";
    } catch { return false; }
  })();
  if (!isDemo) return null;
  // Bascule de profil en démo : change le rôle de l'user démo + recharge.
  const curRole = (window.cbCurrentRole && window.cbCurrentRole()) || "pro";
  const switchRole = (role) => {
    if (role === curRole) return;
    try {
      const u = cbAuth.getCurrentUser() || {};
      u.role = role;
      localStorage.setItem("cb_user_v1", JSON.stringify(u));
      localStorage.setItem("cb_demo_target", role === "pro" ? "accueil" : role);
      sessionStorage.removeItem("cb_splash_shown");
      window.location.href = "/v2/";
    } catch {}
  };
  return (
    <div style={{
      padding: "10px 22px",
      background: "linear-gradient(135deg, oklch(95% 0.10 145), oklch(94% 0.06 200))",
      borderBottom: "1px solid oklch(85% 0.10 145)",
      display: "flex", alignItems: "center", justifyContent: "space-between", gap: 14,
      fontSize: 13,
    }}>
      <div style={{ display: "flex", alignItems: "center", gap: 10, minWidth: 0 }}>
        <span style={{
          display: "inline-block", width: 8, height: 8, borderRadius: "50%",
          background: "oklch(55% 0.18 145)",
          boxShadow: "0 0 0 4px oklch(55% 0.18 145 / 0.2)",
          flexShrink: 0,
        }}/>
        <strong style={{ color: "oklch(35% 0.12 145)", fontWeight: 580 }}>Mode démo</strong>
        <span className="cb-demo-msg" style={{ color: "var(--ink-2)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
          Vous explorez un compte pré-rempli. Aucune donnée n'est sauvegardée.
        </span>
      </div>

      {/* Sélecteur de profil démo — RÉSERVÉ à l'admin (outil interne).
          Les visiteurs en démo pro ne le voient pas. */}
      {curRole === "admin" && (
        <div style={{ display: "flex", alignItems: "center", gap: 6, flexShrink: 0, flexWrap: "wrap" }}>
          <span style={{ fontSize: 11, color: "oklch(35% 0.12 145)", fontWeight: 600 }}>Prévisualiser :</span>
          {[["pro", "Pro"], ["ambassadrice", "Ambassadrice"], ["cofondatrice", "Co-fondatrice"], ["admin", "Admin"]].map(([v, l]) => (
            <button key={v} onClick={() => switchRole(v)} style={{
              padding: "4px 10px", borderRadius: 999, cursor: "pointer", fontFamily: "inherit",
              fontSize: 11.5, fontWeight: curRole === v ? 700 : 540,
              background: curRole === v ? "oklch(55% 0.18 145)" : "rgba(255,255,255,0.7)",
              color: curRole === v ? "#fff" : "oklch(35% 0.12 145)",
              border: "1px solid oklch(80% 0.08 145)", whiteSpace: "nowrap",
            }}>{l}</button>
          ))}
        </div>
      )}
      <div style={{ display: "flex", gap: 8, flexShrink: 0 }}>
        {/* CTA conversion fort : c'est LE moment de capturer le visiteur
            qui a touché l'app et veut maintenant créer son vrai compte. */}
        <button onClick={() => {
            try { if (window.cbExitDemo) window.cbExitDemo(); } catch {}
            // Petit délai pour laisser cbExitDemo finir sa restauration,
            // puis redirection vers la page d'inscription.
            setTimeout(() => { window.location.href = "/inscription"; }, 80);
          }}
          style={{
            padding: "6px 14px",
            background: "linear-gradient(135deg, var(--accent), oklch(45% 0.22 278))",
            color: "#fff",
            border: "none",
            borderRadius: 999,
            fontSize: 12.5, fontWeight: 600, letterSpacing: "-0.005em",
            cursor: "pointer", whiteSpace: "nowrap",
            boxShadow: "0 4px 12px -4px oklch(55% 0.22 278 / 0.45)",
            fontFamily: "inherit",
          }}>
          ✨ Créer mon vrai compte →
        </button>
        <button onClick={() => window.cbExitDemo && window.cbExitDemo()}
          style={{
            padding: "5px 11px",
            background: "rgba(255,255,255,0.7)",
            border: "1px solid oklch(80% 0.08 145)",
            borderRadius: 999,
            fontSize: 12, fontWeight: 540, color: "oklch(35% 0.12 145)",
            cursor: "pointer", whiteSpace: "nowrap",
            fontFamily: "inherit",
          }}>
          Quitter
        </button>
      </div>
    </div>
  );
};

/* ================================================================
   SIDEBAR
================================================================ */
const AppSidebar = ({ module, setMod, data, go }) => {
  return (
    <aside className="app-sidebar" style={{
      borderRight: "1px solid var(--line)",
      background: "var(--surface)",
      padding: "22px 14px 28px",
      display: "flex", flexDirection: "column",
      overflow: "auto",
      gap: 4,
    }}>
      {/* === Header : avatar + nom business + bouton settings === */}
      <div style={{
        padding: "4px 10px 20px",
        marginBottom: 6,
        borderBottom: "1px solid var(--line)",
        display: "flex", alignItems: "center", gap: 12,
      }}>
        <ProAvatar
          url={data.business.avatar}
          initials={(data.business.name || "CB").split(" ").map(x => x[0]).slice(0,2).join("").toUpperCase()}
          hue={data.business.hue} size={36}
        />
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{
            fontFamily: "var(--ff-display)", fontSize: 15.5, fontWeight: 600,
            color: "var(--ink)", letterSpacing: "-0.018em",
            display: "flex", alignItems: "center", gap: 5, minWidth: 0,
          }}>
            <span style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
              {data.business.name || "Votre activité"}
            </span>
            {(() => {
              const u = window.cbAuth && window.cbAuth.getCurrentUser && window.cbAuth.getCurrentUser();
              if (!u) return null;
              return u.emailVerified ? <VerifiedBadge size={15}/> : <UnverifiedBadge size={15}/>;
            })()}
          </div>
          <div style={{
            fontSize: 11, color: "var(--ink-4)",
            overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
          }}>
            {data.business.owner || "ClientBase"}
          </div>
        </div>
        <button onClick={() => setMod("settings")}
          aria-label="Paramètres" title="Paramètres"
          style={{
            width: 36, height: 36, borderRadius: 9, flexShrink: 0,
            background: module === "settings" ? "var(--ink)" : "transparent",
            color: module === "settings" ? "white" : "var(--ink-4)",
            border: module === "settings" ? "none" : "1px solid var(--line)",
            cursor: "pointer", padding: 0,
            display: "flex", alignItems: "center", justifyContent: "center",
            transition: "background .15s, color .15s, border-color .15s",
            fontFamily: "inherit",
          }}
          onMouseEnter={e => {
            if (module !== "settings") {
              e.currentTarget.style.background = "var(--bg-alt)";
              e.currentTarget.style.borderColor = "var(--line-strong)";
              e.currentTarget.style.color = "var(--ink-2)";
            }
          }}
          onMouseLeave={e => {
            if (module !== "settings") {
              e.currentTarget.style.background = "transparent";
              e.currentTarget.style.borderColor = "var(--line)";
              e.currentTarget.style.color = "var(--ink-4)";
            }
          }}>
          <Icon name="settings" size={14}/>
        </button>
      </div>

      {/* === Navigation === */}
      <nav className="cb-app-nav" style={{ display: "flex", flexDirection: "column", gap: 18 }}>
        {APP_MODULE_GROUPS.map((group, gi) => (
          <div key={group.label} className="cb-app-nav-group" style={{ display: "flex", flexDirection: "column" }}>
            {/* Header de groupe, pill colorée discrète */}
            <div className="cb-app-nav-group-label" style={{
              display: "inline-flex", alignSelf: "flex-start", alignItems: "center", gap: 6,
              padding: "3px 10px", margin: "0 0 8px 8px",
              fontSize: 10, fontWeight: 600, color: group.tone.label,
              letterSpacing: "0.08em", textTransform: "uppercase",
              background: `color-mix(in oklab, ${group.tone.dot} 12%, transparent)`,
              borderRadius: 999,
            }}>
              <span aria-hidden style={{
                width: 5, height: 5, borderRadius: 50,
                background: group.tone.dot, flexShrink: 0,
              }}/>
              {group.label}
            </div>
            {/* Modules du groupe */}
            <div style={{ display: "flex", flexDirection: "column", gap: 1 }}>
              {group.ids.map(id => {
                const m = APP_MODULES.find(x => x.id === id);
                if (!m) return null;
                const isActive = module === m.id;
                const tone = MODULE_TONES[m.id] || MODULE_TONES.accueil;
                return (
                  <SidebarNavBtn key={m.id}
                    m={m} isActive={isActive}
                    tone={tone} groupTone={group.tone}
                    onClick={() => setMod(m.id)}
                  />
                );
              })}
            </div>
          </div>
        ))}
      </nav>

      <style>{`
        .cb-app-nav-btn:hover .cb-app-nav-icon {
          transform: scale(1.06);
        }
      `}</style>
    </aside>
  );
};

/* Bouton de navigation individuel, design "tone-colored", indicator animé */
const SidebarNavBtn = ({ m, isActive, tone, groupTone, onClick }) => {
  const [hover, setHover] = React.useState(false);
  return (
    <button onClick={onClick}
      onMouseEnter={() => setHover(true)}
      onMouseLeave={() => setHover(false)}
      className="cb-app-nav-btn"
      style={{
        display: "flex", alignItems: "center", gap: 12,
        padding: "10px 12px", border: "none", width: "100%",
        borderRadius: 8, cursor: "pointer",
        fontSize: 14, fontWeight: isActive ? 620 : 520,
        background: isActive
          ? `color-mix(in oklab, ${tone.solid} 13%, var(--surface))`
          : hover ? "var(--bg-alt)" : "transparent",
        color: isActive ? tone.ink : (hover ? "var(--ink)" : "var(--ink-2)"),
        textAlign: "left", fontFamily: "inherit",
        transition: "background .2s cubic-bezier(.22,1,.36,1), color .15s ease",
        position: "relative",
        letterSpacing: "-0.005em",
      }}>
      {/* Indicateur actif, barre verticale colorée à gauche */}
      <span aria-hidden style={{
        position: "absolute", left: -2, top: 7, bottom: 7,
        width: 3, borderRadius: 99,
        background: tone.solid,
        transform: isActive ? "scaleY(1)" : "scaleY(0)",
        transformOrigin: "center",
        transition: "transform .3s cubic-bezier(.22,1,.36,1)",
      }}/>
      {/* Icône tone-colored */}
      <span className="cb-app-nav-icon" style={{
        width: 28, height: 28, borderRadius: 8,
        background: isActive ? tone.solid : tone.bg,
        color: isActive ? "#fff" : tone.ink,
        display: "flex", alignItems: "center", justifyContent: "center",
        flexShrink: 0,
        transition: "background .25s ease, color .25s ease, transform .2s cubic-bezier(.22,1,.36,1)",
        boxShadow: isActive ? `0 4px 10px -4px ${tone.solid}` : "none",
      }}>
        <Icon name={m.icon} size={14} stroke={isActive ? 2 : 1.7}/>
      </span>
      <span style={{ flex: 1 }}>{m.label}</span>
    </button>
  );
};

/* ================================================================
   TOP BAR
================================================================ */
const AppTopBar = ({ module, openModal, data, actions, go, setMod }) => {
  const [newOpen, setNewOpen] = React.useState(false);
  const [notifOpen, setNotifOpen] = React.useState(false);
  const [query, setQuery] = React.useState("");
  const searchRef = React.useRef(null);

  // Raccourci clavier ⌘K / Ctrl+K : place le curseur dans la recherche
  React.useEffect(() => {
    const onKey = (e) => {
      if ((e.metaKey || e.ctrlKey) && (e.key === "k" || e.key === "K")) {
        e.preventDefault();
        if (searchRef.current) searchRef.current.focus();
      }
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, []);
  const title = APP_MODULES.find(m => m.id === module)?.label
    || (module === "settings" ? "Paramètres" : "");

  const notifs = data.notifications || [];
  const unreadCount = notifs.filter(n => !n.read).length;

  const toggleNotif = () => {
    const willOpen = !notifOpen;
    setNotifOpen(willOpen);
    if (willOpen && unreadCount > 0) {
      // Mark all as read a moment after opening so the UI still shows the unread dots briefly
      setTimeout(() => actions && actions.markAllNotificationsRead && actions.markAllNotificationsRead(), 500);
    }
  };

  const handleNew = () => {
    if (module === "clients")  { openModal("client");       return; }
    if (module === "agenda")   { openModal("appointment");  return; }
    if (module === "factures") { openModal("invoice");      return; }
    if (module === "stock")    { openModal("stock");        return; }
    setNewOpen(v => !v);
  };

  const newMenuItem = (label, icon, action) => (
    <button onClick={() => { setNewOpen(false); action(); }} style={{
      display: "flex", alignItems: "center", gap: 10,
      width: "100%", padding: "9px 12px",
      background: "transparent", border: "none", cursor: "pointer",
      textAlign: "left", fontSize: 13.5, fontFamily: "inherit", color: "var(--ink-2)",
      borderRadius: 6,
    }} className="cb-row-hover">
      <Icon name={icon} size={14} style={{ color: "var(--ink-4)" }}/> {label}
    </button>
  );

  const biz = data.business || {};
  const palettes = useGroupTones();
  const moduleTone = getModuleTone(module, palettes);
  const openMobileMenu = () => {
    try { window.dispatchEvent(new Event("cb-mobile-menu-open")); } catch {}
  };
  return (
    <div className="app-topbar" style={{
      display: "flex", alignItems: "center", gap: 16,
      padding: "18px 32px", borderBottom: "1px solid var(--line)",
      background: "var(--surface)", position: "relative", zIndex: 20,
    }}>
      {/* Bouton hamburger, mobile only */}
      <button onClick={openMobileMenu}
        className="app-mobile-burger"
        aria-label="Ouvrir le menu"
        style={{
          display: "none",
          width: 38, height: 38, borderRadius: 10,
          background: "var(--bg-alt)", border: "1px solid var(--line)",
          cursor: "pointer", padding: 0,
          alignItems: "center", justifyContent: "center",
          color: "var(--ink-2)", flexShrink: 0,
        }}>
        <Icon name="menu" size={17} stroke={2}/>
      </button>

      {/* Identité business à gauche */}
      <div className="app-topbar-biz" style={{ display: "flex", alignItems: "center", gap: 11, minWidth: 0, flex: 1 }}>
        <div className="app-topbar-avatar" style={{ flexShrink: 0 }}>
          <ProAvatar
            url={biz.avatar}
            initials={(biz.name || "CB").split(" ").map(x => x[0]).slice(0,2).join("").toUpperCase()}
            hue={biz.hue} size={36}
          />
        </div>
        <div style={{ minWidth: 0, flex: 1 }}>
          {/* Nom du business, caché en mobile (déjà dans le drawer) */}
          <div className="app-topbar-bizname" style={{
            fontFamily: "var(--ff-display)", fontSize: 15.5, fontWeight: 600,
            letterSpacing: "-0.018em", color: "var(--ink)",
            display: "flex", alignItems: "center", gap: 6,
            overflow: "hidden", whiteSpace: "nowrap",
          }}>
            <span style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", minWidth: 0 }}>
              {biz.name || "Votre activité"}
            </span>
            {(() => {
              const u = window.cbAuth && window.cbAuth.getCurrentUser && window.cbAuth.getCurrentUser();
              if (!u) return null;
              return u.emailVerified ? <VerifiedBadge size={14}/> : <UnverifiedBadge size={14}/>;
            })()}
          </div>
          {/* Ligne module, devient le titre principal en mobile */}
          <div className="app-topbar-modline" style={{
            fontSize: 11.5, color: "var(--ink-4)",
            display: "flex", alignItems: "center", gap: 6,
            overflow: "hidden", minWidth: 0,
          }}>
            <span style={{
              display: "inline-block", width: 5, height: 5, borderRadius: "50%",
              background: moduleTone.solid, flexShrink: 0,
            }}/>
            <span style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
              {title}
            </span>
          </div>
        </div>
      </div>
      <div className="app-topbar-search" style={{ flex: 1, maxWidth: 420, marginLeft: "auto", position: "relative" }}>
        <Icon name="search" size={14} style={{ position: "absolute", left: 12, top: "50%", transform: "translateY(-50%)", color: "var(--ink-4)" }}/>
        <input ref={searchRef} value={query} onChange={e => setQuery(e.target.value)}
          placeholder="Rechercher un client, un RDV, une facture…" style={{
          width: "100%", padding: "10px 14px 10px 38px",
          background: "var(--bg)", border: "1px solid var(--line)",
          borderRadius: 9, fontSize: 14, fontFamily: "inherit", color: "var(--ink)",
          outline: "none",
        }}/>
        <span style={{ position: "absolute", right: 10, top: "50%", transform: "translateY(-50%)" }}>
          <span className="kbd">⌘K</span>
        </span>
        {query.trim().length >= 2 && (
          <SearchResults data={data} query={query}
            openModal={(t, c) => { openModal(t, c); }}
            setMod={setMod}
            onClose={() => setQuery("")}/>
        )}
      </div>
      <div style={{ position: "relative" }}>
        {/* Bouton Notifications — fond accent-soft permanent (au lieu de
            ghost), animation bell-shake quand des notifs non-lues arrivent,
            badge en accent solide bien visible. */}
        <button onClick={toggleNotif}
          className="cb-press-feedback"
          aria-label="Notifications"
          style={{
            height: 38, width: 38, padding: 0,
            display: "inline-flex", alignItems: "center", justifyContent: "center",
            background: notifOpen ? "var(--accent)" : "var(--accent-soft)",
            color: notifOpen ? "#fff" : "var(--accent-ink)",
            border: "none", borderRadius: 10,
            position: "relative", cursor: "pointer",
            transition: "background .2s ease, color .2s ease, transform .12s ease",
          }}
          onMouseEnter={e => { if (!notifOpen) e.currentTarget.style.background = "var(--accent-soft-2)"; }}
          onMouseLeave={e => { if (!notifOpen) e.currentTarget.style.background = "var(--accent-soft)"; }}>
          <Icon name="bell" size={16}
            style={{
              animation: unreadCount > 0 && !notifOpen ? "cbBellShake 1.8s ease-in-out infinite" : "none",
              transformOrigin: "top center",
            }}/>
          {unreadCount > 0 && (
            <span style={{
              position: "absolute", top: -3, right: -3,
              minWidth: 18, height: 18, padding: "0 5px",
              background: "var(--accent)", color: "white",
              borderRadius: 10, fontSize: 10.5, fontWeight: 700,
              display: "flex", alignItems: "center", justifyContent: "center",
              fontFamily: "inherit",
              border: "2px solid var(--surface)",
              boxShadow: "0 4px 10px -4px var(--accent)",
            }}>{unreadCount}</span>
          )}
        </button>
        <style>{`
          @keyframes cbBellShake {
            0%, 88%, 100% { transform: rotate(0); }
            90% { transform: rotate(-9deg); }
            92% { transform: rotate(7deg); }
            94% { transform: rotate(-5deg); }
            96% { transform: rotate(3deg); }
          }
        `}</style>
        {/* Backdrop, monté seulement quand ouvert pour ne pas bloquer les clics */}
        {notifOpen && (
          <div onClick={() => setNotifOpen(false)} style={{ position: "fixed", inset: 0, zIndex: 30 }}/>
        )}
        {/* Panel toujours monté pour animer en sortie aussi (transition
            opacity + translate + scale, pas un simple show/hide brutal). */}
        <div className="app-notif-panel" style={{
          position: "absolute", right: 0, top: "calc(100% + 8px)",
          width: 360, maxWidth: "calc(100vw - 32px)",
          background: "var(--surface)", border: "1px solid var(--line)",
          borderRadius: 12, boxShadow: "var(--sh-3)",
          zIndex: 40, overflow: "hidden",
          opacity: notifOpen ? 1 : 0,
          transform: notifOpen ? "translateY(0) scale(1)" : "translateY(-8px) scale(0.97)",
          transformOrigin: "top right",
          pointerEvents: notifOpen ? "auto" : "none",
          transition: "opacity .22s ease, transform .25s cubic-bezier(.22, 1, .36, 1)",
        }}>
        {notifOpen && (
          <>
              <div style={{
                padding: "12px 14px", borderBottom: "1px solid var(--line)",
                display: "flex", justifyContent: "space-between", alignItems: "center",
              }}>
                <div style={{ fontWeight: 520, fontSize: 14 }}>Notifications</div>
                {notifs.length > 0 && (
                  <button onClick={() => { actions.markAllNotificationsRead(); }} style={{
                    background: "transparent", border: "none", cursor: "pointer",
                    fontSize: 12, color: "var(--accent-ink)", fontFamily: "inherit",
                  }}>Tout marquer lu</button>
                )}
              </div>
              <div style={{ maxHeight: 400, overflowY: "auto" }}>
                {notifs.length === 0 ? (
                  <div style={{ padding: 32, textAlign: "center", color: "var(--ink-4)", fontSize: 13 }}>
                    Aucune notification pour le moment.
                  </div>
                ) : (
                  notifs.slice(0, 20).map(n => (
                    <NotificationRow key={n.id} n={n} data={data}
                      onClick={() => {
                        setNotifOpen(false);
                        if (n.type === "booking" && n.appointmentId && setMod) setMod("agenda");
                        if (n.type === "payment"  && setMod) setMod("factures");
                        if (n.type === "stock"    && setMod) setMod("stock");
                      }}
                      onDelete={() => actions.deleteNotification(n.id)}
                    />
                  ))
                )}
              </div>
          </>
        )}
        </div>
      </div>
      <div style={{ position: "relative" }}>
        {/* Bouton Nouveau — gradient accent (au lieu d'ink), lueur permanente,
            icône qui tourne au hover/click pour signaler l'interactivité. */}
        <button onClick={handleNew}
          className="cb-press-feedback"
          style={{
            height: 38, padding: "0 16px 0 14px",
            display: "inline-flex", alignItems: "center", gap: 8,
            background: "linear-gradient(135deg, var(--accent) 0%, oklch(45% 0.22 288) 100%)",
            color: "white", border: "none", borderRadius: 10,
            cursor: "pointer", fontFamily: "inherit",
            fontSize: 14, fontWeight: 580, letterSpacing: "-0.005em",
            boxShadow: newOpen
              ? "0 10px 24px -8px var(--accent)"
              : "0 4px 12px -3px color-mix(in oklab, var(--accent) 55%, transparent)",
            transition: "transform .18s ease, box-shadow .22s ease",
          }}
          onMouseEnter={e => { e.currentTarget.style.transform = "translateY(-1px)"; }}
          onMouseLeave={e => { e.currentTarget.style.transform = "translateY(0)"; }}>
          <Icon name="plus" size={14}
            style={{
              transform: newOpen ? "rotate(45deg)" : "rotate(0)",
              transition: "transform .25s cubic-bezier(.22,1,.36,1)",
            }}/>
          Nouveau
        </button>
        {newOpen && (
          <>
            <div onClick={() => setNewOpen(false)} style={{ position: "fixed", inset: 0, zIndex: 30 }}/>
            <div style={{
              position: "absolute", right: 0, top: "calc(100% + 6px)",
              background: "var(--surface)", border: "1px solid var(--line)",
              borderRadius: 10, padding: 6, boxShadow: "var(--sh-2)",
              minWidth: 200, zIndex: 40,
            }}>
              {newMenuItem("Nouveau client", "users", () => openModal("client"))}
              {newMenuItem("Nouveau rendez-vous", "calendar", () => openModal("appointment"))}
              {newMenuItem("Nouvelle facture", "invoice", () => openModal("invoice"))}
              {newMenuItem("Nouveau produit", "box", () => openModal("stock"))}
            </div>
          </>
        )}
      </div>
    </div>
  );
};

/* Recherche globale, clients + RDV + factures + prestations + modules */
const SearchResults = ({ data, query, openModal, setMod, onClose }) => {
  const q = query.trim().toLowerCase();
  const norm = (s) => (s || "").toString().toLowerCase();

  // 1) Clients
  const clientHits = (data.clients || []).filter(c =>
    norm(c.name).includes(q) || norm(c.email).includes(q) || norm(c.phone).includes(q)
  ).slice(0, 4);

  // 2) Prestations
  const serviceHits = (data.services || []).filter(s => norm(s.name).includes(q)).slice(0, 3);

  // 3) Factures (par numéro ou nom client)
  const invoiceHits = (data.invoices || []).filter(inv => {
    const cl = (data.clients || []).find(c => c.id === inv.clientId);
    return norm(inv.number).includes(q) || norm(inv.id).includes(q) || (cl && norm(cl.name).includes(q));
  }).slice(0, 3);

  // 4) RDV à venir (par nom client)
  const todayOff = (window.cbTodayOffset && window.cbTodayOffset()) || 0;
  const apptHits = (data.appointments || [])
    .filter(a => a.day >= todayOff)
    .filter(a => {
      const cl = (data.clients || []).find(c => c.id === a.clientId);
      const sv = (data.services || []).find(s => s.id === a.serviceId);
      return (cl && norm(cl.name).includes(q)) || (sv && norm(sv.name).includes(q));
    })
    .slice(0, 3);

  // 5) Modules / pages
  const moduleHits = APP_MODULES.filter(m => norm(m.label).includes(q)).slice(0, 4);

  const totalHits = clientHits.length + serviceHits.length + invoiceHits.length + apptHits.length + moduleHits.length;

  const Section = ({ label }) => (
    <div style={{
      padding: "8px 10px 4px", fontSize: 10, fontWeight: 600,
      color: "var(--ink-4)", textTransform: "uppercase", letterSpacing: "0.06em",
    }}>{label}</div>
  );
  const rowStyle = {
    display: "flex", alignItems: "center", gap: 10, width: "100%",
    padding: "8px 10px", background: "transparent", border: "none",
    cursor: "pointer", textAlign: "left", borderRadius: 8, fontFamily: "inherit",
  };

  return (
    <div style={{
      position: "absolute", top: "calc(100% + 6px)", left: 0, right: 0,
      background: "var(--surface)", border: "1px solid var(--line)",
      borderRadius: 12, boxShadow: "var(--sh-3)", zIndex: 50,
      padding: 6, maxHeight: 420, overflow: "auto",
    }}>
      {totalHits === 0 && (
        <div style={{ padding: "18px 12px", fontSize: 13, color: "var(--ink-4)", textAlign: "center" }}>
          Aucun résultat pour « {query.trim()} »
        </div>
      )}

      {/* Modules */}
      {moduleHits.length > 0 && <Section label="Pages"/>}
      {moduleHits.map(m => {
        const tone = MODULE_TONES[m.id] || MODULE_TONES.accueil;
        return (
          <button key={"m-" + m.id} className="cb-row-hover" style={rowStyle}
            onClick={() => { onClose && onClose(); setMod && setMod(m.id); }}>
            <span style={{
              width: 28, height: 28, borderRadius: 8, flexShrink: 0,
              background: tone.bg, color: tone.ink,
              display: "flex", alignItems: "center", justifyContent: "center",
            }}>
              <Icon name={m.icon} size={14}/>
            </span>
            <div style={{ flex: 1, minWidth: 0 }}>
              <div style={{ fontSize: 13.5, fontWeight: 520, color: "var(--ink)" }}>{m.label}</div>
              <div style={{ fontSize: 11.5, color: "var(--ink-4)" }}>Ouvrir la page</div>
            </div>
            <Icon name="arrow" size={13} style={{ color: "var(--ink-4)" }}/>
          </button>
        );
      })}

      {/* Clients */}
      {clientHits.length > 0 && <Section label="Clients"/>}
      {clientHits.map(c => (
        <button key={"c-" + c.id} onClick={() => { onClose && onClose(); openModal("client", { id: c.id }); }} className="cb-row-hover" style={rowStyle}>
          <Avatar client={c} size={28}/>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontSize: 13.5, fontWeight: 520, color: "var(--ink)" }}>{c.name}</div>
            <div style={{ fontSize: 11.5, color: "var(--ink-4)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{c.email || c.phone || "—"}</div>
          </div>
        </button>
      ))}

      {/* Prestations */}
      {serviceHits.length > 0 && <Section label="Prestations"/>}
      {serviceHits.map(s => (
        <button key={"s-" + s.id} onClick={() => { onClose && onClose(); setMod && setMod("services"); }} className="cb-row-hover" style={rowStyle}>
          <span style={{
            width: 28, height: 28, borderRadius: 8, flexShrink: 0,
            background: "var(--accent-soft)", color: "var(--accent-ink)",
            display: "flex", alignItems: "center", justifyContent: "center",
          }}>
            <Icon name="tag" size={13}/>
          </span>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontSize: 13.5, fontWeight: 520, color: "var(--ink)" }}>{s.name}</div>
            <div style={{ fontSize: 11.5, color: "var(--ink-4)" }}>{s.price ? `${s.price} €` : "—"} · prestation</div>
          </div>
        </button>
      ))}

      {/* RDV à venir */}
      {apptHits.length > 0 && <Section label="Rendez-vous à venir"/>}
      {apptHits.map(a => {
        const cl = (data.clients || []).find(c => c.id === a.clientId);
        const sv = (data.services || []).find(s => s.id === a.serviceId);
        const d = window.cbDateForOffset ? window.cbDateForOffset(a.day) : null;
        return (
          <button key={"a-" + a.id} onClick={() => { onClose && onClose(); setMod && setMod("agenda"); }} className="cb-row-hover" style={rowStyle}>
            <span style={{
              width: 28, height: 28, borderRadius: 8, flexShrink: 0,
              background: "oklch(95% 0.05 30)", color: "oklch(45% 0.14 30)",
              display: "flex", alignItems: "center", justifyContent: "center",
            }}>
              <Icon name="calendar" size={13}/>
            </span>
            <div style={{ flex: 1, minWidth: 0 }}>
              <div style={{ fontSize: 13.5, fontWeight: 520, color: "var(--ink)" }}>{cl ? cl.name : "RDV"}</div>
              <div style={{ fontSize: 11.5, color: "var(--ink-4)" }}>
                {sv ? sv.name : ""}{d ? ` · ${d.getDate()}/${d.getMonth() + 1}` : ""}
              </div>
            </div>
          </button>
        );
      })}

      {/* Factures */}
      {invoiceHits.length > 0 && <Section label="Factures"/>}
      {invoiceHits.map(inv => {
        const cl = (data.clients || []).find(c => c.id === inv.clientId);
        return (
          <button key={"i-" + inv.id} onClick={() => { onClose && onClose(); setMod && setMod("factures"); }} className="cb-row-hover" style={rowStyle}>
            <span style={{
              width: 28, height: 28, borderRadius: 8, flexShrink: 0,
              background: "oklch(94% 0.05 240)", color: "oklch(40% 0.14 240)",
              display: "flex", alignItems: "center", justifyContent: "center",
            }}>
              <Icon name="invoice" size={13}/>
            </span>
            <div style={{ flex: 1, minWidth: 0 }}>
              <div style={{ fontSize: 13.5, fontWeight: 520, color: "var(--ink)" }}>{inv.number || inv.id}</div>
              <div style={{ fontSize: 11.5, color: "var(--ink-4)" }}>
                {cl ? cl.name : "—"} · {fmtEUR(inv.amount)}
              </div>
            </div>
          </button>
        );
      })}
    </div>
  );
};

const timeAgo = (ts) => {
  const diff = Date.now() - ts;
  const m = Math.floor(diff / 60000);
  if (m < 1) return "à l'instant";
  if (m < 60) return `il y a ${m} min`;
  const h = Math.floor(m / 60);
  if (h < 24) return `il y a ${h} h`;
  const d = Math.floor(h / 24);
  if (d === 1) return "hier";
  if (d < 7) return `il y a ${d} j`;
  const w = Math.floor(d / 7);
  return `il y a ${w} sem.`;
};

const NOTIF_ICON = {
  booking: { name: "calendar", bg: "var(--accent-soft)",          c: "var(--accent-ink)" },
  payment: { name: "euro",     bg: "var(--sage-soft)",            c: "oklch(38% 0.08 160)" },
  review:  { name: "star",     bg: "oklch(95% 0.07 85)",          c: "oklch(45% 0.16 80)" },
  no_show: { name: "close",    bg: "oklch(95% 0.05 25)",          c: "oklch(48% 0.16 25)" },
  stock:   { name: "bell",     bg: "oklch(97% 0.025 30)",         c: "oklch(45% 0.14 30)" },
  default: { name: "bell",     bg: "var(--bg-alt)",               c: "var(--ink-3)" },
};

const NotificationRow = ({ n, data, onClick, onDelete }) => {
  const icon = NOTIF_ICON[n.type] || NOTIF_ICON.default;
  const client = n.clientId ? findClient(data, n.clientId) : null;
  return (
    <div style={{
      padding: "12px 14px", borderBottom: "1px solid var(--line)",
      display: "flex", gap: 10, alignItems: "flex-start",
      background: n.read ? "transparent" : "color-mix(in oklab, var(--accent-soft) 40%, transparent)",
      cursor: "pointer", position: "relative",
    }}
      onClick={onClick}
      onMouseEnter={e => e.currentTarget.style.background = "var(--bg-alt)"}
      onMouseLeave={e => e.currentTarget.style.background = n.read ? "transparent" : "color-mix(in oklab, var(--accent-soft) 40%, transparent)"}
    >
      <div style={{
        width: 32, height: 32, borderRadius: 8,
        background: icon.bg, color: icon.c,
        display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0,
      }}>
        <Icon name={icon.name} size={15}/>
      </div>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", gap: 8 }}>
          <div style={{ fontSize: 13.5, fontWeight: 520, color: "var(--ink)" }}>{n.title}</div>
          <div style={{ fontSize: 11, color: "var(--ink-4)", fontFamily: "inherit", flexShrink: 0 }}>{timeAgo(n.createdAt)}</div>
        </div>
        <div style={{ fontSize: 12.5, color: "var(--ink-3)", marginTop: 2, lineHeight: 1.45 }}>{n.message}</div>
        {client && (
          <div style={{ marginTop: 8, display: "flex", alignItems: "center", gap: 8 }}>
            <Avatar client={client} size={22}/>
            <span style={{ fontSize: 12, color: "var(--ink-4)" }}>{client.name}</span>
          </div>
        )}
      </div>
      <button onClick={(e) => { e.stopPropagation(); onDelete(); }} aria-label="Supprimer" style={{
        background: "transparent", border: "none", cursor: "pointer",
        color: "var(--ink-4)", padding: 4, flexShrink: 0, opacity: 0.6,
      }}
        onMouseEnter={e => { e.currentTarget.style.opacity = 1; e.currentTarget.style.color = "oklch(55% 0.18 25)"; }}
        onMouseLeave={e => { e.currentTarget.style.opacity = 0.6; e.currentTarget.style.color = "var(--ink-4)"; }}
      >
        <Icon name="close" size={14}/>
      </button>
    </div>
  );
};

const Avatar = ({ client, size = 32 }) => (
  <div style={{
    width: size, height: size, borderRadius: 50,
    background: `linear-gradient(135deg, oklch(80% 0.12 ${client.hue}), oklch(70% 0.14 ${(client.hue+40)%360}))`,
    color: "white", display: "flex", alignItems: "center", justifyContent: "center",
    fontSize: size * 0.38, fontWeight: 600, flexShrink: 0,
  }}>{client.name.split(" ").map(x => x[0]).slice(0,2).join("")}</div>
);

/* ================================================================
   HOME DASHBOARD
================================================================ */
/* Bannière « Objectif du mois » — style quest du preview dashboard.
   CA du mois vs objectif configurable (persisté en local), barre de
   progression indigo. Utilisée par l'accueil desktop + mobile. */
const AppHomeGoalBanner = ({ monthCa, compact = false }) => {
  const [goalStr, setGoal] = useLocalString("cb_home_month_goal", "3000");
  const [editing, setEditing] = React.useState(false);
  const [draft, setDraft] = React.useState(goalStr);
  const goal = Math.max(1, Number(goalStr) || 0);
  const pct = Math.min(100, Math.round((monthCa / goal) * 100));
  const remaining = Math.max(0, goal - monthCa);
  const reached = monthCa >= goal;

  const save = () => {
    const n = Math.max(0, Math.round(Number(draft) || 0));
    setGoal(String(n));
    setEditing(false);
  };

  return (
    <div style={{
      padding: compact ? "14px 16px" : "16px 20px",
      background: "linear-gradient(135deg, var(--accent-soft) 0%, var(--bg-alt) 100%)",
      border: "1px solid var(--accent-soft-2)",
      borderRadius: 14, marginBottom: compact ? 0 : 20,
      display: "flex", alignItems: "center", gap: 16, flexWrap: "wrap",
    }}>
      <div style={{
        width: 46, height: 46, borderRadius: 12, flexShrink: 0,
        background: "var(--accent)", color: "#fff",
        display: "flex", alignItems: "center", justifyContent: "center", fontSize: 22,
      }}>🎯</div>
      <div style={{ flex: 1, minWidth: 180 }}>
        {editing ? (
          <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
            <span style={{ fontSize: 13.5, color: "var(--ink-2)", fontWeight: 560 }}>Objectif du mois :</span>
            <input type="number" value={draft} min="0" autoFocus
              onChange={e => setDraft(e.target.value)}
              onKeyDown={e => { if (e.key === "Enter") save(); if (e.key === "Escape") setEditing(false); }}
              style={{
                width: 90, padding: "5px 9px", background: "var(--surface)",
                border: "1px solid var(--accent)", borderRadius: 8, fontSize: 13.5,
                fontFamily: "inherit", color: "var(--ink)", outline: "none",
              }}/>
            <span style={{ fontSize: 14, color: "var(--ink-3)", fontWeight: 600 }}>€</span>
            <button onClick={save} className="btn btn-sm btn-accent" style={{ height: 30 }}>OK</button>
          </div>
        ) : (
          <>
            <div style={{ fontFamily: "var(--ff-display)", fontSize: 15.5, fontWeight: 620, letterSpacing: "-0.01em", color: "var(--ink)" }}>
              Objectif du mois : {goal.toLocaleString("fr-FR")} €
            </div>
            <div style={{ fontSize: 13, color: "var(--ink-3)", marginTop: 3 }}>
              {reached
                ? <>🎉 Objectif atteint — {monthCa.toLocaleString("fr-FR")} € ce mois-ci !</>
                : <>Vous êtes à {monthCa.toLocaleString("fr-FR")} € · plus que {remaining.toLocaleString("fr-FR")} €</>}
            </div>
            <div style={{ height: 8, background: "var(--surface)", borderRadius: 4, overflow: "hidden", marginTop: 8, maxWidth: 320 }}>
              <div style={{ height: "100%", width: `${pct}%`, background: "var(--accent)", borderRadius: 4, transition: "width .5s cubic-bezier(.22,1,.36,1)" }}/>
            </div>
          </>
        )}
      </div>
      {!editing && (
        <button onClick={() => { setDraft(goalStr); setEditing(true); }} className="btn btn-sm btn-ghost" style={{ flexShrink: 0 }}>
          Modifier l'objectif
        </button>
      )}
    </div>
  );
};

/* Mini-graphe SVG (sparkline) : barres compactes, accent indigo.
   `values` : tableau de N nombres (CA, RDV, etc.) sur les N derniers jours. */
const Sparkline = ({ values = [], w = 84, h = 26, color = "var(--accent)" }) => {
  if (!values.length) return null;
  const max = Math.max(1, ...values);
  const barW = Math.max(2, Math.floor((w - (values.length - 1) * 2) / values.length));
  return (
    <svg width={w} height={h} viewBox={`0 0 ${w} ${h}`} aria-hidden style={{ display: "block" }}>
      {values.map((v, i) => {
        const barH = Math.max(2, (v / max) * (h - 4));
        const x = i * (barW + 2);
        const y = h - barH;
        return <rect key={i} x={x} y={y} width={barW} height={barH} rx={1.5} fill={color} opacity={i === values.length - 1 ? 1 : 0.55}/>;
      })}
    </svg>
  );
};

/* Bandeau d'insights : 1 à 3 phrases courtes computées sur les données
   réelles du pro (variation semaine, objectif, anniversaire…). Style Linear. */
const DashboardInsights = ({ data, todayCa, weekTotalCa, weekTotalRdv }) => {
  // CA semaine précédente : approximation via dernières 14 jours, on prend
  // la moitié la plus ancienne. Suffisant pour un signal de tendance.
  const todayOff = (window.cbTodayOffset && window.cbTodayOffset()) || 0;
  const prevWeekCa = React.useMemo(() => {
    return (data.appointments || []).reduce((s, a) => {
      const off = todayOff - a.day;
      if (off < 7 || off > 13) return s;
      const svc = (data.services || []).find(x => x.id === a.serviceId);
      return s + (svc ? Number(svc.price) || 0 : 0);
    }, 0);
  }, [data.appointments, data.services, todayOff]);
  const diffPct = prevWeekCa > 0 ? Math.round(((weekTotalCa - prevWeekCa) / prevWeekCa) * 100) : null;

  const insights = [];
  if (diffPct != null) {
    if (diffPct >= 5)      insights.push({ emoji: "📈", text: <>CA semaine en hausse de <strong>+{diffPct} %</strong> vs la précédente.</>, tone: "up" });
    else if (diffPct <= -5) insights.push({ emoji: "📉", text: <>CA semaine en baisse de <strong>{diffPct} %</strong> vs la précédente. Pensez à une relance.</>, tone: "down" });
    else                    insights.push({ emoji: "📊", text: <>CA stable cette semaine (<strong>{diffPct >= 0 ? "+" : ""}{diffPct} %</strong>).</>, tone: "flat" });
  }
  if (weekTotalRdv > 0 && weekTotalCa > 0) {
    const avg = Math.round(weekTotalCa / weekTotalRdv);
    insights.push({ emoji: "🎯", text: <>Panier moyen cette semaine : <strong>{avg} €</strong> sur {weekTotalRdv} RDV.</>, tone: "info" });
  }
  if (insights.length === 0) return null;

  const toneBg = { up: "var(--sage-soft)", down: "var(--rose-soft, var(--accent-soft))", flat: "var(--bg-alt)", info: "var(--accent-soft)" };
  const toneInk = { up: "oklch(38% 0.10 160)", down: "var(--rose-ink, var(--accent-ink))", flat: "var(--ink-3)", info: "var(--accent-ink)" };

  return (
    <div style={{ display: "flex", gap: 10, flexWrap: "wrap", marginBottom: 16 }}>
      {insights.map((i, k) => (
        <div key={k} style={{
          flex: "1 1 240px", minWidth: 220, display: "flex", alignItems: "center", gap: 10,
          padding: "10px 14px", borderRadius: 11,
          background: toneBg[i.tone], color: toneInk[i.tone],
          border: "1px solid color-mix(in oklab, currentColor 15%, transparent)",
        }}>
          <span style={{ fontSize: 18 }}>{i.emoji}</span>
          <span style={{ fontSize: 13, lineHeight: 1.4 }}>{i.text}</span>
        </div>
      ))}
    </div>
  );
};

const AppHome = ({ data, actions, openModal, setMod }) => {
  const today = data.appointments.filter(a => a.day === cbTodayOffset() && !a.done).sort((x, y) => x.h - y.h);
  const next = today[0];
  const firstName = (data.business.owner || "").split(" ")[0];
  // Re-evaluate every minute so the line refreshes (e.g. from "Belle matinée" → "Presque midi").
  const [tick, setTick] = React.useState(0);
  React.useEffect(() => {
    const id = setInterval(() => setTick(t => t + 1), 60000);
    return () => clearInterval(id);
  }, []);
  const greet = cbDynamicGreeting(firstName, today.length);
  const event = cbEventOfDay();
  const bdays = cbBirthdaysToday(data.clients || []);
  const pause = cbActivePause(data.bookingSettings);
  const animCount = useAnimatedNumber(today.length, 650);
  const displayCount = today.length === 0 ? 0 : Math.max(1, Math.round(animCount));

  // Stats de la journée
  const todayAll = (data.appointments || []).filter(a => a.day === cbTodayOffset());
  const todayAllSorted = todayAll.slice().sort((a, b) => a.h - b.h);
  const todayDone = todayAll.filter(a => a.done).length;
  const todayRemaining = today.length;
  const todayTotal = todayAll.length;
  const todayProgressPct = todayTotal > 0 ? Math.round((todayDone / todayTotal) * 100) : 0;
  const todayCa = todayAll.reduce((s, a) => {
    const svc = (data.services || []).find(x => x.id === a.serviceId);
    return s + (svc ? Number(svc.price) || 0 : 0);
  }, 0);

  // CA du mois en cours (pour la bannière « Objectif du mois », style preview).
  const monthCa = React.useMemo(() => {
    const ref = window.cbDateForOffset ? window.cbDateForOffset(cbTodayOffset()) : new Date();
    const m = ref.getMonth(), y = ref.getFullYear();
    return (data.appointments || []).reduce((s, a) => {
      const d = window.cbDateForOffset ? window.cbDateForOffset(a.day) : null;
      if (!d || d.getMonth() !== m || d.getFullYear() !== y) return s;
      const svc = (data.services || []).find(x => x.id === a.serviceId);
      return s + (svc ? Number(svc.price) || 0 : 0);
    }, 0);
  }, [data.appointments, data.services]);

  // Semaine à venir, 7 prochains jours
  const todayOff = cbTodayOffset();
  const weekAhead = React.useMemo(() => {
    const days = [];
    const DAYS_SHORT = ["Dim", "Lun", "Mar", "Mer", "Jeu", "Ven", "Sam"];
    for (let i = 0; i < 7; i++) {
      const off = todayOff + i;
      const d = window.cbDateForOffset ? window.cbDateForOffset(off) : new Date();
      const appts = (data.appointments || []).filter(a => a.day === off);
      const ca = appts.reduce((s, a) => {
        const svc = (data.services || []).find(x => x.id === a.serviceId);
        return s + (svc ? Number(svc.price) || 0 : 0);
      }, 0);
      days.push({
        off, label: DAYS_SHORT[d.getDay()], dayNum: d.getDate(),
        count: appts.length, ca, isToday: i === 0,
      });
    }
    return days;
  }, [data.appointments, data.services, todayOff]);
  const weekMaxCA = Math.max(1, ...weekAhead.map(d => d.ca));
  const weekTotalCa = weekAhead.reduce((s, d) => s + d.ca, 0);
  const weekTotalRdv = weekAhead.reduce((s, d) => s + d.count, 0);

  // Activité récente, 5 derniers RDV faits
  const recentActivity = React.useMemo(() => {
    return (data.appointments || [])
      .filter(a => a.done)
      .map(a => {
        const c = findClient(data, a.clientId);
        const svc = (data.services || []).find(x => x.id === a.serviceId);
        return { a, c, svc };
      })
      .sort((x, y) => (y.a.day - x.a.day) || (y.a.h - x.a.h))
      .slice(0, 5);
  }, [data.appointments, data.services, data.clients]);

  // Cercle de progression de la journée, circumference 2πr
  const PROG_R = 28;
  const PROG_C = 2 * Math.PI * PROG_R;

  // === ACCUEIL MOBILE EXPRESS ===
  // Vue dédiée, ramassée, pensée pour le pouce : carte salutation + KPIs + actions rapides + journée + note.
  const isMobile = useIsMobile(900);
  if (isMobile) {
    return (
      <div style={{ display: "flex", flexDirection: "column", gap: 14, paddingBottom: 24 }}>
        {/* Bandeau pause / vacances si actif */}
        {pause && (
          <div style={{
            padding: "10px 14px",
            background: "var(--surface)", border: "1px solid var(--line)",
            borderLeft: "3px solid var(--accent)",
            borderRadius: 10, display: "flex", alignItems: "center", gap: 9,
          }}>
            <span style={{ fontSize: 14 }}>🌴</span>
            <div style={{ fontSize: 12.5, color: "var(--ink-2)", lineHeight: 1.4, flex: 1, minWidth: 0 }}>
              <strong style={{ fontWeight: 580 }}>En pause</strong>
              <span style={{ color: "var(--ink-4)" }}> · réservations en ligne suspendues</span>
            </div>
          </div>
        )}

        {/* Objectif du mois (style quest du preview) */}
        <AppHomeGoalBanner monthCa={monthCa} compact/>

        {/* Insights de la semaine — phrases courtes computées sur les données */}
        <DashboardInsights data={data} todayCa={todayCa} weekTotalCa={weekTotalCa} weekTotalRdv={weekTotalRdv}/>

        {/* Mini KPIs : 3 mini-cartes, À faire / Faits / CA prévu */}
        <div style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 8 }}>
          {[
            { label: "À faire", value: todayRemaining, icon: "calendar", tone: "oklch(60% 0.20 275)" },
            { label: "Faits",   value: todayDone,      icon: "check",    tone: "oklch(52% 0.22 285)" },
            { label: "CA jour", value: `${todayCa} €`, icon: "euro",     tone: "var(--accent)" },
          ].map(kpi => (
            <div key={kpi.label} style={{
              padding: "11px 12px",
              background: "var(--surface)",
              border: `1px solid color-mix(in oklab, ${kpi.tone} 18%, var(--line))`,
              borderRadius: 12, minWidth: 0,
              display: "flex", flexDirection: "column", gap: 4,
            }}>
              <span style={{
                width: 24, height: 24, borderRadius: 7,
                background: `color-mix(in oklab, ${kpi.tone} 14%, transparent)`,
                color: kpi.tone,
                display: "inline-flex", alignItems: "center", justifyContent: "center",
              }}>
                <Icon name={kpi.icon} size={12}/>
              </span>
              <div style={{
                fontFamily: "var(--ff-display)", fontSize: 17, fontWeight: 600,
                letterSpacing: "-0.02em", color: "var(--ink)", lineHeight: 1,
                whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis",
              }}>{kpi.value}</div>
              <div style={{ fontSize: 10.5, color: "var(--ink-4)", fontWeight: 540 }}>{kpi.label}</div>
            </div>
          ))}
        </div>

        {/* Quick actions — sober white cards, accent indigo discret sur l'icône */}
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10 }}>
          {[
            { label: "Nouveau RDV",      icon: "calendar", act: () => openModal("appointment") },
            { label: "Nouvelle cliente", icon: "users",    act: () => openModal("client") },
          ].map(q => (
            <button key={q.label} onClick={q.act}
              className="cb-press-feedback"
              style={{
                display: "flex", alignItems: "center", gap: 10,
                padding: "13px 14px", background: "var(--surface)",
                border: "1px solid var(--line)", borderRadius: 12,
                cursor: "pointer", fontFamily: "inherit", textAlign: "left",
                transition: "border-color .15s, transform .12s",
              }}>
              <span style={{
                width: 34, height: 34, borderRadius: 9, flexShrink: 0,
                background: "var(--accent-soft)", color: "var(--accent-ink)",
                display: "flex", alignItems: "center", justifyContent: "center",
              }}>
                <Icon name={q.icon} size={15}/>
              </span>
              <span style={{ fontSize: 13.5, fontWeight: 620, letterSpacing: "-0.005em", color: "var(--ink)" }}>
                {q.label}
              </span>
            </button>
          ))}
        </div>

        {/* Note du jour */}
        <DailyNote/>

        {/* Votre journée avec tampons */}
        <div style={{
          padding: 18,
          background: "var(--surface)", border: "1px solid var(--line)",
          borderTop: "3px solid var(--accent)",
          borderRadius: 14,
        }}>
          <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 12 }}>
            <h3 style={{ fontSize: 16, display: "flex", alignItems: "center", gap: 9, margin: 0 }}>
              <span style={{
                width: 24, height: 24, borderRadius: 7,
                background: "var(--accent-soft)",
                color: "var(--accent)",
                display: "inline-flex", alignItems: "center", justifyContent: "center",
              }}>
                <Icon name="calendar" size={13}/>
              </span>
              {greet.msg ? `${greet.msg}, votre journée` : "Votre journée"}
            </h3>
            <button className="btn btn-sm btn-ghost" onClick={() => setMod("agenda")} style={{ fontSize: 12, height: 28 }}>
              Semaine <Icon name="arrow" size={11}/>
            </button>
          </div>
          {todayAllSorted.length === 0 ? (
            <div style={{
              padding: 22, textAlign: "center", background: "var(--bg-alt)",
              borderRadius: 10, border: "1px dashed var(--line-strong)",
            }}>
              <Icon name="sparkle" size={18} style={{ color: "var(--ink-4)" }}/>
              <div style={{ marginTop: 6, fontSize: 13, color: "var(--ink-3)" }}>
                Pas de rendez-vous aujourd'hui. Bonne journée&nbsp;!
              </div>
              <button onClick={() => openModal("appointment")} className="btn btn-sm btn-accent"
                style={{ marginTop: 12, fontSize: 12 }}>
                <Icon name="plus" size={11}/> Ajouter un RDV
              </button>
            </div>
          ) : (
            <div style={{ display: "flex", flexDirection: "column", gap: 7 }}>
              {todayAllSorted.map(a => {
                const c = findClient(data, a.clientId);
                const s = findService(data, a.serviceId);
                const col = COLOR_MAP[a.color];
                const doneAt = a.completedAt
                  ? new Date(a.completedAt).toLocaleTimeString("fr-FR", { hour: "2-digit", minute: "2-digit" })
                  : null;
                return (
                  <div key={a.id} style={{
                    display: "flex", alignItems: "center", gap: 11,
                    padding: "10px 12px",
                    background: a.done ? "var(--bg-alt)" : col.bg,
                    borderLeft: `3px solid ${a.done ? "var(--sage)" : col.border}`,
                    borderRadius: 10,
                    minWidth: 0,
                    opacity: a.done ? 0.85 : 1,
                  }}>
                    <div style={{
                      fontVariantNumeric: "tabular-nums", fontSize: 13, fontWeight: 600,
                      color: a.done ? "var(--ink-4)" : col.ink, flexShrink: 0, whiteSpace: "nowrap",
                    }}>{fmtH(a.h)}</div>
                    {c && <Avatar client={c} size={30}/>}
                    <div style={{ flex: 1, minWidth: 0 }}>
                      <div style={{
                        fontWeight: 540, fontSize: 13.5, color: a.done ? "var(--ink-3)" : "var(--ink)",
                        whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis",
                        textDecoration: a.done ? "line-through" : "none",
                      }}>{c ? c.name : "—"}</div>
                      <div style={{
                        fontSize: 11.5, color: "var(--ink-3)", marginTop: 1,
                        whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis",
                      }}>{s ? s.name : ""}</div>
                    </div>
                    {a.done ? (
                      <>
                        <span style={{
                          display: "inline-flex", alignItems: "center", gap: 4,
                          padding: "3px 8px",
                          background: "var(--sage-soft)", color: "oklch(38% 0.10 160)",
                          border: "1px solid color-mix(in oklab, var(--sage) 25%, transparent)",
                          borderRadius: 999,
                          fontSize: 10.5, fontWeight: 700, letterSpacing: "0.02em", textTransform: "uppercase",
                          flexShrink: 0, whiteSpace: "nowrap",
                        }}>
                          <Icon name="check" size={10} stroke={2.6}/>
                          {doneAt ? doneAt : "Fait"}
                        </span>
                        <button
                          onClick={() => actions.undoAppointmentDone(a.id)}
                          className="btn btn-sm btn-ghost"
                          aria-label="Annuler" title="Retour en arrière"
                          style={{ height: 28, width: 28, padding: 0, flexShrink: 0, color: "var(--ink-3)" }}>
                          <Icon name="arrow" size={11} style={{ transform: "rotate(180deg)" }}/>
                        </button>
                      </>
                    ) : (
                      <button
                        onClick={() => actions.markAppointmentDone(a.id)}
                        className="btn btn-sm"
                        aria-label="Tamponner" title="Tamponner, fait & facturer"
                        style={{
                          background: "var(--ink)", color: "var(--bg)",
                          height: 30, width: 30, padding: 0, flexShrink: 0,
                          display: "inline-flex", alignItems: "center", justifyContent: "center",
                        }}>
                        <Icon name="check" size={13} stroke={2.4}/>
                      </button>
                    )}
                  </div>
                );
              })}
            </div>
          )}
        </div>
      </div>
    );
  }

  return (
    <>
      {/* Pause / vacances active, bandeau doux pinned tout en haut */}
      {pause && (
        <div style={{
          marginBottom: 14, padding: "10px 14px",
          background: "var(--surface)", border: "1px solid var(--line)",
          borderLeft: "3px solid var(--accent)",
          borderRadius: 10, display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap",
        }}>
          <span style={{ fontSize: 16 }}>🌴</span>
          <div style={{ fontSize: 13, color: "var(--ink-2)", lineHeight: 1.4, flex: 1, minWidth: 0 }}>
            <strong style={{ fontWeight: 580 }}>En pause</strong>
            <span style={{ color: "var(--ink-4)" }}>
              {" "}· De retour le {new Date(pause.end).toLocaleDateString("fr-FR", { day: "numeric", month: "long" })}, réservations en ligne suspendues.
            </span>
          </div>
          <button className="btn btn-sm btn-ghost"
            onClick={() => setMod("bookings")}
            style={{ fontSize: 12, height: 28, flexShrink: 0 }}>
            Modifier <Icon name="arrow" size={11}/>
          </button>
        </div>
      )}

      {/* Anniversaires clients du jour, pinned, very discrete */}
      {bdays.length > 0 && (
        <div style={{
          marginBottom: 14, padding: "10px 14px",
          background: "var(--surface)", border: "1px solid var(--line)",
          borderLeft: "3px solid var(--accent)",
          borderRadius: 10, display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap",
        }}>
          <span style={{ fontSize: 16 }}>🎂</span>
          <div style={{ fontSize: 13, color: "var(--ink-2)", lineHeight: 1.4, flex: 1, minWidth: 0 }}>
            <strong style={{ fontWeight: 580 }}>
              {bdays.length === 1 ? "Anniversaire aujourd'hui" : `${bdays.length} anniversaires aujourd'hui`}
            </strong>
            <span style={{ color: "var(--ink-4)" }}>
              {" "}· {bdays.map(c => c.name.split(" ")[0]).join(", ")}, un petit message ferait plaisir ✨
            </span>
          </div>
          <button className="btn btn-sm btn-ghost"
            onClick={() => setMod("clients")}
            style={{ fontSize: 12, height: 28, flexShrink: 0 }}>
            Ouvrir <Icon name="arrow" size={11}/>
          </button>
        </div>
      )}

      {/* Objectif du mois (style quest du preview) */}
      <AppHomeGoalBanner monthCa={monthCa}/>

      {/* Insights de la semaine — phrases courtes computées sur les données */}
      <DashboardInsights data={data} todayCa={todayCa} weekTotalCa={weekTotalCa} weekTotalRdv={weekTotalRdv}/>

      {/* Quick actions — sober white cards style Stripe/Linear.
          Touche indigo uniquement sur l'icône + border au hover. */}
      <div className="cb-home-quick" style={{
        display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 12, marginBottom: 20,
      }}>
        {[
          { label: "Nouveau RDV",     hint: "Bloquer un créneau",  icon: "calendar", act: () => openModal("appointment") },
          { label: "Nouveau client",  hint: "Créer une fiche",     icon: "users",    act: () => openModal("client") },
          { label: "Nouvelle facture",hint: "Émettre maintenant",  icon: "invoice",  act: () => openModal("invoice") },
          { label: "Ma page de RDV",  hint: "Lien public · QR",    icon: "eye",      act: () => setMod("page") },
        ].map(q => (
          <button key={q.label} onClick={q.act}
            className="cb-press-feedback cb-quick-card"
            style={{
              display: "flex", flexDirection: "column", alignItems: "flex-start", gap: 8,
              padding: "18px 18px", background: "var(--surface)",
              border: "1px solid var(--line)", borderRadius: 12,
              cursor: "pointer", fontFamily: "inherit", textAlign: "left",
              transition: "border-color .15s var(--ease,cubic-bezier(.22,1,.36,1)), transform .15s, box-shadow .2s",
            }}
            onMouseEnter={e => {
              e.currentTarget.style.borderColor = "var(--accent)";
              e.currentTarget.style.transform = "translateY(-2px)";
              e.currentTarget.style.boxShadow = "0 8px 22px -12px color-mix(in oklab, var(--accent) 50%, transparent)";
            }}
            onMouseLeave={e => {
              e.currentTarget.style.borderColor = "var(--line)";
              e.currentTarget.style.transform = "translateY(0)";
              e.currentTarget.style.boxShadow = "none";
            }}>
            <span style={{
              width: 38, height: 38, borderRadius: 9, flexShrink: 0,
              background: "var(--accent-soft)", color: "var(--accent-ink)",
              display: "inline-flex", alignItems: "center", justifyContent: "center",
            }}>
              <Icon name={q.icon} size={18}/>
            </span>
            <span style={{ fontSize: 14.5, fontWeight: 620, color: "var(--ink)", letterSpacing: "-0.005em" }}>
              {q.label}
            </span>
            <span style={{ fontSize: 12.5, color: "var(--ink-4)", marginTop: -2 }}>
              {q.hint}
            </span>
          </button>
        ))}
      </div>

      {/* Layout desktop : journée + note en haut, pulse en pleine largeur en bas */}
      <div className="cb-home-grid" style={{
        display: "grid", gridTemplateColumns: "1.4fr 1fr", gap: 18, alignItems: "start",
      }}>
      {/* Today's schedule — trait du haut + icône en indigo (palette de marque). */}
      <div className="cb-home-journee" data-empty={todayAllSorted.length === 0 ? "true" : "false"} style={{
        gridColumn: 1, gridRow: 1, padding: 22,
        background: "var(--surface)", border: "1px solid var(--line)",
        borderTop: "3px solid var(--accent)",
        borderRadius: 14,
      }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 14 }}>
          <h3 style={{ fontSize: 17, display: "flex", alignItems: "center", gap: 10 }}>
            <span style={{
              width: 26, height: 26, borderRadius: 8,
              background: "var(--accent-soft)",
              color: "var(--accent-ink)",
              display: "inline-flex", alignItems: "center", justifyContent: "center",
            }}>
              <Icon name="calendar" size={14}/>
            </span>
            Votre journée
          </h3>
          <button className="btn btn-sm btn-ghost" onClick={() => setMod("agenda")}>
            Voir la semaine <Icon name="arrow" size={12}/>
          </button>
        </div>
        {todayAllSorted.length === 0 ? (
          <div style={{
            padding: 28, textAlign: "center", background: "var(--bg-alt)",
            borderRadius: 10, border: "1px dashed var(--line-strong)",
          }}>
            <Icon name="sparkle" size={20} style={{ color: "var(--ink-4)" }}/>
            <div style={{ marginTop: 8, fontSize: 13.5, color: "var(--ink-3)" }}>
              Pas de rendez-vous aujourd'hui. Bonne journée&nbsp;!
            </div>
          </div>
        ) : (
          <div style={{ display: "flex", flexDirection: "column", gap: 7 }}>
            {todayAllSorted.map(a => {
              const c = findClient(data, a.clientId);
              const s = findService(data, a.serviceId);
              const col = COLOR_MAP[a.color];
              const doneAt = a.completedAt
                ? new Date(a.completedAt).toLocaleTimeString("fr-FR", { hour: "2-digit", minute: "2-digit" })
                : null;
              return (
                <div key={a.id} className="cb-home-rdv" style={{
                  display: "flex", alignItems: "center", gap: 13,
                  padding: "12px 14px",
                  background: a.done ? "var(--bg-alt)" : col.bg,
                  borderLeft: `3px solid ${a.done ? "var(--sage)" : col.border}`,
                  borderRadius: 11,
                  minWidth: 0,
                  opacity: a.done ? 0.85 : 1,
                  transition: "background .2s, opacity .2s",
                }}>
                  <div style={{
                    fontVariantNumeric: "tabular-nums", fontSize: 14, fontWeight: 600,
                    color: a.done ? "var(--ink-4)" : col.ink, flexShrink: 0, whiteSpace: "nowrap",
                  }}>{fmtH(a.h)}</div>
                  {c && <Avatar client={c} size={34}/>}
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{
                      fontWeight: 540, fontSize: 14.5, color: a.done ? "var(--ink-3)" : "var(--ink)",
                      whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis",
                      textDecoration: a.done ? "line-through" : "none",
                    }}>{c ? c.name : "—"}</div>
                    <div style={{
                      fontSize: 12.5, color: "var(--ink-3)", marginTop: 1,
                      whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis",
                    }}>{s ? s.name : ""}</div>
                  </div>
                  {/* Tampon « Fait » avec heure de validation OU prix + bouton */}
                  {a.done ? (
                    <>
                      <span style={{
                        display: "inline-flex", alignItems: "center", gap: 5,
                        padding: "4px 10px",
                        background: "var(--sage-soft)", color: "oklch(38% 0.10 160)",
                        border: "1px solid color-mix(in oklab, var(--sage) 25%, transparent)",
                        borderRadius: 999,
                        fontSize: 11.5, fontWeight: 700, letterSpacing: "0.03em", textTransform: "uppercase",
                        flexShrink: 0, whiteSpace: "nowrap",
                      }}>
                        <Icon name="check" size={12} stroke={2.6}/>
                        {doneAt ? `Fait à ${doneAt}` : "Fait"}
                      </span>
                      <button
                        onClick={() => actions.undoAppointmentDone(a.id)}
                        className="btn btn-sm btn-ghost"
                        aria-label="Annuler la facturation" title="Retour en arrière"
                        style={{ height: 32, width: 32, padding: 0, flexShrink: 0, color: "var(--ink-3)" }}>
                        <Icon name="arrow" size={13} style={{ transform: "rotate(180deg)" }}/>
                      </button>
                    </>
                  ) : (
                    <>
                      {s && (
                        <div style={{
                          fontSize: 13.5, fontWeight: 600, color: "var(--ink-2)",
                          fontVariantNumeric: "tabular-nums", flexShrink: 0,
                          whiteSpace: "nowrap",
                        }}>{fmtEUR(s.price)}</div>
                      )}
                      <button
                        onClick={() => actions.markAppointmentDone(a.id)}
                        className="btn btn-sm cb-home-rdv-btn"
                        aria-label="Tamponner, fait & facturer"
                        title="Tamponner, fait & facturer"
                        style={{
                          background: "var(--ink)", color: "var(--bg)",
                          height: 32, fontSize: 12.5, padding: "0 12px",
                          flexShrink: 0, gap: 6,
                        }}>
                        <Icon name="check" size={13} stroke={2.4}/>
                        <span className="cb-home-rdv-btn-label">Tamponner</span>
                      </button>
                    </>
                  )}
                </div>
              );
            })}
          </div>
        )}
        <style>{`
          @media (max-width: 560px) {
            .cb-home-rdv-btn-label { display: none; }
            .cb-home-rdv-btn { width: 30px; padding: 0 !important; justify-content: center; }
          }
          /* Mobile (<=900px) : rendu via la branche dédiée AppHome express, le layout desktop n'est pas affiché. */
        `}</style>
      </div>

      {/* Note du jour, coin haut-droit desktop, tout en haut mobile */}
      <div className="cb-home-note" style={{ gridColumn: 2, gridRow: 1 }}>
        <DailyNote/>
      </div>

      {/* Cette semaine — trait + icône en indigo (au lieu du violet lavande). */}
      <div className="cb-home-week" style={{
        gridColumn: "1 / -1", gridRow: 2,
        padding: "20px 22px", background: "var(--surface)",
        border: "1px solid var(--line)",
        borderTop: "3px solid var(--accent)",
        borderRadius: 14,
      }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-end", gap: 12, marginBottom: 16, flexWrap: "wrap" }}>
          <div>
            <h3 style={{ fontSize: 17, display: "flex", alignItems: "center", gap: 10, margin: 0 }}>
              <span style={{
                width: 28, height: 28, borderRadius: 9,
                background: "var(--accent-soft)",
                color: "var(--accent-ink)",
                display: "inline-flex", alignItems: "center", justifyContent: "center",
              }}>
                <Icon name="chart" size={15}/>
              </span>
              Cette semaine en un coup d'œil
            </h3>
            <div style={{ fontSize: 13, color: "var(--ink-4)", marginTop: 5 }}>
              {weekTotalRdv} RDV · {fmtEUR(weekTotalCa)} de CA prévu sur 7 jours
            </div>
          </div>
          <button className="btn btn-sm btn-ghost" onClick={() => setMod("agenda")}>
            Voir l'agenda <Icon name="arrow" size={12}/>
          </button>
        </div>
        {/* Bar chart 7 jours */}
        <div style={{ display: "grid", gridTemplateColumns: "repeat(7, 1fr)", gap: 8, alignItems: "stretch" }}>
          {weekAhead.map(d => {
            const h = d.ca > 0 ? Math.max(8, (d.ca / weekMaxCA) * 110) : 4;
            return (
              <button key={d.off}
                onClick={() => setMod("agenda")}
                title={`${d.label} ${d.dayNum}, ${d.count} RDV · ${fmtEUR(d.ca)}`}
                style={{
                  display: "flex", flexDirection: "column", alignItems: "stretch", gap: 6,
                  background: "transparent", border: "none", cursor: "pointer", padding: 0,
                  fontFamily: "inherit",
                }}>
                <div style={{
                  height: 130, display: "flex", alignItems: "flex-end", justifyContent: "center",
                }}>
                  <div style={{
                    width: "100%",
                    height: `${h}px`,
                    background: d.isToday
                      ? "linear-gradient(180deg, var(--accent) 0%, oklch(48% 0.22 295) 100%)"
                      : (d.count > 0
                          ? "linear-gradient(180deg, var(--accent-soft-2) 0%, var(--accent-soft) 100%)"
                          : "var(--bg-alt)"),
                    borderRadius: 8,
                    transition: "height .35s cubic-bezier(.22,1,.36,1)",
                    boxShadow: d.isToday ? "0 6px 16px -8px var(--accent)" : "none",
                  }}/>
                </div>
                <div style={{
                  textAlign: "center", padding: "4px 0",
                  borderRadius: 7,
                  background: d.isToday ? "var(--accent-soft)" : "transparent",
                }}>
                  <div style={{
                    fontSize: 10.5, color: d.isToday ? "var(--accent-ink)" : "var(--ink-4)",
                    fontWeight: 540, letterSpacing: "0.03em",
                  }}>{d.label}</div>
                  <div style={{
                    fontFamily: "var(--ff-display)", fontSize: 15, fontWeight: 600,
                    color: d.isToday ? "var(--accent-ink)" : "var(--ink)",
                    letterSpacing: "-0.02em", marginTop: 1,
                  }}>{d.dayNum}</div>
                  <div style={{
                    fontSize: 10.5, color: d.isToday ? "var(--accent-ink)" : "var(--ink-3)",
                    marginTop: 2, fontWeight: 540,
                  }}>{d.count > 0 ? `${d.count} RDV` : "—"}</div>
                </div>
              </button>
            );
          })}
        </div>
      </div>

      {/* HomePulse, col gauche row 3 */}
      <div className="cb-home-pulse" style={{ gridColumn: 1, gridRow: 3 }}>
        <HomePulse data={data} setMod={setMod}/>
      </div>

      {/* Activité récente — trait + icône en indigo (au lieu du sage). */}
      <div className="cb-home-activity" style={{
        gridColumn: 2, gridRow: 3,
        padding: "20px 22px", background: "var(--surface)",
        border: "1px solid var(--line)",
        borderTop: "3px solid var(--accent)",
        borderRadius: 14,
      }}>
        <h3 style={{ fontSize: 17, display: "flex", alignItems: "center", gap: 10, margin: 0, marginBottom: 14 }}>
          <span style={{
            width: 28, height: 28, borderRadius: 9,
            background: "var(--accent-soft)",
            color: "var(--accent-ink)",
            display: "inline-flex", alignItems: "center", justifyContent: "center",
          }}>
            <Icon name="check" size={15}/>
          </span>
          Activité récente
        </h3>
        {recentActivity.length === 0 ? (
          <div style={{
            padding: "24px 14px", textAlign: "center",
            background: "var(--bg-alt)", border: "1px dashed var(--line-strong)",
            borderRadius: 10, color: "var(--ink-4)", fontSize: 13,
          }}>
            Les RDV facturés apparaîtront ici.
          </div>
        ) : (
          <div style={{ display: "flex", flexDirection: "column", gap: 7 }}>
            {recentActivity.map(({ a, c, svc }) => {
              const d = window.cbDateForOffset ? window.cbDateForOffset(a.day) : new Date();
              const dayLbl = a.day === todayOff ? "Aujourd'hui"
                : a.day === todayOff - 1 ? "Hier"
                : d.toLocaleDateString("fr-FR", { weekday: "short", day: "numeric", month: "short" });
              return (
                <div key={a.id} style={{
                  display: "flex", alignItems: "center", gap: 10,
                  padding: "8px 10px", background: "var(--bg-alt)",
                  border: "1px solid var(--line)", borderRadius: 9,
                }}>
                  {c && <Avatar client={c} size={28}/>}
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{
                      fontSize: 12.5, fontWeight: 540, color: "var(--ink)",
                      overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
                    }}>{c ? c.name : "—"}</div>
                    <div style={{
                      fontSize: 11, color: "var(--ink-4)", marginTop: 1,
                      overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
                    }}>{dayLbl} · {svc ? svc.name : "RDV"}</div>
                  </div>
                  {svc && (
                    <div style={{
                      fontSize: 12, fontWeight: 580, color: "var(--accent)",
                      flexShrink: 0,
                    }}>+{fmtEUR(svc.price)}</div>
                  )}
                </div>
              );
            })}
          </div>
        )}
      </div>
      </div>{/* fin grid accueil */}
    </>
  );
};

/* Note rapide du jour, bloc post-it éditable, persisté par jour en localStorage */
const DailyNote = () => {
  const todayKey = new Date().toISOString().slice(0, 10);
  const storeKey = "cb_daily_note_" + todayKey;
  const [note, setNote] = React.useState(() => {
    try { return localStorage.getItem(storeKey) || ""; } catch { return ""; }
  });
  const [saved, setSaved] = React.useState(false);
  const saveTimer = React.useRef(null);

  const onChange = (v) => {
    setNote(v);
    setSaved(false);
    clearTimeout(saveTimer.current);
    saveTimer.current = setTimeout(() => {
      try {
        if (v.trim()) localStorage.setItem(storeKey, v);
        else localStorage.removeItem(storeKey);
      } catch {}
      setSaved(true);
      setTimeout(() => setSaved(false), 1600);
    }, 500);
  };

  return (
    <div style={{
      padding: 18,
      background: "linear-gradient(165deg, oklch(97% 0.05 95) 0%, oklch(98% 0.025 95) 100%)",
      border: "1px solid oklch(90% 0.06 95)",
      borderRadius: 14,
      position: "relative",
    }}>
      <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 10 }}>
        <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
          <span style={{ fontSize: 16 }}>📌</span>
          <h3 style={{ margin: 0, fontSize: 14.5, fontFamily: "var(--ff-display)", fontWeight: 580, letterSpacing: "-0.012em" }}>
            Note du jour
          </h3>
        </div>
        <span style={{
          fontSize: 11, color: "oklch(50% 0.10 95)", fontWeight: 540,
          opacity: saved ? 1 : 0, transition: "opacity .2s ease",
        }}>
          ✓ Enregistré
        </span>
      </div>
      <textarea
        value={note}
        onChange={e => onChange(e.target.value)}
        placeholder="Pense-bête, choses à faire, rappels… (effacé chaque jour)"
        style={{
          width: "100%", minHeight: 90, resize: "vertical",
          background: "transparent", border: "none", outline: "none",
          fontSize: 13.5, fontFamily: "inherit", color: "var(--ink)",
          lineHeight: 1.6,
        }}
      />
      <div style={{ fontSize: 10.5, color: "oklch(55% 0.08 95)", marginTop: 4 }}>
        Cette note ne concerne qu'aujourd'hui, elle repart à zéro demain.
      </div>
    </div>
  );
};

/* ONE interactive widget with CA (day/week/month) + playful "Et si…" sims. */
const HomePulse = ({ data, setMod }) => {
  const [tab, setTab] = React.useState("ca");

  const monthPaid = data.invoices.filter(i => i.paid).reduce((s, i) => s + i.amount, 0);
  const pending = data.invoices.filter(i => !i.paid).reduce((s, i) => s + i.amount, 0);
  const avgTicket = data.invoices.length > 0 ? monthPaid / data.invoices.length : 45;

  const [goal, setGoal] = React.useState(() => {
    const saved = +(localStorage.getItem("cb_goal_monthly") || 0);
    return saved > 0 ? saved : Math.max(4000, Math.ceil(monthPaid / 1000) * 1000 + 500);
  });
  React.useEffect(() => { localStorage.setItem("cb_goal_monthly", String(goal)); }, [goal]);
  const pctGoal = Math.min(100, (monthPaid / goal) * 100);
  const remaining = Math.max(0, goal - monthPaid);
  const rdvToReach = Math.ceil(remaining / Math.max(avgTicket, 1));

  const TABS = [
    { id: "ca",     label: "Cette semaine",    icon: "euro",    tone: "oklch(58% 0.18 270)" },
    { id: "goal",   label: "Objectif du mois", icon: "zap",     tone: "oklch(52% 0.22 285)" },
    { id: "whatif", label: "Et si…",            icon: "sparkle", tone: "var(--accent)" },
  ];
  const activeTone = (TABS.find(t => t.id === tab) || TABS[0]).tone;

  return (
    <div style={{
      background: "var(--surface)", border: "1px solid var(--line)",
      borderRadius: 14, overflow: "hidden",
      borderTop: `3px solid ${activeTone}`,
      transition: "border-color .25s ease",
    }}>
      <div style={{
        display: "flex", gap: 2, padding: 6,
        background: "var(--bg-alt)", borderBottom: "1px solid var(--line)",
        overflowX: "auto", WebkitOverflowScrolling: "touch",
      }}>
        {TABS.map(t => {
          const active = tab === t.id;
          return (
            <button key={t.id} onClick={() => setTab(t.id)} style={{
              flex: 1, minWidth: 110,
              padding: "9px 12px", fontSize: 12.5, fontWeight: active ? 600 : 520,
              display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 7,
              background: active ? "var(--surface)" : "transparent",
              color: active ? t.tone : "var(--ink-3)",
              boxShadow: active ? "var(--sh-1)" : "none",
              border: "none", borderRadius: 8, cursor: "pointer", fontFamily: "inherit",
              transition: "background .15s, color .15s",
            }}>
              <span style={{
                width: 22, height: 22, borderRadius: 7,
                background: active ? `color-mix(in oklab, ${t.tone} 14%, transparent)` : "transparent",
                color: active ? t.tone : "var(--ink-4)",
                display: "inline-flex", alignItems: "center", justifyContent: "center",
                flexShrink: 0,
                transition: "background .15s, color .15s",
              }}>
                <Icon name={t.icon} size={13}/>
              </span>
              <span>{t.label}</span>
            </button>
          );
        })}
      </div>

      <div style={{ padding: 20 }}>
        {tab === "ca" && (
          <PulseCA monthPaid={monthPaid} pending={pending} avgTicket={avgTicket}
            invoices={data.invoices}
            appointments={data.appointments || []}
            services={data.services || []}
            onMore={() => setMod("factures")}/>
        )}
        {tab === "goal" && (
          <PulseGoal goal={goal} setGoal={setGoal} monthPaid={monthPaid}
            pct={pctGoal} remaining={remaining} rdvToReach={rdvToReach} avgTicket={avgTicket}/>
        )}
        {tab === "whatif" && (
          <PulseWhatIf monthPaid={monthPaid} avgTicket={avgTicket} goal={goal}/>
        )}
      </div>
    </div>
  );
};

const PulseCA = ({ monthPaid, pending, avgTicket, invoices, appointments, services, onMore }) => {
  const [range, setRange] = React.useState("week"); // day | week | upcoming | month
  const [hover, setHover] = React.useState(null);

  // Valeur en € pour un offset jour donné = somme des prix des prestations
  // de tous les RDV de ce jour (passés ou futurs).
  const valueForDay = React.useCallback((off) => {
    let sum = 0;
    for (const a of appointments) {
      if (a.day !== off) continue;
      const svc = services.find(s => s.id === a.serviceId);
      if (svc) sum += Number(svc.price) || 0;
    }
    return Math.round(sum);
  }, [appointments, services]);

  const todayOff = window.cbTodayOffset ? window.cbTodayOffset() : 0;

  const series = React.useMemo(() => {
    if (range === "day") {
      // RDV d'aujourd'hui par heure (8h → 19h)
      return Array.from({ length: 12 }, (_, i) => {
        const hour = 8 + i;
        let s = 0;
        for (const a of appointments) {
          if (a.day !== todayOff) continue;
          if (Math.floor(a.h) === hour) {
            const svc = services.find(svc => svc.id === a.serviceId);
            if (svc) s += Number(svc.price) || 0;
          }
        }
        return Math.round(s);
      });
    }
    if (range === "week") {
      // 3 jours passés + aujourd'hui + 3 jours suivants
      return Array.from({ length: 7 }, (_, i) => valueForDay(todayOff - 3 + i));
    }
    if (range === "upcoming") {
      // Aujourd'hui + 6 prochains jours (focus prévisionnel)
      return Array.from({ length: 7 }, (_, i) => valueForDay(todayOff + i));
    }
    // 30 jours glissants : 7 passés + 23 à venir
    return Array.from({ length: 30 }, (_, i) => valueForDay(todayOff - 7 + i));
  }, [appointments, services, range, todayOff, valueForDay]);

  // Index du "aujourd'hui" dans la série, pour le repère visuel
  const todayIdx = range === "week" ? 3 : range === "upcoming" ? 0 : range === "month" ? 7 : -1;

  const total = series.reduce((a, b) => a + b, 0);
  const max = Math.max(1, ...series);
  const width = 300, height = 70;
  const pts = series.map((v, i) => [
    series.length === 1 ? width / 2 : (i / (series.length - 1)) * width,
    height - (v / max) * (height - 6) - 3,
  ]);
  const poly = pts.map(p => p.join(",")).join(" ");
  const area = `M 0 ${height} L ${poly.split(" ").join(" L ")} L ${width} ${height} Z`;

  // Libellé d'un point i de la série, basé sur son offset jour absolu
  const _hintForOffset = (off) => {
    const d = window.cbDateForOffset ? window.cbDateForOffset(off) : new Date();
    const days = window.CB_FR_DAYS_SHORT || ["Dim","Lun","Mar","Mer","Jeu","Ven","Sam"];
    const months = window.CB_FR_MONTHS_SHORT || ["janv.","févr.","mars","avril","mai","juin","juil.","août","sept.","oct.","nov.","déc."];
    return `${days[d.getDay()]} ${d.getDate()} ${months[d.getMonth()]}`;
  };
  const labelMap = {
    day:      { title: "Aujourd'hui",            hint: (i) => `${8 + i}h` },
    week:     { title: "Cette semaine",          hint: (i) => _hintForOffset(todayOff - 3 + i) },
    upcoming: { title: "Les 7 prochains jours",  hint: (i) => _hintForOffset(todayOff + i) },
    month:    { title: "30 jours glissants",     hint: (i) => _hintForOffset(todayOff - 7 + i) },
  };
  const L = labelMap[range];

  return (
    <div>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: 10, flexWrap: "wrap", marginBottom: 10 }}>
        <div style={{ fontSize: 12, color: "var(--ink-4)", fontWeight: 500 }}>{L.title}</div>
        <div style={{
          display: "inline-flex", padding: 3,
          background: "var(--bg-alt)", border: "1px solid var(--line)",
          borderRadius: 999, gap: 2,
        }}>
          {[["day", "Jour"], ["week", "Semaine"], ["upcoming", "À venir"], ["month", "Mois"]].map(([k, l]) => (
            <button key={k} onClick={() => { setRange(k); setHover(null); }} style={{
              padding: "4px 10px", fontSize: 11.5, fontWeight: 500,
              background: range === k ? "var(--surface)" : "transparent",
              color: range === k ? "var(--ink)" : "var(--ink-3)",
              boxShadow: range === k ? "var(--sh-1)" : "none",
              border: "none", borderRadius: 999, cursor: "pointer", fontFamily: "inherit",
              transition: "background .15s, color .15s",
            }}>{l}</button>
          ))}
        </div>
      </div>

      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-end", gap: 12, flexWrap: "wrap" }}>
        <div>
          <div style={{
            fontFamily: "var(--ff-display)", fontSize: "clamp(28px, 3.5vw, 36px)",
            fontWeight: 580, letterSpacing: "-0.03em", color: "var(--accent-ink)",
            fontVariantNumeric: "tabular-nums",
          }}>
            {hover != null ? fmtEUR(series[hover]) : fmtEUR(total)}
          </div>
          <div style={{ fontSize: 12, color: "var(--ink-4)", marginTop: 2 }}>
            {hover != null
              ? L.hint(hover)
              : range === "upcoming" ? "potentiel à venir"
              : range === "week"     ? "cette semaine (réel + prévu)"
              : range === "month"    ? "sur 30 jours"
              : "aujourd'hui"}
          </div>
        </div>
        <div style={{
          padding: "6px 10px", background: "var(--warn-soft)",
          color: "var(--warn-ink)", borderRadius: 8,
          fontSize: 12, fontWeight: 520,
        }}>
          {fmtEUR(pending)} en attente
        </div>
      </div>

      <svg viewBox={`0 0 ${width} ${height}`} style={{ width: "100%", height: 70, marginTop: 12, overflow: "visible" }}>
        <defs>
          <linearGradient id="pulseCaFill" x1="0" y1="0" x2="0" y2="1">
            <stop offset="0%" stopColor="var(--accent)" stopOpacity="0.25"/>
            <stop offset="100%" stopColor="var(--accent)" stopOpacity="0"/>
          </linearGradient>
          <style>{`
            @keyframes cbSparklineDraw { from { stroke-dashoffset: 1000; } to { stroke-dashoffset: 0; } }
            @keyframes cbSparklineFill { from { opacity: 0; } to { opacity: 1; } }
            .cb-sparkline-line { stroke-dasharray: 1000; animation: cbSparklineDraw 900ms cubic-bezier(.22,.61,.36,1) both; }
            .cb-sparkline-fill { animation: cbSparklineFill 600ms ease-out 300ms both; }
          `}</style>
        </defs>
        <path className="cb-sparkline-fill" key={`fill-${range}`} d={area} fill="url(#pulseCaFill)"/>
        {/* Repère "aujourd'hui", sépare le passé réel du prévisionnel */}
        {todayIdx > 0 && todayIdx < pts.length && (
          <line x1={pts[todayIdx][0]} y1={0} x2={pts[todayIdx][0]} y2={height}
            stroke="var(--ink-4)" strokeWidth="1" strokeDasharray="2 3" opacity="0.5"/>
        )}
        <polyline className="cb-sparkline-line" key={`line-${range}`}
          points={poly} fill="none" stroke="var(--accent)" strokeWidth="2.2"
          strokeLinecap="round" strokeLinejoin="round"/>
        {pts.map(([x, y], i) => (
          <g key={i}>
            <circle cx={x} cy={y} r={hover === i ? 4 : 0}
              fill="var(--accent)" stroke="white" strokeWidth="2"
              style={{ transition: "r .15s" }}/>
            <rect x={x - width / (pts.length * 2)} y={0} width={width / pts.length} height={height}
              fill="transparent"
              onMouseEnter={() => setHover(i)} onMouseLeave={() => setHover(null)}
              onTouchStart={() => setHover(i)} onTouchEnd={() => setHover(null)}
              style={{ cursor: "pointer" }}/>
          </g>
        ))}
      </svg>

      <button onClick={onMore} className="btn btn-sm btn-ghost" style={{ marginTop: 10, height: 30, fontSize: 12 }}>
        Voir les factures <Icon name="arrow" size={12}/>
      </button>
    </div>
  );
};

/* "Et si j'ajoute X prestations ce mois ?" */
const PulseWhatIf = ({ monthPaid, avgTicket, goal }) => {
  const [extra, setExtra] = React.useState(5);
  const projected = monthPaid + extra * avgTicket;
  const pct = Math.min(100, (projected / goal) * 100);
  const hours = Math.round(extra * 1.1);
  return (
    <div>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", flexWrap: "wrap", gap: 8 }}>
        <div style={{ fontSize: 12, color: "var(--ink-4)", fontWeight: 500 }}>
          Et si je fais
        </div>
        <div style={{
          fontFamily: "var(--ff-display)", fontSize: "clamp(24px, 3vw, 30px)",
          fontWeight: 580, letterSpacing: "-0.025em", color: "var(--accent-ink)",
          fontVariantNumeric: "tabular-nums",
        }}>
          +{extra} <span style={{ fontSize: 13, color: "var(--ink-3)", fontWeight: 450, fontFamily: "var(--ff-text)" }}>prestations</span>
        </div>
      </div>
      <input type="range" min={1} max={40} step={1}
        value={extra} onChange={e => setExtra(+e.target.value)}
        style={{ width: "100%", accentColor: "var(--accent)", cursor: "pointer", marginTop: 10 }}/>

      <div style={{
        marginTop: 14, display: "grid",
        gridTemplateColumns: "repeat(3, 1fr)", gap: 10,
      }} className="app-col-3">
        {[
          { l: "CA ajouté",    v: fmtEUR(extra * avgTicket), sub: `${extra} × ${fmtEUR(avgTicket)}` },
          { l: "Nouveau total", v: fmtEUR(projected),        sub: pct >= 100 ? "objectif atteint 🎉" : `${Math.round(pct)}% de l'objectif` },
          { l: "Temps estimé",  v: `${hours} h`,             sub: "prestation + pause" },
        ].map((x, i) => (
          <div key={i} style={{
            padding: "12px 14px", background: "var(--bg-alt)",
            border: "1px solid var(--line)", borderRadius: 10,
          }}>
            <div style={{ fontSize: 11, color: "var(--ink-4)", fontWeight: 500 }}>{x.l}</div>
            <div style={{
              fontFamily: "var(--ff-display)", fontSize: 18, fontWeight: 580,
              letterSpacing: "-0.025em", marginTop: 4, fontVariantNumeric: "tabular-nums",
            }}>{x.v}</div>
            <div style={{ fontSize: 10.5, color: "var(--ink-4)", marginTop: 2 }}>{x.sub}</div>
          </div>
        ))}
      </div>
    </div>
  );
};

const PulseGoal = ({ goal, setGoal, monthPaid, pct, remaining, rdvToReach, avgTicket }) => (
  <div>
    <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", flexWrap: "wrap", gap: 8 }}>
      <div style={{ fontSize: 12, color: "var(--ink-4)", fontWeight: 500 }}>Objectif du mois</div>
      <div style={{
        fontFamily: "var(--ff-display)", fontSize: "clamp(22px, 3vw, 28px)",
        fontWeight: 580, letterSpacing: "-0.025em", color: "var(--accent-ink)",
      }}>
        {fmtEUR(goal)}
      </div>
    </div>
    <input type="range" min={1000} max={12000} step={100}
      value={goal} onChange={e => setGoal(+e.target.value)}
      style={{ width: "100%", accentColor: "var(--accent)", cursor: "pointer", marginTop: 8 }}/>

    <div style={{
      height: 12, background: "var(--bg-alt)", borderRadius: 999,
      overflow: "hidden", marginTop: 10,
    }}>
      <div style={{
        width: `${pct}%`, height: "100%",
        background: "linear-gradient(90deg, var(--accent) 0%, oklch(45% 0.22 288) 100%)",
        borderRadius: 999, transition: "width .4s ease",
      }}/>
    </div>
    <div style={{
      marginTop: 8, display: "flex", justifyContent: "space-between",
      fontSize: 12.5, color: "var(--ink-2)",
    }}>
      <span><strong style={{ fontVariantNumeric: "tabular-nums" }}>{fmtEUR(monthPaid)}</strong> encaissés</span>
      <span style={{ color: "var(--ink-4)" }}>
        {pct >= 100 ? "🎉 Objectif atteint" : `${Math.round(pct)}%`}
      </span>
    </div>
    <div style={{
      marginTop: 12, padding: "10px 12px",
      background: "var(--bg-alt)", borderRadius: 10,
      fontSize: 13, color: "var(--ink-2)", lineHeight: 1.5,
    }}>
      {pct >= 100
        ? <>Objectif dépassé de <strong>{fmtEUR(monthPaid - goal)}</strong> ✨</>
        : <>Encore <strong>{fmtEUR(remaining)}</strong> · environ {rdvToReach} prestations au panier moyen de {fmtEUR(avgTicket)}.</>
      }
    </div>
  </div>
);


/* ================================================================
   AGENDA, click cell to add, click appointment to delete / mark done
================================================================ */
const AppAgenda = (props) => {
  const isMobile = useIsMobile();
  return isMobile ? <AppAgendaMobile {...props}/> : <AppAgendaDesktop {...props}/>;
};

// Dropdown « Exporter mon agenda » : 3 boutons logos colorés
// (Google / Apple / Outlook) qui génèrent les liens/ICS pour synchroniser
// l'agenda du pro avec son calendrier perso. Pour Apple/ICS, on inclut
// TOUS les RDV à venir (le fichier sert d'import en masse). Pour Google
// et Outlook, deeplink limité à 1 event (limitation des APIs deeplink),
// donc on propose l'option Apple pour l'export complet.
const CalendarExportDropdown = ({ appointments, clients, services, business, open, setOpen }) => {
  const ref = React.useRef(null);
  React.useEffect(() => {
    if (!open) return;
    const onDown = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener("mousedown", onDown);
    return () => document.removeEventListener("mousedown", onDown);
  }, [open, setOpen]);

  // Construit les events ICS pour TOUS les RDV à venir (>= aujourd'hui).
  const buildEvents = React.useCallback(() => {
    const today = cbTodayOffset();
    const future = (appointments || []).filter(a => a.day >= today);
    return future.map(a => {
      const c = (clients || []).find(x => x.id === a.clientId);
      const s = (services || []).find(x => x.id === a.serviceId);
      const baseDate = cbDateForOffset(a.day);
      if (!baseDate) return null;
      const hh = Math.floor(a.h);
      const mm = Math.round((a.h - hh) * 60);
      const start = new Date(baseDate);
      start.setHours(hh, mm, 0, 0);
      const durHours = (s && s.duration) ? s.duration : 1;
      const end = new Date(start.getTime() + durHours * 60 * 60 * 1000);
      const title = `${s ? s.name : "RDV"}${c ? ` — ${c.name}` : ""}`;
      const descLines = [
        c ? `Client : ${c.name}` : null,
        c && c.phone ? `Tél : ${c.phone}` : null,
        s ? `Prestation : ${s.name}` : null,
        s ? `Prix : ${s.price} €` : null,
      ].filter(Boolean).join("\n");
      const business_ = business || {};
      const location = [business_.address, business_.postalCode, business_.city].filter(Boolean).join(", ");
      return {
        uid: `cb-rdv-${a.id}@clientbase.fr`,
        start, end, title, description: descLines, location,
      };
    }).filter(Boolean);
  }, [appointments, clients, services, business]);

  const exportAll = () => {
    const events = buildEvents();
    if (events.length === 0) {
      showToast("Aucun RDV à venir à exporter", "warn");
      setOpen(false);
      return;
    }
    const ics = window.cbBuildICS(events);
    const filename = `clientbase-agenda-${cbYmd(new Date())}`;
    const ok = window.cbDownloadICS(filename, ics);
    if (ok) {
      showToast(`${events.length} RDV exporté${events.length > 1 ? "s" : ""} en .ics 📅`);
      setOpen(false);
    } else {
      showToast("Téléchargement bloqué par le navigateur", "warn");
    }
  };

  const openGoogleHelp = () => {
    setOpen(false);
    // Google : pas de bulk-import via deeplink. On télécharge l'ICS et
    // on ouvre Google Calendar pour que l'utilisateur l'importe.
    const events = buildEvents();
    if (events.length === 0) { showToast("Aucun RDV à exporter", "warn"); return; }
    const ics = window.cbBuildICS(events);
    window.cbDownloadICS(`clientbase-agenda-${cbYmd(new Date())}`, ics);
    showToast("Fichier .ics téléchargé. Importez-le dans Google Calendar →");
    setTimeout(() => {
      window.open("https://calendar.google.com/calendar/u/0/r/settings/export", "_blank", "noopener");
    }, 600);
  };

  const openOutlookHelp = () => {
    setOpen(false);
    const events = buildEvents();
    if (events.length === 0) { showToast("Aucun RDV à exporter", "warn"); return; }
    const ics = window.cbBuildICS(events);
    window.cbDownloadICS(`clientbase-agenda-${cbYmd(new Date())}`, ics);
    showToast("Fichier .ics téléchargé. Importez-le dans Outlook →");
    setTimeout(() => {
      window.open("https://outlook.live.com/calendar/0/options/calendar/SharedCalendars", "_blank", "noopener");
    }, 600);
  };

  return (
    <div ref={ref} style={{ position: "relative", marginLeft: "auto" }}>
      {/* Bouton « Exporter » mis en avant avec les 3 micro-logos
          (Apple / Google / Outlook) visibles d'emblée. Donne le signal
          que la fonction existe sans avoir à ouvrir le dropdown. */}
      <button onClick={() => setOpen(v => !v)}
        className="cb-press-feedback"
        style={{
          display: "inline-flex", alignItems: "center", gap: 8,
          padding: "6px 12px 6px 8px", height: 32,
          background: "var(--surface)",
          border: "1px solid var(--accent-soft-2)",
          borderRadius: 999,
          cursor: "pointer", fontFamily: "inherit",
          fontSize: 12.5, fontWeight: 540,
          color: "var(--accent-ink)",
          transition: "background .15s ease, border-color .15s ease, box-shadow .18s ease",
        }}
        onMouseEnter={e => {
          e.currentTarget.style.background = "var(--accent-soft)";
          e.currentTarget.style.boxShadow = "0 4px 12px -6px var(--accent)";
        }}
        onMouseLeave={e => {
          e.currentTarget.style.background = "var(--surface)";
          e.currentTarget.style.boxShadow = "none";
        }}>
        {/* Micro-logos brand officiels (SVG) empilés en avatars 20px. */}
        <span aria-hidden style={{
          display: "inline-flex", marginRight: 2,
        }}>
          {[
            { icon: window.CbCalIconApple   && React.createElement(window.CbCalIconApple,   { size: 12 }), bg: "#000",         offset: 0 },
            { icon: window.CbCalIconGoogle  && React.createElement(window.CbCalIconGoogle,  { size: 12 }), bg: "#FFFFFF",      offset: -7 },
            { icon: window.CbCalIconOutlook && React.createElement(window.CbCalIconOutlook, { size: 14 }), bg: "transparent",  offset: -7 },
          ].map((l, i) => (
            <span key={i} style={{
              width: 20, height: 20, borderRadius: 50,
              background: l.bg,
              display: "inline-flex", alignItems: "center", justifyContent: "center",
              border: "1.5px solid var(--surface)",
              marginLeft: l.offset,
              boxShadow: "0 2px 4px -1px rgba(0,0,0,0.15)",
              overflow: "hidden",
            }}>{l.icon}</span>
          ))}
        </span>
        Exporter mon agenda
        <svg width="10" height="10" viewBox="0 0 24 24" fill="none"
          stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round"
          style={{
            transform: open ? "rotate(180deg)" : "rotate(0)",
            transition: "transform .2s ease",
          }}>
          <path d="M6 9l6 6 6-6"/>
        </svg>
      </button>
      {open && (
        <div role="menu" style={{
          position: "absolute", top: "calc(100% + 6px)", right: 0,
          minWidth: 240,
          background: "var(--surface)", border: "1px solid var(--line)",
          borderRadius: 12, padding: 6,
          boxShadow: "0 20px 40px -16px rgba(15,18,30,0.18), 0 6px 14px -6px rgba(15,18,30,0.08)",
          // z-index élevé pour passer au-dessus de la grille agenda + de
          // toute autre carte. 9000 reste sous nos modals (9999).
          zIndex: 9000,
          animation: "cbExportIn .18s cubic-bezier(.22,1,.36,1)",
        }}>
          <div style={{
            padding: "8px 10px 6px",
            fontSize: 10.5, fontWeight: 700, color: "var(--ink-4)",
            textTransform: "uppercase", letterSpacing: "0.06em",
          }}>
            Exporter vers
          </div>
          <CalendarExportItem onClick={openGoogleHelp}
            logo={window.CbCalIconGoogle && React.createElement(window.CbCalIconGoogle, { size: 18 })}
            bg="#FFFFFF"
            label="Google Calendar"
            hint="Télécharge + ouvre import"/>
          <CalendarExportItem onClick={exportAll}
            logo={window.CbCalIconApple && React.createElement(window.CbCalIconApple, { size: 18 })}
            bg="#000" fg="#fff"
            label="Apple Calendar"
            hint="Double-clic ouvre l'app"/>
          <CalendarExportItem onClick={openOutlookHelp}
            logo={window.CbCalIconOutlook && React.createElement(window.CbCalIconOutlook, { size: 22 })}
            bg="transparent"
            label="Outlook"
            hint="Télécharge + ouvre import"/>
          <CalendarExportItem onClick={exportAll}
            logo={
              <svg width="16" height="16" viewBox="0 0 24 24" fill="none"
                stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3"/>
              </svg>
            }
            bg="var(--bg-alt)" fg="var(--ink-2)"
            label="Télécharger .ics"
            hint="Format universel"/>
          <style>{`
            @keyframes cbExportIn {
              from { opacity: 0; transform: translateY(-6px) scale(0.97); }
              to   { opacity: 1; transform: translateY(0) scale(1); }
            }
          `}</style>
        </div>
      )}
    </div>
  );
};

const CalendarExportItem = ({ onClick, logo, bg, fg, label, hint }) => {
  const [hover, setHover] = React.useState(false);
  return (
    <button onClick={onClick}
      onMouseEnter={() => setHover(true)}
      onMouseLeave={() => setHover(false)}
      style={{
        display: "flex", alignItems: "center", gap: 10,
        width: "100%", padding: "9px 10px",
        background: hover ? "var(--bg-alt)" : "transparent",
        border: "none", borderRadius: 8,
        cursor: "pointer", fontFamily: "inherit",
        textAlign: "left",
      }}>
      <span aria-hidden style={{
        width: 28, height: 28, borderRadius: 7, flexShrink: 0,
        background: bg, color: fg,
        display: "inline-flex", alignItems: "center", justifyContent: "center",
        fontWeight: 700, fontSize: 14, fontFamily: "var(--ff-display)",
      }}>{logo}</span>
      <span style={{ flex: 1, minWidth: 0 }}>
        <span style={{ display: "block", fontSize: 13, fontWeight: 540, color: "var(--ink)" }}>{label}</span>
        <span style={{ display: "block", fontSize: 11, color: "var(--ink-4)", marginTop: 1 }}>{hint}</span>
      </span>
    </button>
  );
};

const AppAgendaMobile = ({ data, actions, openModal }) => {
  const todayOff = cbTodayOffset();
  const [day, setDay] = React.useState(todayOff);
  const [selected, setSelected] = React.useState(null);

  const anchor = (() => { const d = new Date(); d.setHours(0,0,0,0); return d; })();
  const DAY_NAMES_SHORT = ["Dim", "Lun", "Mar", "Mer", "Jeu", "Ven", "Sam"];
  const MONTHS_SHORT = ["jan", "fév", "mars", "avr", "mai", "juin", "juil", "août", "sept", "oct", "nov", "déc"];

  // Offset (jours, relatif à aujourd'hui) du lundi de la semaine courante affichée.
  // 0 = lundi de cette semaine, -7 = semaine précédente, +7 = semaine suivante.
  const todayDow = anchor.getDay(); // 0=Dim ... 6=Sam
  const mondayOfsToday = -((todayDow + 6) % 7); // décalage en jours pour atteindre le lundi
  const [weekStart, setWeekStart] = React.useState(0);

  const weekDays = React.useMemo(() => {
    const days = [];
    for (let i = 0; i < 7; i++) {
      const offset = mondayOfsToday + weekStart + i;
      const d = new Date(anchor);
      d.setDate(anchor.getDate() + offset);
      days.push({
        idx: todayOff + offset,
        label: DAY_NAMES_SHORT[d.getDay()],
        dayNum: d.getDate(),
        monthLabel: MONTHS_SHORT[d.getMonth()],
        isToday: offset === 0,
        isPast: offset < 0,
        date: d,
      });
    }
    return days;
  }, [weekStart]);

  // S'assurer que `day` correspond à un jour visible, sinon prendre le premier
  React.useEffect(() => {
    if (!weekDays.find(d => d.idx === day)) {
      setDay(weekDays[0].idx);
    }
    // eslint-disable-next-line
  }, [weekStart]);

  const dayAppts = data.appointments
    .filter(a => a.day === day)
    .sort((a, b) => a.h - b.h);

  // Libellé de la semaine, « 12 → 18 mai » (mois affiché une seule fois si même mois)
  const firstD = weekDays[0];
  const lastD = weekDays[6];
  const weekRangeLabel = firstD.monthLabel === lastD.monthLabel
    ? `${firstD.dayNum} → ${lastD.dayNum} ${lastD.monthLabel}`
    : `${firstD.dayNum} ${firstD.monthLabel} → ${lastD.dayNum} ${lastD.monthLabel}`;
  const isThisWeek = weekStart === 0;

  return (
    <>
      {/* Navigateur semaine, semaine précédente / actuelle / suivante */}
      <div style={{
        display: "flex", alignItems: "center", justifyContent: "space-between",
        gap: 8, marginBottom: 10,
        padding: "8px 10px",
        background: "var(--surface)", border: "1px solid var(--line)",
        borderRadius: 12,
      }}>
        <button onClick={() => setWeekStart(weekStart - 7)} aria-label="Semaine précédente"
          style={{
            width: 36, height: 36, padding: 0, fontSize: 18,
            background: "transparent", border: "1px solid var(--line)",
            borderRadius: 10, color: "var(--ink-2)", cursor: "pointer", fontFamily: "inherit",
          }}>‹</button>
        <div style={{ textAlign: "center", flex: 1, minWidth: 0 }}>
          <div style={{
            fontSize: 13.5, fontWeight: 580, color: "var(--ink)",
            letterSpacing: "-0.01em",
          }}>{weekRangeLabel}</div>
          {!isThisWeek && (
            <button onClick={() => setWeekStart(0)}
              style={{
                fontSize: 11.5, color: "var(--accent-ink)", fontWeight: 600,
                background: "transparent", border: "none", cursor: "pointer", fontFamily: "inherit",
                marginTop: 2, padding: 0,
              }}>Cette semaine</button>
          )}
        </div>
        <button onClick={() => setWeekStart(weekStart + 7)} aria-label="Semaine suivante"
          style={{
            width: 36, height: 36, padding: 0, fontSize: 18,
            background: "transparent", border: "1px solid var(--line)",
            borderRadius: 10, color: "var(--ink-2)", cursor: "pointer", fontFamily: "inherit",
          }}>›</button>
      </div>

      {/* Grille semaine, 7 jours visibles d'un coup, plus de scroll horizontal */}
      <div style={{
        display: "grid", gridTemplateColumns: "repeat(7, 1fr)",
        gap: 5, marginBottom: 14,
      }}>
        {weekDays.map((d) => {
          const n = data.appointments.filter(a => a.day === d.idx).length;
          const isActive = day === d.idx;
          // Repère jour courant : bordure accent 2px + ring lumineux,
          // plus marqué que l'ancien fond accent-soft seul.
          const todayBorder = isActive
            ? "var(--accent)"
            : d.isToday ? "var(--accent)" : "var(--line)";
          const todayShadow = d.isToday && !isActive
            ? "0 0 0 2px color-mix(in oklab, var(--accent) 28%, transparent)"
            : "none";
          return (
            <button key={d.idx}
              onClick={() => { setDay(d.idx); setSelected(null); }}
              style={{
                position: "relative",
                padding: "8px 4px",
                background: isActive ? "var(--accent)" : d.isToday ? "var(--accent-soft)" : "var(--surface)",
                color: isActive ? "white" : d.isToday ? "var(--accent-ink)" : d.isPast ? "var(--ink-4)" : "var(--ink-2)",
                border: `${d.isToday && !isActive ? 2 : 1}px solid ${todayBorder}`,
                boxShadow: todayShadow,
                borderRadius: 10, cursor: "pointer",
                fontFamily: "inherit",
                display: "flex", flexDirection: "column", gap: 2, alignItems: "center",
                transition: "background .15s, border-color .15s, box-shadow .15s, transform .12s",
              }}
              onMouseDown={e => { e.currentTarget.style.transform = "scale(0.96)"; }}
              onMouseUp={e => { e.currentTarget.style.transform = "scale(1)"; }}
              onMouseLeave={e => { e.currentTarget.style.transform = "scale(1)"; }}
              >
              <span style={{ fontSize: 10, opacity: isActive ? 0.9 : 1, color: isActive ? "white" : "var(--ink-4)" }}>
                {d.label}
              </span>
              <span style={{ fontFamily: "var(--ff-display)", fontSize: 16, fontWeight: 600, letterSpacing: "-0.02em" }}>
                {d.dayNum}
              </span>
              {n > 0 ? (
                <span style={{
                  fontSize: 9, fontWeight: 600,
                  background: isActive ? "rgba(255,255,255,0.25)" : "var(--accent-soft)",
                  color: isActive ? "white" : "var(--accent-ink)",
                  padding: "1px 5px", borderRadius: 10, marginTop: 1,
                }}>{n}</span>
              ) : (
                <span style={{ height: 14 }}/>
              )}
              {/* Petit point sous la case si c'est aujourd'hui, visible aussi quand la case n'est pas active */}
              {d.isToday && !isActive && (
                <span aria-hidden style={{
                  position: "absolute", bottom: -6, left: "50%", transform: "translateX(-50%)",
                  width: 5, height: 5, borderRadius: "50%", background: "var(--accent)",
                  boxShadow: "0 0 0 2px var(--bg)",
                }}/>
              )}
            </button>
          );
        })}
      </div>


      {/* Appointments list */}
      {dayAppts.length === 0 ? (
        <div style={{
          padding: "36px 20px", textAlign: "center",
          background: "var(--surface)", border: "1px dashed var(--line-strong)",
          borderRadius: 12, color: "var(--ink-4)",
        }}>
          <div style={{ fontSize: 14 }}>Aucun rendez-vous ce jour.</div>
          <button className="btn btn-sm btn-ghost" style={{ marginTop: 12 }} onClick={() => openModal("appointment", { day })}>
            <Icon name="plus" size={13}/> Ajouter le premier
          </button>
        </div>
      ) : (
        <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
          {dayAppts.map(a => {
            const c = findClient(data, a.clientId);
            const s = findService(data, a.serviceId);
            const col = COLOR_MAP[a.color] || COLOR_MAP.accent;
            const isSelected = selected === a.id;
            return (
              <div key={a.id} style={{
                background: "var(--surface)",
                border: "1px solid var(--line)",
                borderLeft: `3px solid ${col.border}`,
                borderRadius: 10,
                overflow: "hidden",
              }}>
                <button onClick={() => setSelected(isSelected ? null : a.id)} style={{
                  width: "100%", padding: "12px 14px",
                  display: "flex", alignItems: "center", gap: 12,
                  background: "transparent", border: "none", cursor: "pointer",
                  textAlign: "left", fontFamily: "inherit",
                }}>
                  <div style={{
                    minWidth: 58, padding: "6px 4px",
                    background: col.bg, color: col.ink,
                    borderRadius: 8, fontVariantNumeric: "tabular-nums", fontSize: 13,
                    fontWeight: 600, textAlign: "center", lineHeight: 1.25,
                  }}>
                    <div>{fmtH(a.h)}</div>
                    <div style={{ fontSize: 10, opacity: 0.7, marginTop: 1 }}>{fmtH(a.h + a.d)}</div>
                  </div>
                  {c && <Avatar client={c} size={36}/>}
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{
                      fontWeight: 520, fontSize: 14, color: "var(--ink)",
                      overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
                      textDecoration: a.done ? "line-through" : "none",
                      opacity: a.done ? 0.7 : 1,
                    }}>
                      {c ? c.name : "—"}
                    </div>
                    <div style={{ fontSize: 12.5, color: "var(--ink-3)", marginTop: 2, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
                      {s ? s.name : ""}{s ? ` · ${fmtEUR(s.price)}` : ""}
                    </div>
                  </div>
                  {a.done ? (
                    <span style={{
                      padding: "3px 8px", fontSize: 10.5, fontWeight: 600,
                      background: "var(--sage-soft)", color: "oklch(38% 0.08 160)",
                      borderRadius: 999, textTransform: "uppercase", letterSpacing: "0.04em",
                      display: "inline-flex", alignItems: "center", gap: 4,
                    }}>
                      <Icon name="check" size={10} stroke={2.8}/> Fait
                    </span>
                  ) : (
                    <Icon name="arrow" size={14} style={{
                      color: "var(--ink-4)",
                      transform: isSelected ? "rotate(90deg)" : "rotate(0deg)",
                      transition: "transform 0.15s",
                    }}/>
                  )}
                </button>
                {isSelected && (
                  <div style={{
                    padding: "10px 14px 14px",
                    borderTop: "1px solid var(--line)",
                    background: "var(--bg-alt)",
                    display: "flex", gap: 8, flexWrap: "wrap",
                  }}>
                    {a.done ? (
                      <button className="btn btn-sm btn-ghost" style={{ flex: 1, minWidth: 150 }} onClick={() => { actions.undoAppointmentDone(a.id); setSelected(null); }}>
                        <Icon name="arrow" size={13} style={{ transform: "rotate(180deg)" }}/> Annuler la facturation
                      </button>
                    ) : (
                      <button className="btn btn-sm" style={{ flex: 1, minWidth: 150, background: "var(--ink)", color: "var(--bg)" }} onClick={() => { actions.markAppointmentDone(a.id); setSelected(null); }}>
                        <Icon name="check" size={13} stroke={2.4}/> Marquer fait & facturer
                      </button>
                    )}
                    <button className="btn btn-sm btn-ghost" style={{ color: "oklch(55% 0.18 25)" }} onClick={() => { actions.deleteAppointment(a.id); setSelected(null); }}>
                      Supprimer
                    </button>
                  </div>
                )}
              </div>
            );
          })}
        </div>
      )}

    </>
  );
};

const AppAgendaDesktop = ({ data, actions, openModal }) => {
  const hours = Array.from({ length: 11 }, (_, i) => i + 8); // 8–18
  const [selected, setSelected] = React.useState(null);
  // weekShift = nombre de jours par rapport au lundi de la semaine courante.
  // 0 = cette semaine, -7 = précédente, +7 = suivante.
  const [weekShift, setWeekShift] = React.useState(0);
  const mondayOffset = _AGENDA_MONDAY_OFFSET + weekShift;
  const isThisWeek = weekShift === 0;

  // Labels de jours dynamiques pour la semaine affichée
  const dayLabels = React.useMemo(() => (
    ["Lun", "Mar", "Mer", "Jeu", "Ven", "Sam"].map((lbl, i) => {
      const d = cbDateForOffset(mondayOffset + i);
      return `${lbl} ${d.getDate()}`;
    })
  ), [mondayOffset]);

  // Plage "21 → 26 avril 2026" — mois affiché une fois si même mois sur la semaine
  const rangeLabel = React.useMemo(() => {
    const first = cbDateForOffset(mondayOffset);
    const last = cbDateForOffset(mondayOffset + 5);
    const months = CB_FR_MONTHS_LONG;
    const sameMonth = first.getMonth() === last.getMonth();
    const sameYear = first.getFullYear() === last.getFullYear();
    if (sameMonth && sameYear) {
      return `${first.getDate()} → ${last.getDate()} ${months[last.getMonth()]} ${last.getFullYear()}`;
    }
    if (sameYear) {
      return `${first.getDate()} ${months[first.getMonth()]} → ${last.getDate()} ${months[last.getMonth()]} ${last.getFullYear()}`;
    }
    return `${first.getDate()} ${months[first.getMonth()]} ${first.getFullYear()} → ${last.getDate()} ${months[last.getMonth()]} ${last.getFullYear()}`;
  }, [mondayOffset]);

  return (
    <>
      <div style={{ display: "flex", gap: 8, marginBottom: 16, alignItems: "center" }}>
        <button className="btn btn-sm btn-ghost" aria-label="Semaine précédente"
          onClick={() => setWeekShift(w => w - 7)}>‹</button>
        <div style={{
          fontFamily: "var(--ff-display)", fontSize: 17, fontWeight: 560,
          padding: "0 8px", letterSpacing: "-0.01em",
          display: "flex", alignItems: "center", gap: 10,
        }}>
          {rangeLabel}
          {!isThisWeek && (
            <button onClick={() => setWeekShift(0)}
              style={{
                fontSize: 11.5, color: "var(--accent-ink)", fontWeight: 600,
                background: "var(--accent-soft)",
                border: "1px solid var(--accent-soft-2)",
                padding: "3px 9px", borderRadius: 999,
                cursor: "pointer", fontFamily: "inherit",
              }}>Cette semaine</button>
          )}
        </div>
        <button className="btn btn-sm btn-ghost" aria-label="Semaine suivante"
          onClick={() => setWeekShift(w => w + 7)}>›</button>
        <button className="btn btn-sm btn-primary cb-press-feedback"
          onClick={() => openModal("appointment", { day: mondayOffset, h: 9 })}>
          <Icon name="plus" size={13}/> Nouveau RDV
        </button>
      </div>

      <div className="app-scroll-x" style={{ background: "var(--surface)", border: "1px solid var(--line)", borderRadius: 12, overflow: "auto" }}>
        <div className="app-agenda-grid" style={{ display: "grid", gridTemplateColumns: "60px repeat(6, minmax(110px, 1fr))", borderBottom: "1px solid var(--line)", minWidth: 720 }}>
          <div/>
          {dayLabels.map((d, i) => {
            // L'index "aujourd'hui" dans la semaine visible :
            // weekShift correspond au lundi affiché ; aujourd'hui = jour absolu 0.
            const isToday = isThisWeek && (mondayOffset + i === cbTodayOffset());
            return (
              <div key={d} style={{
                padding: "12px 14px",
                borderLeft: "1px solid var(--line)",
                fontSize: 12.5, fontWeight: 520,
                color: isToday ? "var(--accent-ink)" : "var(--ink-2)",
                background: isToday ? "var(--accent-soft)" : "transparent",
              }}>
                <div>{d.split(" ")[0]}</div>
                <div style={{ fontFamily: "var(--ff-display)", fontSize: 18, fontWeight: 580, letterSpacing: "-0.025em" }}>{d.split(" ")[1]}</div>
              </div>
            );
          })}
        </div>

        <div className="app-agenda-grid" style={{ display: "grid", gridTemplateColumns: "60px repeat(6, minmax(110px, 1fr))", position: "relative", minWidth: 720 }}>
          <div>
            {hours.map(h => (
              <div key={h} style={{
                height: 44, padding: "3px 8px", textAlign: "right",
                fontSize: 10.5, fontVariantNumeric: "tabular-nums", color: "var(--ink-4)",
                borderBottom: "1px solid var(--line)", fontWeight: 500,
              }}>{h}:00</div>
            ))}
          </div>
          {dayLabels.map((d, dayIdx) => {
            // Récupère la pause du jour (si configurée dans booking_settings).
            // dayIdx 0-5 = Lun-Sam → ISO 1-6 dans schedule. On utilise
            // resolveSchedule pour normaliser les anciens formats.
            const dayDate = window.cbDateForOffset ? window.cbDateForOffset(mondayOffset + dayIdx) : null;
            const isoIdx = dayDate ? ((dayDate.getDay() + 6) % 7) + 1 : (dayIdx + 1);
            const schedule = resolveSchedule(data.bookingSettings || {});
            const daySched = schedule[isoIdx] || schedule[String(isoIdx)] || {};
            const hasBreak = daySched.break && daySched.breakStart != null && daySched.breakEnd != null
                             && daySched.breakEnd > daySched.breakStart;
            // Fermeture journée (avant ouverture / après fermeture) : on
            // peint en gris très clair les heures hors-horaires aussi
            // pour matérialiser visuellement les créneaux indisponibles.
            const dayStart = daySched.start != null ? Number(daySched.start) : 8;
            const dayEnd   = daySched.end != null ? Number(daySched.end) : 19;
            const dayOpen  = daySched.open !== false;
            return (
            <div key={d} style={{ position: "relative", borderLeft: "1px solid var(--line)" }}>
              {hours.map(h => (
                <div key={h} onClick={() => openModal("appointment", { day: mondayOffset + dayIdx, h })}
                  style={{
                    height: 44, borderBottom: "1px solid var(--line)", cursor: "pointer",
                  }}
                  onMouseEnter={e => e.currentTarget.style.background = "var(--bg-alt)"}
                  onMouseLeave={e => e.currentTarget.style.background = "transparent"}
                />
              ))}
              {/* Overlay JOURNÉE FERMÉE : grise toute la colonne si pas
                  ouvert ce jour-là (week-end par défaut, vacances, etc.). */}
              {!dayOpen && (
                <div aria-hidden style={{
                  position: "absolute", top: 0, left: 0, right: 0, bottom: 0,
                  background: "repeating-linear-gradient(135deg, var(--bg-alt) 0 6px, var(--bg) 6px 12px)",
                  pointerEvents: "none",
                  display: "flex", alignItems: "center", justifyContent: "center",
                  fontSize: 11, color: "var(--ink-4)", fontWeight: 540,
                }}>Fermé</div>
              )}
              {/* Overlay PAUSE : zone grisée avec label "Pause" */}
              {dayOpen && hasBreak && (() => {
                const top = (daySched.breakStart - 8) * 44;
                const height = (daySched.breakEnd - daySched.breakStart) * 44;
                if (top < 0 || top + height <= 0) return null;
                return (
                  <div aria-hidden style={{
                    position: "absolute", top: Math.max(0, top), left: 0, right: 0,
                    height: Math.min(height, (18 - 8) * 44),
                    background: "var(--bg-alt)",
                    backgroundImage: "repeating-linear-gradient(135deg, transparent 0 6px, rgba(15,16,32,0.04) 6px 12px)",
                    pointerEvents: "none",
                    display: "flex", alignItems: "center", justifyContent: "center",
                    fontSize: 10.5, color: "var(--ink-3)", fontWeight: 600,
                    borderTop: "1px dashed var(--line-strong)",
                    borderBottom: "1px dashed var(--line-strong)",
                  }}>
                    🍴 Pause
                  </div>
                );
              })()}
              {/* Overlay HORS-HORAIRES : avant ouverture ou après fermeture */}
              {dayOpen && dayStart > 8 && (
                <div aria-hidden style={{
                  position: "absolute", top: 0, left: 0, right: 0,
                  height: (dayStart - 8) * 44,
                  background: "var(--bg-alt)", opacity: 0.5, pointerEvents: "none",
                }}/>
              )}
              {dayOpen && dayEnd < 19 && (
                <div aria-hidden style={{
                  position: "absolute", top: (dayEnd - 8) * 44, left: 0, right: 0,
                  bottom: 0,
                  background: "var(--bg-alt)", opacity: 0.5, pointerEvents: "none",
                }}/>
              )}
              {data.appointments.filter(a => a.day === mondayOffset + dayIdx).map(a => {
                const c = findClient(data, a.clientId);
                const s = findService(data, a.serviceId);
                const col = COLOR_MAP[a.color] || COLOR_MAP.accent;
                const top = (a.h - 8) * 44;
                const height = a.d * 44 - 4;
                // Délai (battement) : prestation > règle globale.
                const _gBuf = (data.business && data.business.policyBufferMin) || 0;
                const bufferMin = (s && s.bufferMin != null) ? Math.max(0, s.bufferMin) : _gBuf;
                const bufTop = (a.h + a.d - 8) * 44;
                const bufH = (bufferMin / 60) * 44;
                return (
                  <React.Fragment key={a.id}>
                  <button
                    onClick={(e) => { e.stopPropagation(); setSelected(a.id); }}
                    style={{
                      position: "absolute", top: top + 2, left: 4, right: 4,
                      height, padding: "5px 8px",
                      background: col.bg,
                      border: "none",
                      borderLeft: `2.5px solid ${col.border}`,
                      borderRadius: 6,
                      fontSize: 11, overflow: "hidden", cursor: "pointer",
                      textAlign: "left", fontFamily: "inherit",
                      boxShadow: selected === a.id ? `0 0 0 2px ${col.border}, var(--sh-2)` : "none",
                      opacity: a.done ? 0.55 : 1,
                    }}>
                    <div style={{
                      fontWeight: 520, color: col.ink, fontSize: 11.5,
                      overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
                      textDecoration: a.done ? "line-through" : "none",
                    }}>
                      {a.done && "✓ "}{c ? c.name : "—"}
                    </div>
                    <div style={{ color: "var(--ink-3)", fontSize: 10.5, marginTop: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{s ? s.name : ""}</div>
                  </button>
                  {bufferMin > 0 && bufH > 0 && (
                    <div aria-hidden title={`Battement ${bufferMin} min`} style={{
                      position: "absolute", top: bufTop + 2, left: 4, right: 4,
                      height: Math.max(0, bufH - 2), borderRadius: 5, pointerEvents: "none",
                      backgroundImage: "repeating-linear-gradient(135deg, transparent 0 5px, rgba(15,16,32,0.06) 5px 10px)",
                    }}/>
                  )}
                  </React.Fragment>
                );
              })}
            </div>
            );
          })}
        </div>
      </div>

      {selected && (() => {
        const a = data.appointments.find(x => x.id === selected);
        if (!a) return null;
        const c = findClient(data, a.clientId);
        const s = findService(data, a.serviceId);
        return (
          <div style={{
            marginTop: 16, padding: 18,
            background: "var(--surface)", border: "1px solid var(--line)",
            borderRadius: 12, display: "flex", alignItems: "center", gap: 16, flexWrap: "wrap",
          }}>
            {c && <Avatar client={c} size={40}/>}
            <div style={{ flex: 1, minWidth: 200 }}>
              <div style={{ fontSize: 15, fontWeight: 520, display: "flex", alignItems: "center", gap: 8 }}>
                <span style={{ textDecoration: a.done ? "line-through" : "none", opacity: a.done ? 0.7 : 1 }}>{c ? c.name : "—"}</span>
                {a.done && (
                  <span style={{
                    padding: "2px 8px", fontSize: 10.5, fontWeight: 600,
                    background: "var(--sage-soft)", color: "oklch(38% 0.08 160)",
                    borderRadius: 999, textTransform: "uppercase", letterSpacing: "0.04em",
                  }}>✓ Facturé</span>
                )}
              </div>
              <div style={{ fontSize: 12.5, color: "var(--ink-3)", marginTop: 2 }}>
                {(() => {
                  const d = cbDateForOffset(a.day);
                  return `${CB_FR_DAYS_SHORT[d.getDay()]} ${d.getDate()} ${CB_FR_MONTHS_SHORT[d.getMonth()]}`;
                })()} · {fmtH(a.h)}, {fmtH(a.h + a.d)} · {s ? s.name : ""}{s ? ` · ${fmtEUR(s.price)}` : ""}
              </div>
            </div>
            {a.done ? (
              <button className="btn btn-sm btn-ghost" onClick={() => { actions.undoAppointmentDone(a.id); setSelected(null); }}>
                <Icon name="arrow" size={13} style={{ transform: "rotate(180deg)" }}/> Annuler la facturation
              </button>
            ) : (
              <button className="btn btn-sm" style={{ background: "var(--ink)", color: "var(--bg)" }} onClick={() => { actions.markAppointmentDone(a.id); setSelected(null); }}>
                <Icon name="check" size={13} stroke={2.4}/> Marquer fait & facturer
              </button>
            )}
            <button className="btn btn-sm btn-ghost" onClick={() => { actions.deleteAppointment(a.id); setSelected(null); }}>
              Supprimer
            </button>
            <button className="btn btn-sm btn-ghost" onClick={() => setSelected(null)}>Fermer</button>
          </div>
        );
      })()}

    </>
  );
};

/* ================================================================
   CLIENTS, filter, add, edit, delete
================================================================ */
const CLIENT_TAG_COLORS = {
  "VIP":      { bg: "var(--warn-soft)", c: "var(--warn-ink)" },
  "Fidèle":   { bg: "var(--accent-soft)", c: "var(--accent-ink)" },
  "Nouvelle": { bg: "var(--sage-soft)",   c: "oklch(38% 0.08 160)" },
  "Inactive": { bg: "var(--bg-alt)",      c: "var(--ink-3)" },
};
const tagColor = (t) => CLIENT_TAG_COLORS[t] || { bg: "var(--bg-alt)", c: "var(--ink-3)" };

/* À-venir d'un client : RDV non terminés et dans le futur (day >= today). */
const clientUpcoming = (c, data) => {
  const todayOff = (typeof window !== "undefined" && window.cbTodayOffset) ? window.cbTodayOffset() : 0;
  let count = 0, planned = 0;
  for (const a of (data.appointments || [])) {
    if (a.clientId !== c.id) continue;
    if (a.done) continue;
    if (a.day < todayOff) continue;
    count++;
    const svc = (data.services || []).find(s => s.id === a.serviceId);
    if (svc) planned += Number(svc.price) || 0;
  }
  return { count, planned };
};

const ClientCardMobile = ({ c, data, actions, openModal }) => {
  const up = clientUpcoming(c, data);
  return (
  <div style={{
    background: "var(--surface)", border: "1px solid var(--line)",
    borderRadius: 12, padding: 14, display: "flex", flexDirection: "column", gap: 12,
  }}>
    <button onClick={() => openModal("client-detail", { id: c.id })} style={{
      display: "flex", alignItems: "center", gap: 12,
      background: "transparent", border: "none", cursor: "pointer",
      padding: 0, textAlign: "left", fontFamily: "inherit", width: "100%",
    }}>
      <Avatar client={c} size={44}/>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ fontWeight: 520, fontSize: 15, color: "var(--ink)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{c.name}</div>
        <div style={{ fontSize: 12.5, color: "var(--ink-4)", marginTop: 2, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
          {c.email || c.phone || "Aucun contact"}
        </div>
      </div>
    </button>

    <div style={{
      display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 8,
      padding: "10px 0", borderTop: "1px solid var(--line)", borderBottom: "1px solid var(--line)",
    }}>
      <div>
        <div style={{ fontSize: 11, color: "var(--ink-4)", fontWeight: 500 }}>Visites</div>
        <div style={{ fontFamily: "var(--ff-display)", fontSize: 17, fontWeight: 580, marginTop: 2 }}>{c.visits}</div>
        {up.count > 0 && (
          <div style={{ fontSize: 10.5, color: "var(--accent-ink)", fontWeight: 580, marginTop: 1 }}>
            +{up.count} à venir
          </div>
        )}
      </div>
      <div>
        <div style={{ fontSize: 11, color: "var(--ink-4)", fontWeight: 500 }}>Dépensé</div>
        <div style={{ fontFamily: "var(--ff-display)", fontSize: 17, fontWeight: 580, marginTop: 2 }}>{fmtEUR(c.spent)}</div>
        {up.planned > 0 && (
          <div style={{ fontSize: 10.5, color: "var(--accent-ink)", fontWeight: 580, marginTop: 1 }}>
            +{fmtEUR(up.planned)} prévus
          </div>
        )}
      </div>
      <div>
        <div style={{ fontSize: 11, color: "var(--ink-4)", fontWeight: 500 }}>Fidélité</div>
        <div style={{ display: "flex", alignItems: "center", gap: 6, marginTop: 5 }}>
          <div style={{ flex: 1, height: 5, background: "var(--bg-alt)", borderRadius: 10, overflow: "hidden", minWidth: 24 }}>
            <div style={{ width: `${(c.fid / data.fideliteRules.visits) * 100}%`, height: "100%", background: "var(--accent)" }}/>
          </div>
          <span style={{ fontSize: 11.5, color: "var(--ink-3)", fontFamily: "inherit" }}>{c.fid}/{data.fideliteRules.visits}</span>
        </div>
      </div>
    </div>

    <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
      <div style={{ fontSize: 12, color: "var(--ink-4)" }}>
        <Icon name="clock" size={12} style={{ verticalAlign: "-2px", marginRight: 4 }}/>
        {c.last}
      </div>
      <div style={{ display: "flex", gap: 6 }}>
        {c.email && (
          <a href={`mailto:${c.email}`} className="btn btn-sm btn-ghost" aria-label="Envoyer un mail" style={{ height: 34, width: 34, padding: 0, color: "var(--ink-2)" }}>
            <Icon name="mail" size={14}/>
          </a>
        )}
        {c.phone && (
          <a href={`tel:${c.phone.replace(/\s+/g, "")}`} className="btn btn-sm btn-ghost" aria-label="Appeler" style={{ height: 34, width: 34, padding: 0, color: "var(--ink-2)" }}>
            <Icon name="phone" size={14}/>
          </a>
        )}
        <button onClick={() => openModal("client", { id: c.id })} className="btn btn-sm btn-ghost" style={{ height: 34, fontSize: 12.5 }}>Modifier</button>
        <button onClick={() => actions.deleteClient(c.id)} aria-label="Supprimer" className="btn btn-sm btn-ghost" style={{ height: 34, width: 34, padding: 0, color: "oklch(55% 0.18 25)" }}>
          <Icon name="close" size={14}/>
        </button>
      </div>
    </div>
  </div>
  );
};

/* Modale "Message groupé" : permet d'envoyer un email à plusieurs clients
   d'un coup (annonce, promo, fermeture exceptionnelle…). Utilise un mailto:
   avec les adresses en BCC pour que les destinataires ne se voient pas
   entre elles, pas de backend nécessaire. */
const BULK_TEMPLATES = [
  {
    label: "Promotion",
    subject: "Une petite promo pour vous",
    body: "Bonjour,\n\nPour vous remercier de votre fidélité, je vous propose ce mois-ci [VOTRE OFFRE].\nValable jusqu'au [DATE].\n\nÀ très vite,\n[VOTRE PRÉNOM]",
  },
  {
    label: "Nouveauté",
    subject: "Une nouveauté à découvrir",
    body: "Bonjour,\n\nJ'ai le plaisir de vous annoncer [NOUVELLE PRESTATION / NOUVEAU PRODUIT].\nN'hésitez pas à réserver pour découvrir.\n\nÀ très vite,\n[VOTRE PRÉNOM]",
  },
  {
    label: "Fermeture",
    subject: "Fermeture exceptionnelle",
    body: "Bonjour,\n\nJe vous informe que je serai fermé·e du [DATE] au [DATE].\nJe vous recontacte dès la réouverture pour reprogrammer si besoin.\n\nMerci de votre compréhension,\n[VOTRE PRÉNOM]",
  },
];

const BulkMessageModal = ({ open, onClose, clients, business }) => {
  const withEmail = (clients || []).filter(c => (c.email || "").trim());
  const [selected, setSelected] = React.useState({}); // { [id]: true }
  const [subject, setSubject] = React.useState("");
  const [body, setBody] = React.useState("");

  // À l'ouverture : tout coché par défaut
  React.useEffect(() => {
    if (!open) return;
    const all = {};
    withEmail.forEach(c => { all[c.id] = true; });
    setSelected(all);
    setSubject("");
    setBody("");
    // eslint-disable-next-line
  }, [open]);

  const toggleAll = (val) => {
    const next = {};
    withEmail.forEach(c => { next[c.id] = val; });
    setSelected(next);
  };
  const selectedCount = Object.values(selected).filter(Boolean).length;
  const selectedEmails = withEmail.filter(c => selected[c.id]).map(c => c.email);

  const applyTemplate = (t) => {
    setSubject(t.subject);
    setBody(t.body);
  };

  const send = () => {
    if (selectedEmails.length === 0) { showToast("Sélectionnez au moins un client", "warn"); return; }
    if (!subject.trim() || !body.trim()) { showToast("Sujet et message requis", "warn"); return; }
    const from = (business && business.contactEmail) || "";
    const params = new URLSearchParams();
    params.set("bcc", selectedEmails.join(","));
    params.set("subject", subject.trim());
    params.set("body", body.trim());
    // mailto: ouvre le client mail par défaut. Le pro envoie depuis sa propre adresse.
    const mailto = `mailto:${from}?${params.toString()}`;
    window.location.href = mailto;
    showToast(`Message ouvert pour ${selectedEmails.length} client${selectedEmails.length > 1 ? "s" : ""}`);
    onClose();
  };

  if (!open) return null;
  return (
    <Modal open={open} onClose={onClose}
      title={`Message groupé · ${selectedCount}/${withEmail.length}`}
      footer={
        <>
          <button className="btn btn-ghost" onClick={onClose}>Annuler</button>
          <button className="btn btn-primary" onClick={send} disabled={selectedCount === 0}>
            Ouvrir dans mon mail
          </button>
        </>
      }>
      <div style={{ display: "grid", gap: 14 }}>
        <div style={{
          padding: "10px 12px",
          background: "var(--accent-soft)", border: "1px solid var(--accent-soft-2)",
          borderRadius: 9, fontSize: 12.5, color: "var(--accent-ink)", lineHeight: 1.45,
        }}>
          ✦ Le message s'ouvrira dans votre application de mail. Les destinataires sont en
          <strong> copie cachée (BCC)</strong>, elles ne se verront pas entre elles.
        </div>

        {/* Templates */}
        <div>
          <div style={{ fontSize: 12.5, color: "var(--ink-3)", fontWeight: 500, marginBottom: 6 }}>
            Modèles rapides
          </div>
          <div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>
            {BULK_TEMPLATES.map(t => (
              <button key={t.label} onClick={() => applyTemplate(t)} style={{
                padding: "6px 12px", fontSize: 12.5, fontWeight: 500,
                background: "white", border: "1px solid var(--line)",
                borderRadius: 999, cursor: "pointer", fontFamily: "inherit",
                color: "var(--ink-2)",
              }}>
                {t.label}
              </button>
            ))}
          </div>
        </div>

        <FormField label="Sujet" value={subject} onChange={setSubject}
          placeholder="Une petite promo pour vous"/>
        <div>
          <label style={fieldLabelStyle}>Message</label>
          <textarea value={body} onChange={e => setBody(e.target.value)}
            placeholder="Bonjour,&#10;&#10;Je vous propose…"
            rows={8}
            style={{ ...fieldInputStyle, resize: "vertical", minHeight: 160 }}/>
        </div>

        {/* Recipients */}
        <div>
          <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 6 }}>
            <label style={fieldLabelStyle}>Destinataires ({selectedCount}/{withEmail.length})</label>
            <div style={{ display: "flex", gap: 6 }}>
              <button onClick={() => toggleAll(true)} className="btn btn-sm btn-ghost" style={{ height: 26, fontSize: 11.5 }}>
                Tout cocher
              </button>
              <button onClick={() => toggleAll(false)} className="btn btn-sm btn-ghost" style={{ height: 26, fontSize: 11.5 }}>
                Tout décocher
              </button>
            </div>
          </div>
          {withEmail.length === 0 ? (
            <div style={{
              padding: 16, textAlign: "center",
              background: "var(--bg-alt)", border: "1px dashed var(--line-strong)",
              borderRadius: 9, color: "var(--ink-3)", fontSize: 13,
            }}>
              Aucun client n'a d'email enregistré.
            </div>
          ) : (
            <div style={{
              maxHeight: 220, overflowY: "auto",
              background: "var(--bg-alt)", border: "1px solid var(--line)",
              borderRadius: 9, padding: 6,
            }}>
              {withEmail.map(c => {
                const checked = !!selected[c.id];
                return (
                  <label key={c.id} style={{
                    display: "flex", alignItems: "center", gap: 10,
                    padding: "7px 10px",
                    background: checked ? "var(--surface)" : "transparent",
                    borderRadius: 7, cursor: "pointer", fontSize: 13.5,
                  }}>
                    <input type="checkbox" checked={checked}
                      onChange={() => setSelected(s => ({ ...s, [c.id]: !checked }))}
                      style={{ width: 15, height: 15, accentColor: "var(--accent)", cursor: "pointer" }}/>
                    <span style={{ fontWeight: 520, color: "var(--ink)", flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
                      {c.name}
                    </span>
                    <span style={{ fontSize: 12, color: "var(--ink-4)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", maxWidth: 200 }}>
                      {c.email}
                    </span>
                  </label>
                );
              })}
            </div>
          )}
        </div>
      </div>
    </Modal>
  );
};

const AppClients = ({ data, actions, openModal }) => {
  const isMobile = useIsMobile();
  const [search, setSearch] = React.useState("");
  const [bulkOpen, setBulkOpen] = React.useState(false);

  const filtered = data.clients.filter(c => {
    if (!search.trim()) return true;
    const q = search.trim().toLowerCase();
    return c.name.toLowerCase().includes(q) || (c.email || "").toLowerCase().includes(q);
  });

  const clientsWithEmail = data.clients.filter(c => (c.email || "").trim());

  return (
    <>
      <div style={{ display: "flex", gap: 8, marginBottom: 14, alignItems: "center", flexWrap: "wrap" }}>
        <input value={search} onChange={e => setSearch(e.target.value)} placeholder="Rechercher un client…" style={{
          padding: "9px 12px", flex: 1, minWidth: 160,
          background: "var(--surface)", border: "1px solid var(--line)",
          borderRadius: 8, fontSize: 13.5, fontFamily: "inherit", color: "var(--ink)", outline: "none",
        }}/>
        {clientsWithEmail.length > 0 && (
          isMobile ? (
            <button className="btn btn-sm cb-press-feedback"
              style={{
                flexShrink: 0,
                background: "var(--accent)", color: "white",
                padding: "9px 14px", fontWeight: 580, fontSize: 13.5,
                borderRadius: 10, gap: 6,
              }}
              title={`Envoyer un message à ${clientsWithEmail.length} client${clientsWithEmail.length > 1 ? "s" : ""}`}
              onClick={() => setBulkOpen(true)}>
              <Icon name="mail" size={15}/> Message groupé
            </button>
          ) : (
            <button className="btn btn-sm btn-ghost"
              style={{ flexShrink: 0, border: "1px solid var(--line)" }}
              title={`Envoyer un message à ${clientsWithEmail.length} client${clientsWithEmail.length > 1 ? "s" : ""}`}
              onClick={() => setBulkOpen(true)}>
              <Icon name="mail" size={14}/> Message groupé
            </button>
          )
        )}
        {!isMobile && (
          <button className="btn btn-sm" style={{ background: "var(--ink)", color: "var(--bg)", flexShrink: 0 }} onClick={() => openModal("client")}>
            <Icon name="plus" size={14}/> Nouvelle
          </button>
        )}
      </div>

      <BulkMessageModal open={bulkOpen} onClose={() => setBulkOpen(false)}
        clients={data.clients} business={data.business || {}}/>

      {filtered.length === 0 ? (
        <div style={{ padding: 40, textAlign: "center", color: "var(--ink-4)", background: "var(--surface)", border: "1px solid var(--line)", borderRadius: 12 }}>
          {search ? "Aucun client trouvée." : "Vous n'avez pas encore de clients. Ajoutez-en une pour commencer."}
        </div>
      ) : isMobile ? (
        <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
          {filtered.map(c => (
            <ClientCardMobile key={c.id} c={c} data={data} actions={actions} openModal={openModal}/>
          ))}
        </div>
      ) : (
        <ClientsSplitDesktop
          clients={filtered}
          data={data}
          actions={actions}
          openModal={openModal}
        />
      )}
    </>
  );
};

/* === Vue desktop split-screen : liste à gauche + fiche détail à droite === */
const ClientsSplitDesktop = ({ clients, data, actions, openModal }) => {
  const [selectedId, setSelectedId] = React.useState(() => clients[0] ? clients[0].id : null);
  // Si la liste filtre et l'item sélectionné n'est plus visible, on prend le premier de la liste
  React.useEffect(() => {
    if (!clients.find(c => c.id === selectedId)) {
      setSelectedId(clients[0] ? clients[0].id : null);
    }
  }, [clients, selectedId]);
  const selected = selectedId ? data.clients.find(c => c.id === selectedId) : null;
  const totalVisits = data.fideliteRules.visits;

  return (
    <div style={{
      display: "grid",
      gridTemplateColumns: "minmax(280px, 1fr) minmax(380px, 460px)",
      gap: 16, alignItems: "start",
    }}>
      {/* Liste cliquable à gauche */}
      <div style={{
        background: "var(--surface)", border: "1px solid var(--line)",
        borderRadius: 12, overflow: "hidden",
      }}>
        {clients.map((c, i) => {
          const up = clientUpcoming(c, data);
          const isActive = c.id === selectedId;
          const pct = Math.min(100, (c.fid / totalVisits) * 100);
          return (
            <button key={c.id} onClick={() => setSelectedId(c.id)}
              style={{
                width: "100%", padding: "12px 14px",
                background: isActive ? "var(--accent-soft)" : "transparent",
                border: "none",
                borderBottom: i < clients.length - 1 ? "1px solid var(--line)" : "none",
                borderLeft: `3px solid ${isActive ? "var(--accent)" : "transparent"}`,
                cursor: "pointer", fontFamily: "inherit", textAlign: "left",
                display: "flex", alignItems: "center", gap: 12,
                transition: "background .15s, border-color .15s",
              }}
              onMouseEnter={e => { if (!isActive) e.currentTarget.style.background = "var(--bg-alt)"; }}
              onMouseLeave={e => { if (!isActive) e.currentTarget.style.background = "transparent"; }}>
              <Avatar client={c} size={36}/>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{
                  fontWeight: isActive ? 600 : 540, fontSize: 13.5,
                  color: isActive ? "var(--accent-ink)" : "var(--ink)",
                  overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
                }}>{c.name}</div>
                <div style={{
                  fontSize: 11.5, color: "var(--ink-4)", marginTop: 2,
                  display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap",
                }}>
                  <span>{c.visits} visite{c.visits > 1 ? "s" : ""}</span>
                  {up.count > 0 && (
                    <span style={{ color: "var(--accent-ink)", fontWeight: 580 }}>
                      +{up.count} à venir
                    </span>
                  )}
                </div>
                <div style={{ marginTop: 5, display: "flex", alignItems: "center", gap: 6 }}>
                  <div style={{ flex: 1, height: 4, background: "var(--bg-alt)", borderRadius: 999, overflow: "hidden" }}>
                    <div style={{ width: `${pct}%`, height: "100%", background: "var(--accent)" }}/>
                  </div>
                  <span style={{ fontSize: 10.5, color: "var(--ink-4)" }}>{c.fid}/{totalVisits}</span>
                </div>
              </div>
              <Icon name="arrow" size={12} style={{ color: isActive ? "var(--accent-ink)" : "var(--ink-4)", flexShrink: 0 }}/>
            </button>
          );
        })}
      </div>

      {/* Fiche détail à droite, sticky */}
      <div style={{ position: "sticky", top: 16 }}>
        {selected ? (
          <div style={{
            background: "var(--surface)", border: "1px solid var(--line)",
            borderRadius: 14, overflow: "hidden",
            animation: "cbFiche .25s cubic-bezier(.22,1,.36,1) both",
          }}>
            <style>{`@keyframes cbFiche { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }`}</style>
            {/* Barre d'actions du panneau */}
            <div style={{
              padding: "12px 16px", borderBottom: "1px solid var(--line)",
              background: "var(--bg-alt)",
              display: "flex", justifyContent: "space-between", alignItems: "center", gap: 10,
            }}>
              <div style={{ fontSize: 11.5, color: "var(--ink-4)", fontWeight: 600, letterSpacing: "0.06em", textTransform: "uppercase" }}>
                Fiche cliente
              </div>
              <div style={{ display: "flex", gap: 6 }}>
                <button onClick={() => openModal("client", { id: selected.id })}
                  className="btn btn-sm btn-ghost" style={{ height: 30, fontSize: 12 }}>
                  <Icon name="settings" size={12}/> Modifier
                </button>
                <button onClick={() => { if (window.confirm(`Supprimer ${selected.name} ?`)) actions.deleteClient(selected.id); }}
                  className="btn btn-sm btn-ghost" aria-label="Supprimer"
                  style={{ height: 30, width: 30, padding: 0, color: "oklch(55% 0.18 25)" }}>
                  <Icon name="trash" size={13}/>
                </button>
              </div>
            </div>
            <div style={{ padding: "22px 24px" }} key={selected.id}>
              <ClientDetailBody client={selected} data={data}/>
            </div>
          </div>
        ) : (
          <div style={{
            padding: "40px 24px", textAlign: "center",
            background: "var(--surface)", border: "1px dashed var(--line-strong)",
            borderRadius: 14, color: "var(--ink-4)",
          }}>
            <div style={{ fontSize: 26, marginBottom: 8 }}>👤</div>
            <div style={{ fontSize: 13.5, color: "var(--ink-3)" }}>
              Sélectionnez une cliente pour voir sa fiche.
            </div>
          </div>
        )}
      </div>
    </div>
  );
};

/* ================================================================
   MESSAGES, send works
================================================================ */
const AppMessages = ({ data, actions }) => {
  const [draft, setDraft] = React.useState("");
  const active = data.activeConv;
  const client = active ? findClient(data, active) : null;
  const msgs = active ? (data.messages[active] || []) : [];
  const scrollRef = React.useRef(null);

  React.useEffect(() => {
    if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
  }, [msgs.length, active]);

  const send = () => {
    if (!draft.trim() || !active) return;
    actions.sendMessage(active, draft);
    setDraft("");
  };

  return (
    <div className="app-messages-grid" style={{ background: "var(--surface)", border: "1px solid var(--line)", borderRadius: 12, overflow: "hidden", display: "grid", gridTemplateColumns: "280px 1fr 260px", height: "calc(100vh - 220px)" }}>
      <div className={"app-messages-list" + (active ? " has-active" : "")} style={{ borderRight: "1px solid var(--line)", overflow: "auto" }}>
        <div style={{ padding: "14px 16px", borderBottom: "1px solid var(--line)", fontSize: 13, fontWeight: 520 }}>
          Conversations <span style={{ color: "var(--ink-4)", fontWeight: 450 }}>({data.conversations.length})</span>
        </div>
        {data.conversations.map(conv => {
          const c = findClient(data, conv.clientId);
          if (!c) return null;
          const isActive = conv.clientId === active;
          return (
            <button key={conv.clientId} onClick={() => actions.openConv(conv.clientId)} style={{
              padding: "12px 16px",
              background: isActive ? "var(--accent-soft)" : "transparent",
              cursor: "pointer", display: "flex", gap: 10, alignItems: "center",
              width: "100%", textAlign: "left",
              border: "none", borderBottom: "1px solid var(--line)",
              fontFamily: "inherit",
            }}>
              <Avatar client={c} size={36}/>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
                  <div style={{ fontWeight: 520, fontSize: 13.5, color: "var(--ink)" }}>{c.name}</div>
                  <div style={{ fontSize: 11, color: "var(--ink-4)" }}>{conv.time}</div>
                </div>
                <div style={{ fontSize: 12.5, color: "var(--ink-3)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", marginTop: 2 }}>{conv.last}</div>
              </div>
              {conv.unread > 0 && <div style={{ background: "var(--accent)", color: "white", borderRadius: 10, padding: "1px 7px", fontSize: 11, fontWeight: 600 }}>{conv.unread}</div>}
            </button>
          );
        })}
      </div>

      <div className={"app-messages-thread" + (active ? " has-active" : "")} style={{ display: "flex", flexDirection: "column", background: "var(--bg)" }}>
        {client ? (
          <>
            <div style={{ padding: "14px 20px", borderBottom: "1px solid var(--line)", background: "var(--surface)", display: "flex", alignItems: "center", gap: 10 }}>
              <button onClick={() => actions.openConv(null)} className="app-messages-back" aria-label="Retour" style={{
                display: "none", background: "transparent", border: "none", cursor: "pointer",
                color: "var(--ink-2)", padding: 4,
              }}>
                <Icon name="arrow" size={18} style={{ transform: "rotate(180deg)" }}/>
              </button>
              <Avatar client={client} size={32}/>
              <div>
                <div style={{ fontWeight: 520, fontSize: 14 }}>{client.name}</div>
                <div style={{ fontSize: 11.5, color: "var(--sage)", display: "flex", alignItems: "center", gap: 4 }}>
                  <span style={{ width: 6, height: 6, borderRadius: 50, background: "var(--sage)" }}/> En ligne
                </div>
              </div>
            </div>
            <div ref={scrollRef} style={{ flex: 1, padding: 20, display: "flex", flexDirection: "column", gap: 8, overflow: "auto" }}>
              {msgs.map(m => (
                <div key={m.id} style={{
                  alignSelf: m.from === "me" ? "flex-end" : "flex-start",
                  maxWidth: "70%",
                  display: "flex", flexDirection: "column",
                  alignItems: m.from === "me" ? "flex-end" : "flex-start",
                }}>
                  <div style={{
                    padding: "8px 12px",
                    background: m.from === "me" ? "var(--accent)" : "var(--surface)",
                    color: m.from === "me" ? "white" : "var(--ink)",
                    border: m.from === "me" ? "none" : "1px solid var(--line)",
                    borderRadius: 14, fontSize: 13.5,
                  }}>{m.text}</div>
                  <div style={{ fontSize: 10.5, color: "var(--ink-4)", marginTop: 3, padding: "0 4px" }}>{m.time}</div>
                </div>
              ))}
            </div>
            <div style={{ padding: 14, borderTop: "1px solid var(--line)", background: "var(--surface)", display: "flex", gap: 8 }}>
              <input value={draft} onChange={e => setDraft(e.target.value)}
                onKeyDown={e => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); send(); } }}
                placeholder="Écrire un message…" style={{
                  flex: 1, padding: "10px 14px",
                  background: "var(--bg)", border: "1px solid var(--line-strong)",
                  borderRadius: 10, fontSize: 13, fontFamily: "inherit",
                  color: "var(--ink)", outline: "none",
                }}/>
              <button className="btn btn-sm" onClick={send} style={{ background: "var(--accent)", color: "white" }}>
                <Icon name="arrow" size={14}/> Envoyer
              </button>
            </div>
          </>
        ) : (
          <div style={{ flex: 1, display: "flex", alignItems: "center", justifyContent: "center", color: "var(--ink-4)", fontSize: 14 }}>
            Sélectionnez une conversation.
          </div>
        )}
      </div>

      <div className="app-messages-right" style={{ borderLeft: "1px solid var(--line)", padding: 20, background: "var(--surface)" }}>
        {client ? (
          <>
            <div style={{ textAlign: "center" }}>
              <div style={{ margin: "0 auto", display: "flex", justifyContent: "center" }}>
                <Avatar client={client} size={56}/>
              </div>
              <div style={{ marginTop: 10, fontWeight: 520 }}>{client.name}</div>
              <div style={{ fontSize: 12, color: "var(--ink-4)" }}>{client.visits > 10 ? "Cliente fidèle" : "Cliente"} · {client.visits} visites</div>
            </div>
            <div style={{ marginTop: 20, fontSize: 11.5, color: "var(--ink-4)", textTransform: "uppercase", letterSpacing: "0.06em", fontWeight: 520 }}>Informations</div>
            <div style={{ marginTop: 8, display: "flex", flexDirection: "column", gap: 6, fontSize: 12.5 }}>
              {client.email && <div style={{ display: "flex", justifyContent: "space-between" }}><span style={{ color: "var(--ink-4)" }}>Email</span><span style={{ fontWeight: 520 }}>{client.email}</span></div>}
              {client.phone && <div style={{ display: "flex", justifyContent: "space-between" }}><span style={{ color: "var(--ink-4)" }}>Téléphone</span><span style={{ fontFamily: "inherit", fontWeight: 520 }}>{client.phone}</span></div>}
              {client.social && (() => {
                const net = (data.bookingSettings && data.bookingSettings.preferredSocial) || "instagram";
                const url = cbSocialUrl(net, client.social);
                return (
                  <div style={{ display: "flex", justifyContent: "space-between" }}>
                    <span style={{ color: "var(--ink-4)" }}>{CB_SOCIAL_LABEL[net] || "Réseau"}</span>
                    {url ? (
                      <a href={url} target="_blank" rel="noopener noreferrer"
                        style={{ fontWeight: 520, color: "var(--accent-ink)", textDecoration: "none" }}>
                        {client.social}
                      </a>
                    ) : (
                      <span style={{ fontWeight: 520 }}>{client.social}</span>
                    )}
                  </div>
                );
              })()}
              {client.birthday && <div style={{ display: "flex", justifyContent: "space-between" }}><span style={{ color: "var(--ink-4)" }}>Anniversaire</span><span style={{ fontFamily: "inherit", fontWeight: 520 }}>{client.birthday}</span></div>}
              <div style={{ display: "flex", justifyContent: "space-between" }}><span style={{ color: "var(--ink-4)" }}>Total dépensé</span><span style={{ fontFamily: "inherit", fontWeight: 520 }}>{fmtEUR(client.spent)}</span></div>
              <div style={{ display: "flex", justifyContent: "space-between" }}><span style={{ color: "var(--ink-4)" }}>Fidélité</span><span style={{ fontFamily: "inherit", fontWeight: 520, color: "var(--accent-ink)" }}>{client.fid}/{data.fideliteRules.visits}</span></div>
            </div>
          </>
        ) : (
          <div style={{ color: "var(--ink-4)", fontSize: 13 }}>—</div>
        )}
      </div>
    </div>
  );
};

/* ================================================================
   CARTE DE FIDÉLITÉ, edit rules, add visits, customize card aspect
================================================================ */
// Thèmes visuels partagés entre cartes de fidélité et cartes cadeaux,
// l'utilisateur choisit l'apparence qui colle à sa marque.
const CARD_THEMES = {
  indigo:   { label: "Indigo",   grad: "linear-gradient(135deg, var(--accent) 0%, oklch(48% 0.22 295) 100%)",                  swatch: "var(--accent)",         glow: "oklch(42% 0.16 285)" },
  rose:     { label: "Rose",     grad: "linear-gradient(135deg, oklch(60% 0.18 340) 0%, oklch(50% 0.20 310) 100%)",            swatch: "oklch(60% 0.18 340)",   glow: "oklch(45% 0.18 320)" },
  sage:     { label: "Sage",     grad: "linear-gradient(135deg, oklch(55% 0.13 165) 0%, oklch(48% 0.12 185) 100%)",            swatch: "oklch(55% 0.13 165)",   glow: "oklch(48% 0.13 175)" },
  amber:    { label: "Ambre",    grad: "linear-gradient(135deg, oklch(64% 0.16 60) 0%, oklch(55% 0.18 50) 100%)",              swatch: "oklch(62% 0.16 55)",    glow: "oklch(55% 0.16 55)" },
  ocean:    { label: "Océan",    grad: "linear-gradient(135deg, oklch(56% 0.15 215) 0%, oklch(46% 0.18 245) 100%)",            swatch: "oklch(52% 0.16 230)",   glow: "oklch(48% 0.16 235)" },
  midnight: { label: "Nuit",     grad: "linear-gradient(135deg, oklch(32% 0.08 270) 0%, oklch(22% 0.10 280) 100%)",            swatch: "oklch(28% 0.10 275)",   glow: "oklch(25% 0.10 275)" },
};
const useCardTheme = (key, fallback = "indigo") => {
  const [tid, setTid] = React.useState(() => {
    try { return localStorage.getItem(key) || fallback; } catch { return fallback; }
  });
  const set = (id) => {
    setTid(id);
    try { localStorage.setItem(key, id); } catch {}
  };
  return [tid, set];
};
const useLocalString = (key, fallback = "") => {
  const [v, setV] = React.useState(() => {
    try { const raw = localStorage.getItem(key); return raw == null ? fallback : raw; } catch { return fallback; }
  });
  const set = (val) => { setV(val); try { localStorage.setItem(key, val); } catch {} };
  return [v, set];
};

// Comme useLocalString mais pour des objets/tableaux (sérialisés en JSON).
// Si rien n'est stocké, retombe sur `fallback` (utile pour seeder une démo).
const useLocalJSON = (key, fallback) => {
  const [v, setV] = React.useState(() => {
    try { const raw = localStorage.getItem(key); return raw == null ? fallback : JSON.parse(raw); }
    catch { return fallback; }
  });
  const set = (val) => {
    setV(prev => {
      const next = typeof val === "function" ? val(prev) : val;
      try { localStorage.setItem(key, JSON.stringify(next)); } catch {}
      return next;
    });
  };
  return [v, set];
};

/* Modale d'édition partagée fidélité + cartes cadeaux.
   Contient aperçu en direct + apparence + texte. Aucune édition n'a
   lieu en dehors de cette modale, la vue principale reste consultation. */
const CardEditorModal = ({
  open, onClose, title,
  themeId, onThemeChange,
  text, onTextChange, textPlaceholder,
  emoji, onEmojiChange, emojiPlaceholder,
  preview,
}) => (
  <Modal open={open} onClose={onClose} wide title={title}>
    <div style={{ padding: "18px 22px", display: "flex", flexDirection: "column", gap: 18 }}>
      {/* Aperçu en direct */}
      <div>
        <div style={{
          display: "flex", alignItems: "center", gap: 8, marginBottom: 8,
          fontSize: 11, color: "var(--ink-4)", fontWeight: 600,
          letterSpacing: "0.06em", textTransform: "uppercase",
        }}>
          <span style={{ width: 4, height: 4, borderRadius: 50, background: "var(--accent)" }}/>
          Aperçu en direct
        </div>
        {preview}
      </div>

      {/* Apparence : pastilles de thème */}
      <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
        <div style={{ fontSize: 12.5, color: "var(--ink-3)", fontWeight: 540 }}>
          Apparence
        </div>
        <div style={{ display: "flex", gap: 7, flexWrap: "wrap" }}>
          {Object.entries(CARD_THEMES).map(([id, t]) => {
            const active = id === themeId;
            return (
              <button key={id} onClick={() => onThemeChange(id)} title={t.label} aria-label={t.label}
                style={{
                  width: 30, height: 30, borderRadius: 9, background: t.swatch,
                  border: active ? "2px solid var(--ink)" : "2px solid transparent",
                  boxShadow: active ? `0 0 0 2px var(--surface) inset, 0 6px 14px -4px ${t.swatch}` : "var(--sh-1)",
                  cursor: "pointer", padding: 0,
                  transition: "transform .12s, box-shadow .15s",
                }}
                onMouseDown={e => e.currentTarget.style.transform = "scale(0.92)"}
                onMouseUp={e => e.currentTarget.style.transform = "scale(1)"}
                onMouseLeave={e => e.currentTarget.style.transform = "scale(1)"}/>
            );
          })}
        </div>
      </div>

      {/* Texte sur la carte */}
      <div style={{ display: "grid", gridTemplateColumns: "1fr 90px", gap: 10 }}>
        <div>
          <label style={{ fontSize: 12.5, color: "var(--ink-3)", fontWeight: 540, display: "block", marginBottom: 6 }}>
            Texte sur la carte
          </label>
          <input value={text} onChange={e => onTextChange(e.target.value)} placeholder={textPlaceholder}
            maxLength={40}
            style={{
              width: "100%", padding: "9px 12px",
              background: "var(--bg-alt)", border: "1px solid var(--line)", borderRadius: 9,
              fontSize: 13, fontFamily: "inherit", color: "var(--ink)", outline: "none",
            }}/>
        </div>
        <div>
          <label style={{ fontSize: 12.5, color: "var(--ink-3)", fontWeight: 540, display: "block", marginBottom: 6 }}>
            Emoji
          </label>
          <input value={emoji} onChange={e => onEmojiChange(e.target.value)} placeholder={emojiPlaceholder}
            maxLength={4}
            style={{
              width: "100%", padding: "9px 12px", textAlign: "center",
              background: "var(--bg-alt)", border: "1px solid var(--line)", borderRadius: 9,
              fontSize: 16, fontFamily: "inherit", color: "var(--ink)", outline: "none",
            }}/>
        </div>
      </div>

      <div style={{ display: "flex", justifyContent: "flex-end", paddingTop: 4 }}>
        <button onClick={onClose} className="btn btn-accent btn-sm">Terminé</button>
      </div>
    </div>
  </Modal>
);

const FideliteClientRow = ({ c, data, actions, theme, cardText, cardEmoji }) => {
  const [copied, setCopied] = React.useState(false);
  const total = data.fideliteRules.visits;
  const remaining = Math.max(0, total - c.fid);
  const pct = Math.min(100, (c.fid / total) * 100);

  const cardUrl = `${window.location.origin}/?card=${cbAuth.getCurrentUser()?.bookingSlug || ""}&cli=${c.id}`;

  const copyLink = async () => {
    try {
      if (navigator.clipboard) await navigator.clipboard.writeText(cardUrl);
      else {
        const ta = document.createElement("textarea");
        ta.value = cardUrl; document.body.appendChild(ta); ta.select();
        document.execCommand("copy"); document.body.removeChild(ta);
      }
      setCopied(true);
      setTimeout(() => setCopied(false), 1800);
      showToast("Lien de la carte copié");
    } catch (e) {
      showToast("Copie impossible, sélectionnez à la main", "warn");
    }
  };

  const isComplete = c.fid >= total;
  // Thème choisi par l'utilisateur (avec passage en sage si récompense prête)
  const t = theme || CARD_THEMES.indigo;
  const completeT = CARD_THEMES.sage;
  const grad = isComplete ? completeT.grad : t.grad;
  const shadow = isComplete
    ? `0 14px 32px -14px ${completeT.glow}`
    : `0 14px 32px -14px ${t.glow}`;

  return (
    <div style={{
      position: "relative", overflow: "hidden",
      borderRadius: 16, padding: "18px 20px",
      background: grad, color: "#fff",
      boxShadow: shadow,
      minHeight: 200,
      display: "flex", flexDirection: "column", justifyContent: "space-between", gap: 14,
    }}>
      <div aria-hidden style={{ position: "absolute", top: -40, right: -30, width: 140, height: 140, borderRadius: "50%", background: "rgba(255,255,255,0.10)" }}/>
      <div aria-hidden style={{ position: "absolute", bottom: -60, right: 80, width: 110, height: 110, borderRadius: "50%", background: "rgba(255,255,255,0.06)" }}/>

      {/* Top : chip + brand + statut */}
      <div style={{ position: "relative", display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 10 }}>
        <div style={{ display: "flex", alignItems: "center", gap: 9 }}>
          <div aria-hidden style={{
            width: 32, height: 22, borderRadius: 5,
            background: "linear-gradient(135deg, oklch(85% 0.06 90) 0%, oklch(72% 0.10 80) 100%)",
            border: "1px solid rgba(255,255,255,0.25)",
            display: "inline-flex", alignItems: "center", justifyContent: "center", fontSize: 12,
          }}>{cardEmoji || ""}</div>
          <div style={{ fontSize: 10.5, fontWeight: 700, letterSpacing: "0.1em", textTransform: "uppercase", opacity: 0.9 }}>
            {cardText || "Fidélité · ClientBase"}
          </div>
        </div>
        {isComplete && (
          <span style={{
            padding: "3px 9px", background: "rgba(255,255,255,0.22)",
            border: "1px solid rgba(255,255,255,0.35)", borderRadius: 999,
            fontSize: 10, fontWeight: 700, letterSpacing: "0.05em", textTransform: "uppercase",
          }}>🎁 Récompense prête</span>
        )}
      </div>

      {/* Middle : nom + statut compact */}
      <div style={{ position: "relative" }}>
        <div style={{
          fontFamily: "var(--ff-display)", fontSize: 20, fontWeight: 600, letterSpacing: "-0.018em",
          marginBottom: 4, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis",
        }}>
          {c.name}
        </div>
        <div style={{ fontSize: 12, opacity: 0.88 }}>
          {isComplete ? "Carte complète" : remaining === 1 ? "Plus qu'une visite !" : `Encore ${remaining} visites avant la récompense`}
        </div>
      </div>

      {/* Bottom : compteur + jauge + actions */}
      <div style={{ position: "relative" }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 6 }}>
          <div style={{ fontSize: 10.5, opacity: 0.85, letterSpacing: "0.06em", textTransform: "uppercase", fontWeight: 600 }}>
            Visites
          </div>
          <div style={{ fontFamily: "var(--ff-display)", fontSize: 22, fontWeight: 600, letterSpacing: "-0.02em" }}>
            {c.fid}<span style={{ opacity: 0.55 }}>/{total}</span>
          </div>
        </div>
        <div style={{ height: 5, background: "rgba(255,255,255,0.22)", borderRadius: 999, overflow: "hidden", marginBottom: 10 }}>
          <div style={{ width: `${pct}%`, height: "100%", background: "#fff", borderRadius: 999, transition: "width .3s" }}/>
        </div>

        <div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>
          <div style={{
            display: "flex", alignItems: "center", gap: 2,
            background: "rgba(255,255,255,0.18)", border: "1px solid rgba(255,255,255,0.28)",
            borderRadius: 8, padding: 2,
          }}>
            <button onClick={() => actions.adjustFidelite(c.id, -1)} disabled={c.fid <= 0} style={{
              width: 28, height: 28, background: "transparent", border: "none",
              cursor: c.fid > 0 ? "pointer" : "not-allowed",
              color: "#fff", fontSize: 16, borderRadius: 6,
              opacity: c.fid > 0 ? 1 : 0.4, fontFamily: "inherit",
            }}>−</button>
            <button onClick={() => actions.adjustFidelite(c.id, +1)} disabled={c.fid >= total} style={{
              width: 28, height: 28, background: "transparent", border: "none",
              cursor: c.fid < total ? "pointer" : "not-allowed",
              color: "#fff", fontSize: 16, borderRadius: 6,
              opacity: c.fid < total ? 1 : 0.4, fontFamily: "inherit",
            }}>+</button>
          </div>
          <button onClick={copyLink} style={{
            display: "inline-flex", alignItems: "center", gap: 5,
            padding: "6px 11px", height: 32,
            background: "rgba(255,255,255,0.18)", color: "#fff",
            border: "1px solid rgba(255,255,255,0.28)", borderRadius: 8,
            cursor: "pointer", fontFamily: "inherit", fontSize: 12, fontWeight: 540,
          }}>
            <Icon name={copied ? "check" : "link"} size={12}/> {copied ? "Copié" : "Lien"}
          </button>
          {isComplete && (
            <button onClick={() => {
              if (!window.confirm(`Remettre la carte de ${c.name} à zéro ? (Récompense offerte)`)) return;
              actions.resetFidelite(c.id);
            }} style={{
              display: "inline-flex", alignItems: "center", gap: 5,
              padding: "6px 11px", height: 32, marginLeft: "auto",
              background: "#fff", color: "oklch(40% 0.16 165)",
              border: "none", borderRadius: 8, cursor: "pointer",
              fontFamily: "inherit", fontSize: 12, fontWeight: 600,
            }}>
              Offrir & réinitialiser
            </button>
          )}
        </div>
      </div>
    </div>
  );
};

/* ====== Helpers carte cadeau ====== */
const _generateGiftCode = () => {
  // Code lisible : 2 mots de 3 lettres + 2 chiffres (ex. SOLEIL47)
  const words = ["LUNE", "ETOILE", "OCEAN", "SOLEIL", "PERLE", "AURORE", "JADE", "CORAIL", "ANEMONE", "SAFRAN", "VELOURS", "OPALE", "MIRAGE", "ZENITH", "AMBRE", "CYGNE"];
  const word = words[Math.floor(Math.random() * words.length)];
  const nums = Math.floor(Math.random() * 90) + 10;
  return `${word}${nums}`;
};
const _addMonthsToYmd = (ymd, months) => {
  try {
    const d = new Date(ymd + "T00:00:00");
    d.setMonth(d.getMonth() + months);
    return d.toISOString().slice(0, 10);
  } catch { return ymd; }
};
const _fmtGiftDate = (ymd) => {
  if (!ymd) return "—";
  try {
    const d = new Date(ymd + "T00:00:00");
    return `${d.getDate()}/${String(d.getMonth() + 1).padStart(2, "0")}/${d.getFullYear()}`;
  } catch { return ymd; }
};

/* ====== Module Cartes cadeaux ====== */
const AppGiftCards = ({ data, actions }) => {
  const cards = (data.giftCards || []).slice().sort((a, b) => (b.createdAt || "").localeCompare(a.createdAt || ""));
  const [themeId, setThemeId] = useCardTheme("cb_giftcard_theme", "rose");
  const theme = CARD_THEMES[themeId] || CARD_THEMES.rose;
  const [cardText, setCardText] = useLocalString("cb_giftcard_text", "");
  const [cardEmoji, setCardEmoji] = useLocalString("cb_giftcard_emoji", "");
  const [search, setSearch] = React.useState("");
  const [creating, setCreating] = React.useState(false);
  const [draft, setDraft] = React.useState({
    amount: "50", recipientName: "", recipientEmail: "",
    senderName: "", message: "", expiresIn: "12",
  });
  const [filter, setFilter] = React.useState("all"); // all | active | used | expired

  const todayYmd = new Date().toISOString().slice(0, 10);
  const isExpired = (c) => c.expiresAt && c.expiresAt < todayYmd;

  const q = search.trim().toLowerCase();
  const filtered = cards.filter(c => {
    if (filter === "active" && (c.used || isExpired(c))) return false;
    if (filter === "used" && !c.used) return false;
    if (filter === "expired" && (c.used || !isExpired(c))) return false;
    if (q) {
      const hay = `${c.code || ""} ${c.recipientName || ""} ${c.senderName || ""}`.toLowerCase();
      if (!hay.includes(q)) return false;
    }
    return true;
  });

  const stats = {
    total: cards.length,
    active: cards.filter(c => !c.used && !isExpired(c)).length,
    used: cards.filter(c => c.used).length,
    pending: cards.filter(c => !c.used && !isExpired(c)).reduce((s, c) => s + (Number(c.amount) || 0), 0),
  };

  const startCreate = () => {
    setDraft({
      amount: "50", recipientName: "", recipientEmail: "",
      senderName: "", message: "", expiresIn: "12",
    });
    setCreating(true);
  };

  const save = () => {
    const amount = Number(draft.amount) || 0;
    if (amount <= 0) { showToast("Choisissez un montant", "warn"); return; }
    if (!draft.recipientName.trim()) { showToast("Nom du destinataire requis", "warn"); return; }
    actions.addGiftCard({
      amount,
      recipientName: draft.recipientName,
      recipientEmail: draft.recipientEmail,
      senderName: draft.senderName,
      message: draft.message,
      expiresAt: _addMonthsToYmd(todayYmd, Number(draft.expiresIn) || 12),
    });
    setCreating(false);
  };

  const copyCode = (code) => {
    try {
      if (navigator.clipboard) navigator.clipboard.writeText(code);
      showToast(`Code ${code} copié`);
    } catch {
      showToast("Impossible de copier", "warn");
    }
  };

  const shareWhatsapp = (gc) => {
    const txt = `🎁 Vous avez reçu une carte cadeau de ${gc.amount}€ chez ${data.business.name || "votre pro"} !\n\nCode : *${gc.code}*\nValable jusqu'au ${_fmtGiftDate(gc.expiresAt)}\n\n${gc.message ? `« ${gc.message} »` : ""}\n\nPrésentez ce code lors de votre prochain RDV.`;
    window.open(`https://wa.me/?text=${encodeURIComponent(txt)}`, "_blank");
  };

  const [editorOpen, setEditorOpen] = React.useState(false);

  return (
    <div>
      {/* Header, titre + description (« fonctionnement carte cadeau ») + Modifier */}
      <div style={{
        padding: "14px 18px", background: "var(--surface)",
        border: "1px solid var(--line)", borderRadius: 12, marginBottom: 14,
        display: "flex", alignItems: "center", gap: 14, flexWrap: "wrap",
      }}>
        <div style={{
          width: 44, height: 44, borderRadius: 10,
          background: theme.grad,
          color: "white", display: "flex", alignItems: "center", justifyContent: "center",
          flexShrink: 0,
        }}>
          <Icon name="gift" size={22}/>
        </div>
        <div style={{ flex: 1, minWidth: 200 }}>
          <div style={{ fontWeight: 580, fontSize: 15 }}>Fonctionnement carte cadeau</div>
          <div style={{ fontSize: 12.5, color: "var(--ink-3)", marginTop: 2, lineHeight: 1.45 }}>
            Émettez une carte cadeau, elle est offerte à votre cliente, l'invitée présente le code à son RDV.
          </div>
        </div>
        <button className="btn btn-sm btn-ghost cb-press-feedback" onClick={() => setEditorOpen(true)}>
          <Icon name="sparkle" size={13}/> Modifier
        </button>
        {!creating && (
          <button onClick={startCreate} className="btn btn-primary btn-sm cb-press-feedback">
            <Icon name="plus" size={13}/> Nouvelle carte
          </button>
        )}
      </div>

      {/* Modale d'édition, aperçu + apparence + texte */}
      <CardEditorModal
        open={editorOpen} onClose={() => setEditorOpen(false)}
        title="Modifier la carte cadeau"
        themeId={themeId} onThemeChange={setThemeId}
        text={cardText} onTextChange={setCardText}
        emoji={cardEmoji} onEmojiChange={setCardEmoji}
        textPlaceholder="Carte cadeau · ClientBase"
        emojiPlaceholder="🎁"
        preview={
          <GiftCardRow
            gc={{
              id: "_preview", code: "LUNE42", amount: 50,
              recipientName: "Lucie Bernard", senderName: "Camille Petit",
              createdAt: todayYmd, expiresAt: _addMonthsToYmd(todayYmd, 12), used: false,
            }}
            expired={false} theme={theme}
            cardText={cardText} cardEmoji={cardEmoji}
            onCopy={() => {}} onShare={() => {}} onMarkUsed={() => {}} onDelete={() => {}}
          />
        }
      />

      {/* Search */}
      <input value={search} onChange={e => setSearch(e.target.value)}
        placeholder="Rechercher une carte (code, destinataire)…" style={{
        padding: "9px 12px", width: "100%", marginBottom: 14,
        background: "var(--surface)", border: "1px solid var(--line)",
        borderRadius: 8, fontSize: 13.5, fontFamily: "inherit", color: "var(--ink)", outline: "none",
      }}/>

      {/* Form */}
      {creating && (
        <div style={{
          marginBottom: 18, padding: 18,
          background: "var(--surface)", border: "1px solid var(--accent-soft-2)",
          borderRadius: 14, boxShadow: "var(--sh-2)",
        }}>
          <div style={{ fontSize: 14, fontWeight: 580, marginBottom: 14 }}>
            Émettre une nouvelle carte cadeau
          </div>
          <div style={{ display: "grid", gap: 12 }}>
            <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }} className="app-split">
              <ServiceField label="Montant (€) *" type="number" value={draft.amount}
                onChange={v => setDraft({ ...draft, amount: v })} placeholder="50"/>
              <div>
                <label style={{ fontSize: 12.5, color: "var(--ink-3)", display: "block", marginBottom: 4 }}>
                  Validité
                </label>
                <select value={draft.expiresIn} onChange={e => setDraft({ ...draft, expiresIn: e.target.value })}
                  style={{ ...fieldInputStyle, cursor: "pointer" }}>
                  <option value="6">6 mois</option>
                  <option value="12">1 an</option>
                  <option value="24">2 ans</option>
                  <option value="36">3 ans</option>
                </select>
              </div>
            </div>
            <ServiceField label="Nom de la destinataire *" value={draft.recipientName}
              onChange={v => setDraft({ ...draft, recipientName: v })}
              placeholder="ex. Lucie Bernard"/>
            <ServiceField label="Email de la destinataire (optionnel)" type="email" value={draft.recipientEmail}
              onChange={v => setDraft({ ...draft, recipientEmail: v })}
              placeholder="lucie@exemple.fr"/>
            <ServiceField label="De la part de (optionnel)" value={draft.senderName}
              onChange={v => setDraft({ ...draft, senderName: v })}
              placeholder="ex. Camille Petit"/>
            <div>
              <label style={{ fontSize: 12.5, color: "var(--ink-3)", display: "block", marginBottom: 4 }}>
                Message personnalisé (optionnel)
              </label>
              <textarea value={draft.message} onChange={e => setDraft({ ...draft, message: e.target.value })}
                placeholder="Joyeux anniversaire ma belle !"
                maxLength={200} rows={2}
                style={{ ...fieldInputStyle, resize: "vertical", minHeight: 60 }}/>
            </div>
          </div>
          <div style={{ display: "flex", gap: 8, marginTop: 14 }}>
            <button onClick={save} className="btn btn-accent btn-sm">
              Émettre la carte
            </button>
            <button onClick={() => setCreating(false)} className="btn btn-ghost btn-sm">Annuler</button>
          </div>
        </div>
      )}

      {/* Filtres */}
      {cards.length > 0 && (
        <div style={{ display: "flex", gap: 6, marginBottom: 12, flexWrap: "wrap" }}>
          {[
            { id: "all",     label: `Toutes (${stats.total})` },
            { id: "active",  label: `Actives (${stats.active})` },
            { id: "used",    label: `Utilisées (${stats.used})` },
            { id: "expired", label: "Expirées" },
          ].map(f => (
            <button key={f.id} onClick={() => setFilter(f.id)}
              style={{
                padding: "6px 12px",
                background: filter === f.id ? "var(--ink)" : "var(--surface)",
                color: filter === f.id ? "var(--bg)" : "var(--ink-2)",
                border: `1px solid ${filter === f.id ? "var(--ink)" : "var(--line)"}`,
                borderRadius: 999, fontSize: 12.5, fontWeight: 540,
                cursor: "pointer", fontFamily: "inherit",
              }}>
              {f.label}
            </button>
          ))}
        </div>
      )}

      {/* Liste */}
      {filtered.length === 0 ? (
        <div style={{
          padding: "40px 20px", textAlign: "center",
          background: "var(--bg-alt)", border: "1px dashed var(--line-strong)",
          borderRadius: 12, color: "var(--ink-3)",
        }}>
          <div style={{ fontSize: 32, marginBottom: 8 }}>🎁</div>
          <div style={{ fontSize: 14.5, fontWeight: 520, color: "var(--ink-2)" }}>
            {filter === "all" ? "Aucune carte cadeau pour le moment" : "Aucune carte dans cette catégorie"}
          </div>
          <div style={{ fontSize: 13, marginTop: 6 }}>
            Cliquez sur « Nouvelle carte » pour en émettre une.
          </div>
        </div>
      ) : (
        <div style={{
          display: "grid",
          gridTemplateColumns: "repeat(auto-fill, minmax(min(340px, 100%), 1fr))",
          gap: 10,
        }}>
          {filtered.map(gc => (
            <GiftCardRow key={gc.id} gc={gc} expired={isExpired(gc)} theme={theme}
              cardText={cardText} cardEmoji={cardEmoji}
              onCopy={() => copyCode(gc.code)}
              onShare={() => shareWhatsapp(gc)}
              onMarkUsed={() => actions.markGiftCardUsed(gc.id)}
              onDelete={() => actions.deleteGiftCard(gc.id)}/>
          ))}
        </div>
      )}
    </div>
  );
};

const GiftKpi = ({ label, value, icon, tone = "neutral" }) => {
  const tones = {
    sage:    { bg: "var(--sage-soft)",   c: "oklch(38% 0.08 160)" },
    accent:  { bg: "var(--accent-soft)", c: "var(--accent-ink)" },
    warn:    { bg: "oklch(97% 0.04 30)", c: "oklch(45% 0.14 30)" },
    neutral: { bg: "var(--bg-alt)",      c: "var(--ink-3)" },
  }[tone];
  return (
    <div style={{
      padding: 14, background: "var(--surface)", border: "1px solid var(--line)",
      borderRadius: 12,
    }}>
      <div style={{
        width: 28, height: 28, borderRadius: 8,
        background: tones.bg, color: tones.c,
        display: "flex", alignItems: "center", justifyContent: "center",
        marginBottom: 8,
      }}>
        <Icon name={icon} size={13}/>
      </div>
      <div style={{ fontFamily: "var(--ff-display)", fontSize: 22, fontWeight: 580, letterSpacing: "-0.025em" }}>
        {value}
      </div>
      <div style={{ fontSize: 11.5, color: "var(--ink-3)", marginTop: 1 }}>{label}</div>
    </div>
  );
};

const GiftCardRow = ({ gc, expired, onCopy, onShare, onMarkUsed, onDelete, theme, cardText, cardEmoji }) => {
  const isInactive = gc.used || expired;
  const status = gc.used ? "Utilisée" : expired ? "Expirée" : "Active";

  // Thème choisi par l'utilisateur ; gris atténué si utilisée/expirée
  const t = theme || CARD_THEMES.rose;
  const grad = isInactive
    ? "linear-gradient(135deg, oklch(55% 0.05 320) 0%, oklch(45% 0.06 300) 100%)"
    : t.grad;
  const shadow = isInactive
    ? "0 14px 32px -14px oklch(45% 0.05 320)"
    : `0 14px 32px -14px ${t.glow}`;

  return (
    <div style={{
      position: "relative", overflow: "hidden",
      borderRadius: 16, padding: "18px 20px",
      background: grad, color: "#fff",
      boxShadow: shadow,
      minHeight: 210,
      display: "flex", flexDirection: "column", justifyContent: "space-between", gap: 14,
      opacity: isInactive ? 0.78 : 1,
    }}>
      <div aria-hidden style={{ position: "absolute", top: -40, right: -30, width: 140, height: 140, borderRadius: "50%", background: "rgba(255,255,255,0.10)" }}/>
      <div aria-hidden style={{ position: "absolute", bottom: -60, right: 80, width: 110, height: 110, borderRadius: "50%", background: "rgba(255,255,255,0.06)" }}/>

      {/* Top : ruban + brand + statut */}
      <div style={{ position: "relative", display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 10 }}>
        <div style={{ display: "flex", alignItems: "center", gap: 9 }}>
          <div aria-hidden style={{
            width: 32, height: 22, borderRadius: 5,
            background: "linear-gradient(135deg, oklch(95% 0.05 60) 0%, oklch(85% 0.12 50) 100%)",
            border: "1px solid rgba(255,255,255,0.25)",
            display: "inline-flex", alignItems: "center", justifyContent: "center", fontSize: 12,
          }}>{cardEmoji || "🎁"}</div>
          <div style={{ fontSize: 10.5, fontWeight: 700, letterSpacing: "0.1em", textTransform: "uppercase", opacity: 0.9 }}>
            {cardText || "Carte cadeau · ClientBase"}
          </div>
        </div>
        <span style={{
          padding: "3px 9px", background: "rgba(255,255,255,0.22)",
          border: "1px solid rgba(255,255,255,0.32)", borderRadius: 999,
          fontSize: 10, fontWeight: 700, letterSpacing: "0.05em", textTransform: "uppercase",
        }}>{status}</span>
      </div>

      {/* Middle : code + montant */}
      <div style={{ position: "relative", display: "flex", alignItems: "flex-end", justifyContent: "space-between", gap: 14 }}>
        <div style={{ minWidth: 0 }}>
          <div style={{ fontSize: 10, opacity: 0.78, letterSpacing: "0.06em", textTransform: "uppercase", marginBottom: 4, fontWeight: 600 }}>
            Code
          </div>
          <div style={{
            fontFamily: "var(--ff-display)", fontSize: 22, fontWeight: 600, letterSpacing: "0.02em",
          }}>
            {gc.code}
          </div>
        </div>
        <div style={{ textAlign: "right", flexShrink: 0 }}>
          <div style={{ fontSize: 10, opacity: 0.78, letterSpacing: "0.06em", textTransform: "uppercase", marginBottom: 4, fontWeight: 600 }}>
            Montant
          </div>
          <div style={{
            fontFamily: "var(--ff-display)", fontSize: 28, fontWeight: 600, letterSpacing: "-0.02em", lineHeight: 1,
          }}>
            {gc.amount} €
          </div>
        </div>
      </div>

      {/* Bottom : destinataire + dates + actions */}
      <div style={{ position: "relative" }}>
        <div style={{
          fontSize: 12, opacity: 0.92, marginBottom: 2,
          overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
        }}>
          Pour <strong>{gc.recipientName || "—"}</strong>
          {gc.senderName && <span style={{ opacity: 0.85 }}> · de la part de {gc.senderName}</span>}
        </div>
        <div style={{ fontSize: 10.5, opacity: 0.78, marginBottom: 10 }}>
          Émise le {_fmtGiftDate(gc.createdAt)} · Valable jusqu'au {_fmtGiftDate(gc.expiresAt)}
        </div>

        <div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>
          <button onClick={onCopy} style={{
            display: "inline-flex", alignItems: "center", gap: 5,
            padding: "6px 11px", height: 30,
            background: "rgba(255,255,255,0.2)", color: "#fff",
            border: "1px solid rgba(255,255,255,0.28)", borderRadius: 8,
            cursor: "pointer", fontFamily: "inherit", fontSize: 12, fontWeight: 540,
          }}>
            <Icon name="copy" size={12}/> Code
          </button>
          {!gc.used && !expired && (
            <>
              <button onClick={onShare} style={{
                display: "inline-flex", alignItems: "center", gap: 5,
                padding: "6px 11px", height: 30,
                background: "rgba(255,255,255,0.2)", color: "#fff",
                border: "1px solid rgba(255,255,255,0.28)", borderRadius: 8,
                cursor: "pointer", fontFamily: "inherit", fontSize: 12, fontWeight: 540,
              }}>
                💬 WhatsApp
              </button>
              <button onClick={onMarkUsed} style={{
                display: "inline-flex", alignItems: "center", gap: 5,
                padding: "6px 11px", height: 30,
                background: "#fff", color: "oklch(40% 0.18 320)",
                border: "none", borderRadius: 8,
                cursor: "pointer", fontFamily: "inherit", fontSize: 12, fontWeight: 600,
              }}>
                <Icon name="check" size={12}/> Utilisée
              </button>
            </>
          )}
          <button onClick={onDelete} aria-label="Supprimer" style={{
            marginLeft: "auto", width: 30, height: 30, padding: 0,
            background: "rgba(255,255,255,0.18)", color: "#fff",
            border: "1px solid rgba(255,255,255,0.28)", borderRadius: 8,
            cursor: "pointer", display: "inline-flex", alignItems: "center", justifyContent: "center",
          }}>
            <Icon name="trash" size={12}/>
          </button>
        </div>
      </div>
    </div>
  );
};

const AppFidelite = ({ data, actions, openModal }) => {
  const [search, setSearch] = React.useState("");
  const [themeId, setThemeId] = useCardTheme("cb_fidelite_theme", "indigo");
  const theme = CARD_THEMES[themeId] || CARD_THEMES.indigo;
  const [cardText, setCardText] = useLocalString("cb_fidelite_text", "");
  const [cardEmoji, setCardEmoji] = useLocalString("cb_fidelite_emoji", "");
  const filtered = data.clients.filter(c => {
    if (!search.trim()) return true;
    return c.name.toLowerCase().includes(search.trim().toLowerCase());
  });
  const sorted = [...filtered].sort((a, b) => (b.fid || 0) - (a.fid || 0));

  const [editorOpen, setEditorOpen] = React.useState(false);

  return (
    <div>
      {/* Header, titre + description (« programme de fidélité ») + Modifier */}
      <div style={{
        padding: "14px 18px", background: "var(--surface)",
        border: "1px solid var(--line)", borderRadius: 12, marginBottom: 14,
        display: "flex", alignItems: "center", gap: 14, flexWrap: "wrap",
      }}>
        <div style={{
          width: 44, height: 44, borderRadius: 10,
          background: theme.grad,
          color: "white", display: "flex", alignItems: "center", justifyContent: "center",
          flexShrink: 0,
        }}>
          <Icon name="gift" size={22}/>
        </div>
        <div style={{ flex: 1, minWidth: 200 }}>
          <div style={{ fontWeight: 580, fontSize: 15 }}>Programme de fidélité</div>
          <div style={{ fontSize: 12.5, color: "var(--ink-3)", marginTop: 2, lineHeight: 1.45 }}>
            {data.fideliteRules.visits} visites = {data.fideliteRules.rewardLabel}
          </div>
        </div>
        <button className="btn btn-sm btn-ghost" onClick={() => openModal("fidelite-rules")}>
          Règles
        </button>
        <button className="btn btn-sm btn-ghost cb-press-feedback" onClick={() => setEditorOpen(true)}>
          <Icon name="sparkle" size={13}/> Modifier
        </button>
      </div>

      {/* Modale d'édition, aperçu + apparence + texte */}
      <CardEditorModal
        open={editorOpen} onClose={() => setEditorOpen(false)}
        title="Modifier la carte de fidélité"
        themeId={themeId} onThemeChange={setThemeId}
        text={cardText} onTextChange={setCardText}
        emoji={cardEmoji} onEmojiChange={setCardEmoji}
        textPlaceholder="Fidélité · ClientBase"
        emojiPlaceholder="💖"
        preview={
          <FideliteClientRow
            c={{ id: "_preview", name: "Marie · aperçu", fid: 6, hue: 270 }}
            data={{ ...data, fideliteRules: { ...data.fideliteRules, visits: 10 } }}
            actions={{ adjustFidelite: () => {}, resetFidelite: () => {} }}
            theme={theme} cardText={cardText} cardEmoji={cardEmoji}
          />
        }
      />

      {/* Search */}
      <input value={search} onChange={e => setSearch(e.target.value)}
        placeholder="Rechercher un client…" style={{
        padding: "9px 12px", width: "100%", marginBottom: 14,
        background: "var(--surface)", border: "1px solid var(--line)",
        borderRadius: 8, fontSize: 13.5, fontFamily: "inherit", color: "var(--ink)", outline: "none",
      }}/>

      {/* Client cards list */}
      {sorted.length === 0 ? (
        <div style={{ padding: 40, textAlign: "center", color: "var(--ink-4)", background: "var(--surface)", border: "1px solid var(--line)", borderRadius: 12 }}>
          Aucun client trouvée.
        </div>
      ) : (
        <div style={{
          display: "grid",
          gridTemplateColumns: "repeat(auto-fill, minmax(min(340px, 100%), 1fr))",
          gap: 12,
        }}>
          {sorted.map(c => <FideliteClientRow key={c.id} c={c} data={data} actions={actions} theme={theme} cardText={cardText} cardEmoji={cardEmoji}/>)}
        </div>
      )}
    </div>
  );
};

/* ================================================================
   MARKETING — un seul module, 3 onglets : Campagnes / Avis / Parrainage.
   Envoi simulé via window.cbMarketingSend (cf. point de branchement réel
   dans shared.jsx). Données seedées pour la démo, persistées en local.
================================================================ */
const MKT_SEED_CAMPAIGNS = [
  { id: "c2", channel: "sms", segment: "Clients fidèles", recipients: 38, date: "2026-05-12", opened: 31,
    message: "🌸 -15% sur votre prochaine pose ce mois-ci, merci pour votre fidélité ! Réservez vite." },
  { id: "c1", channel: "email", segment: "Tous les clients", recipients: 124, date: "2026-04-28", opened: 71,
    message: "Nouveautés du printemps : découvrez nos nouvelles couleurs et prenez RDV en ligne." },
];
const MKT_SEED_REVIEWS = [
  { id: "r3", name: "Camille L.", rating: 5, date: "2026-05-16", text: "Toujours au top, Léa est une vraie pro. Je recommande les yeux fermés !" },
  { id: "r2", name: "Sarah M.",   rating: 5, date: "2026-05-10", text: "Pose nickel, tenue parfaite. Salon très propre et accueillant." },
  { id: "r1", name: "Inès D.",    rating: 4, date: "2026-05-03", text: "Très contente du résultat, juste un peu d'attente." },
];
const MKT_SEED_REFERRALS = [
  { id: "p3", name: "Julie B.", date: "2026-05-15", status: "Récompense versée" },
  { id: "p2", name: "Léa T.",   date: "2026-05-09", status: "1er RDV pris" },
  { id: "p1", name: "Manon R.", date: "2026-05-02", status: "Inscrite" },
];

const _mktCard = { background: "var(--surface)", border: "1px solid var(--line)", borderRadius: 12, padding: 18 };
const _mktFmtDate = (ymd) => {
  try {
    const [y, m, d] = ymd.split("-").map(Number);
    return new Date(y, m - 1, d).toLocaleDateString("fr-FR", { day: "numeric", month: "short", year: "numeric" });
  } catch { return ymd; }
};

const MktStars = ({ n = 0, size = 14 }) => (
  <span aria-label={`${n} sur 5`} style={{ display: "inline-flex", gap: 1, color: "var(--gold, #F59E0B)" }}>
    {[1, 2, 3, 4, 5].map(i => (
      <span key={i} style={{ color: i <= n ? "#F59E0B" : "var(--line-strong)", fontSize: size, lineHeight: 1 }}>★</span>
    ))}
  </span>
);

// Bandeau « envoi simulé » — rappel honnête que rien n'est réellement envoyé.
const MktSimNote = () => (
  <div style={{
    display: "flex", alignItems: "center", gap: 8,
    padding: "8px 12px", marginBottom: 14,
    background: "var(--accent-soft)", border: "1px solid var(--accent-soft-2)",
    borderRadius: 9, fontSize: 12, color: "var(--accent-ink)",
  }}>
    <Icon name="zap" size={13}/>
    <span>Envoi <strong>simulé</strong> pour l'instant — l'interface est complète, le branchement d'un vrai fournisseur SMS/email viendra ensuite.</span>
  </div>
);

/* En-tête commun aux 3 modules marketing (icône + titre + sous-titre). */
const MktHeader = ({ icon, title, sub }) => (
  <div style={{
    ..._mktCard, padding: "14px 18px", marginBottom: 14,
    display: "flex", alignItems: "center", gap: 14, flexWrap: "wrap",
  }}>
    <div style={{
      width: 44, height: 44, borderRadius: 10,
      background: "linear-gradient(135deg, var(--accent), var(--accent-ink))",
      color: "white", display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0,
    }}>
      <Icon name={icon} size={22}/>
    </div>
    <div style={{ flex: 1, minWidth: 200 }}>
      <div style={{ fontWeight: 580, fontSize: 15 }}>{title}</div>
      <div style={{ fontSize: 12.5, color: "var(--ink-3)", marginTop: 2 }}>{sub}</div>
    </div>
  </div>
);

/* Packs de crédits SMS — prix de vente (marge incluse sur le coût ~0,05 €/SMS).
   ⚠️ Achat SIMULÉ : le jour du branchement, "Acheter" ouvrira Stripe Checkout
   et un webhook créditera le compte côté serveur (cf. explication SMS). */
const SMS_PACKS = [
  { id: "p100",  qty: 100,  price: 9,  popular: false },
  { id: "p500",  qty: 500,  price: 39, popular: true },
  { id: "p1000", qty: 1000, price: 69, popular: false },
];

/* ---- Onglet Campagnes ---- */
const MktCampaigns = ({ data }) => {
  const clients = data.clients || [];
  const [campaigns, setCampaigns] = useLocalJSON("cb_mkt_campaigns", MKT_SEED_CAMPAIGNS);
  const [smsCredits, setSmsCredits] = useLocalJSON("cb_sms_credits", 50);
  const [buyOpen, setBuyOpen] = React.useState(false);
  const [buying, setBuying] = React.useState(null); // id du pack en cours d'achat
  const [channel, setChannel] = React.useState("sms");
  const [segId, setSegId] = React.useState("all");
  const [message, setMessage] = React.useState("");
  const [sending, setSending] = React.useState(false);

  // Achat d'un pack — SIMULÉ (paiement Stripe à brancher plus tard).
  const buyPack = async (pack) => {
    setBuying(pack.id);
    await new Promise(r => setTimeout(r, 800)); // simule l'aller-retour Stripe
    setSmsCredits(c => (Number(c) || 0) + pack.qty);
    setBuying(null);
    setBuyOpen(false);
    showToast(`Paiement simulé · ${pack.qty} crédits SMS ajoutés`);
  };

  const segments = [
    { id: "all",   label: "Tous les clients",  filter: () => true },
    { id: "loyal", label: "Clients fidèles",   filter: c => (c.fid || 0) >= 5 },
    { id: "new",   label: "Nouveaux clients",  filter: c => (c.fid || 0) <= 1 },
  ];
  const seg = segments.find(s => s.id === segId) || segments[0];
  const recipients = clients.filter(seg.filter);

  const templates = [
    "🌸 -15% sur votre prochaine prestation ce mois-ci ! Réservez vite.",
    "Ça fait un moment ! On vous garde un créneau cette semaine ?",
    "Joyeux anniversaire 🎂 ! Profitez de -20% sur la presta de votre choix.",
  ];

  const send = async () => {
    if (!message.trim()) { showToast("Écrivez un message avant d'envoyer.", "warn"); return; }
    if (recipients.length === 0) { showToast("Aucun destinataire dans ce segment.", "warn"); return; }
    // Les SMS consomment des crédits (1 par destinataire). L'email est gratuit.
    if (channel === "sms" && (Number(smsCredits) || 0) < recipients.length) {
      showToast(`Crédits SMS insuffisants (${smsCredits} restants pour ${recipients.length} envois).`, "warn");
      setBuyOpen(true);
      return;
    }
    setSending(true);
    try {
      const res = await window.cbMarketingSend({ channel, recipients: recipients.map(c => c.id), message });
      if (res && res.ok) {
        if (channel === "sms") setSmsCredits(c => (Number(c) || 0) - recipients.length);
        setCampaigns(prev => [{
          id: "c" + Date.now().toString(36),
          channel, segment: seg.label, recipients: recipients.length,
          date: new Date().toISOString().slice(0, 10), opened: 0, message: message.trim(),
        }, ...prev]);
        showToast(`Campagne envoyée à ${recipients.length} client${recipients.length > 1 ? "s" : ""} (simulé)`);
        setMessage("");
      } else {
        showToast("Échec de l'envoi.", "warn");
      }
    } catch { showToast("Échec de l'envoi.", "warn"); }
    setSending(false);
  };

  const maxLen = channel === "sms" ? 160 : 600;

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
      <MktHeader icon="chat" title="Campagnes" sub="Envoyez SMS et emails à vos clients en quelques clics."/>
      <MktSimNote/>

      {/* Solde de crédits SMS + achat */}
      <div style={{ ..._mktCard, display: "flex", alignItems: "center", gap: 14, flexWrap: "wrap" }}>
        <div style={{
          width: 42, height: 42, borderRadius: 10, flexShrink: 0,
          background: "var(--accent-soft)", color: "var(--accent-ink)",
          display: "flex", alignItems: "center", justifyContent: "center",
        }}>
          <Icon name="phone" size={19}/>
        </div>
        <div style={{ flex: 1, minWidth: 140 }}>
          <div style={{ fontFamily: "var(--ff-display)", fontSize: 20, fontWeight: 680, letterSpacing: "-0.02em", lineHeight: 1 }}>
            {smsCredits} <span style={{ fontSize: 13, color: "var(--ink-4)", fontWeight: 500 }}>crédits SMS</span>
          </div>
          <div style={{ fontSize: 12, color: "var(--ink-4)", marginTop: 3 }}>1 crédit = 1 SMS envoyé · l'email est gratuit</div>
        </div>
        <button onClick={() => setBuyOpen(v => !v)} className="btn btn-sm btn-accent" style={{ flexShrink: 0 }}>
          <Icon name="plus" size={14}/> Acheter des crédits
        </button>
      </div>

      {/* Packs (dépliable) */}
      {buyOpen && (
        <div style={{ ..._mktCard }}>
          <div style={{ fontWeight: 600, fontSize: 14.5, marginBottom: 4 }}>Recharger en SMS</div>
          <div style={{ fontSize: 12.5, color: "var(--ink-4)", marginBottom: 14 }}>
            Paiement sécurisé (simulé pour l'instant). Les crédits sont ajoutés immédiatement.
          </div>
          <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(140px, 1fr))", gap: 10 }}>
            {SMS_PACKS.map(pack => (
              <div key={pack.id} style={{
                position: "relative", padding: "16px 14px", textAlign: "center",
                background: "var(--surface)", borderRadius: 12,
                border: pack.popular ? "1.5px solid var(--accent)" : "1px solid var(--line)",
              }}>
                {pack.popular && (
                  <span style={{
                    position: "absolute", top: -9, left: "50%", transform: "translateX(-50%)",
                    background: "var(--accent)", color: "#fff", fontSize: 10, fontWeight: 700,
                    padding: "2px 9px", borderRadius: 999, whiteSpace: "nowrap",
                  }}>POPULAIRE</span>
                )}
                <div style={{ fontFamily: "var(--ff-display)", fontSize: 22, fontWeight: 700, letterSpacing: "-0.02em" }}>{pack.qty}</div>
                <div style={{ fontSize: 11.5, color: "var(--ink-4)", marginBottom: 10 }}>SMS</div>
                <div style={{ fontSize: 15, fontWeight: 620, marginBottom: 10 }}>{pack.price} €</div>
                <button onClick={() => buyPack(pack)} disabled={buying === pack.id}
                  className="btn btn-sm btn-accent" style={{ width: "100%" }}>
                  {buying === pack.id ? "Paiement…" : "Acheter"}
                </button>
              </div>
            ))}
          </div>
        </div>
      )}

      {/* Composer */}
      <div style={{ ..._mktCard }}>
        <div style={{ fontWeight: 600, fontSize: 14.5, marginBottom: 14 }}>Nouvelle campagne</div>

        {/* Canal */}
        <div style={{ display: "flex", gap: 8, marginBottom: 14 }}>
          {[{ id: "sms", label: "SMS", icon: "phone" }, { id: "email", label: "Email", icon: "mail" }].map(ch => {
            const on = channel === ch.id;
            return (
              <button key={ch.id} onClick={() => setChannel(ch.id)} style={{
                flex: 1, display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 7,
                padding: "10px 12px", borderRadius: 9, cursor: "pointer", fontFamily: "inherit",
                fontSize: 13.5, fontWeight: on ? 620 : 520,
                background: on ? "var(--accent-soft)" : "var(--surface)",
                color: on ? "var(--accent-ink)" : "var(--ink-3)",
                border: on ? "1px solid var(--accent)" : "1px solid var(--line)",
              }}>
                <Icon name={ch.icon} size={15}/>{ch.label}
              </button>
            );
          })}
        </div>

        {/* Segment */}
        <label style={{ display: "block", fontSize: 13, color: "var(--ink-3)", fontWeight: 540, marginBottom: 7 }}>Destinataires</label>
        <div style={{ display: "flex", gap: 8, flexWrap: "wrap", marginBottom: 14 }}>
          {segments.map(s => {
            const on = segId === s.id;
            const count = clients.filter(s.filter).length;
            return (
              <button key={s.id} onClick={() => setSegId(s.id)} style={{
                padding: "7px 12px", borderRadius: 999, cursor: "pointer", fontFamily: "inherit",
                fontSize: 12.5, fontWeight: on ? 600 : 520,
                background: on ? "var(--accent)" : "var(--surface)",
                color: on ? "#fff" : "var(--ink-2)",
                border: on ? "1px solid var(--accent)" : "1px solid var(--line)",
              }}>
                {s.label} · {count}
              </button>
            );
          })}
        </div>

        {/* Message */}
        <label style={{ display: "block", fontSize: 13, color: "var(--ink-3)", fontWeight: 540, marginBottom: 7 }}>Message</label>
        <textarea value={message} maxLength={maxLen} onChange={e => setMessage(e.target.value)}
          rows={3} placeholder={channel === "sms" ? "Votre SMS (160 caractères max)…" : "Votre email…"}
          style={{
            width: "100%", padding: "11px 13px", background: "var(--surface)",
            border: "1px solid var(--line)", borderRadius: 9, fontSize: 14, fontFamily: "inherit",
            color: "var(--ink)", outline: "none", resize: "vertical",
          }}/>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", margin: "6px 0 12px" }}>
          <div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>
            {templates.map((t, i) => (
              <button key={i} onClick={() => setMessage(t)} style={{
                padding: "4px 9px", borderRadius: 999, cursor: "pointer", fontFamily: "inherit",
                fontSize: 11.5, color: "var(--ink-3)", background: "var(--bg-alt)", border: "1px solid var(--line)",
              }}>Modèle {i + 1}</button>
            ))}
          </div>
          <span style={{ fontSize: 11.5, color: "var(--ink-4)", fontFamily: "var(--ff-mono)" }}>{message.length}/{maxLen}</span>
        </div>

        <button onClick={send} disabled={sending} className="btn btn-accent" style={{ width: "100%" }}>
          {sending ? "Envoi…" : `Envoyer à ${recipients.length} destinataire${recipients.length > 1 ? "s" : ""}`}
        </button>
      </div>

      {/* Historique */}
      <div>
        <div style={{ fontSize: 11.5, textTransform: "uppercase", letterSpacing: "0.08em", color: "var(--ink-4)", fontWeight: 680, margin: "4px 0 10px" }}>
          Campagnes envoyées
        </div>
        <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
          {campaigns.length === 0 && (
            <div style={{ ..._mktCard, color: "var(--ink-4)", fontSize: 13, textAlign: "center" }}>Aucune campagne pour l'instant.</div>
          )}
          {campaigns.map(c => (
            <div key={c.id} style={{ ..._mktCard, display: "flex", gap: 14, alignItems: "flex-start" }}>
              <div style={{
                width: 38, height: 38, borderRadius: 9, flexShrink: 0,
                background: "var(--accent-soft)", color: "var(--accent-ink)",
                display: "flex", alignItems: "center", justifyContent: "center",
              }}>
                <Icon name={c.channel === "email" ? "mail" : "phone"} size={17}/>
              </div>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
                  <strong style={{ fontSize: 13.5, color: "var(--ink)" }}>{c.segment}</strong>
                  <span style={{ fontSize: 11.5, color: "var(--ink-4)" }}>· {c.recipients} destinataires · {_mktFmtDate(c.date)}</span>
                </div>
                <div style={{ fontSize: 13, color: "var(--ink-3)", marginTop: 4, lineHeight: 1.5 }}>{c.message}</div>
                <div style={{ display: "flex", gap: 10, marginTop: 8 }}>
                  <span style={{ fontSize: 11, fontWeight: 600, color: "#065F46", background: "var(--green-soft, #D1FAE5)", padding: "2px 8px", borderRadius: 999 }}>Envoyée</span>
                  {c.opened > 0 && <span style={{ fontSize: 11.5, color: "var(--ink-4)" }}>{Math.round((c.opened / c.recipients) * 100)}% ouverts</span>}
                </div>
              </div>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
};

/* ---- Onglet Avis ---- */
const MktReviews = ({ data }) => {
  const clients = data.clients || [];
  const [reviews] = useLocalJSON("cb_mkt_reviews", MKT_SEED_REVIEWS);
  const [auto, setAuto] = useLocalString("cb_mkt_review_auto", "1");
  const [reqChannel, setReqChannel] = useLocalString("cb_review_channel", "email");
  const [reqMsg, setReqMsg] = useLocalString("cb_review_msg", "Merci de votre visite ! Si vous avez 30 s, votre avis nous aiderait beaucoup ⭐ {lien}");
  const [sending, setSending] = React.useState(false);
  const avg = reviews.length ? (reviews.reduce((s, r) => s + r.rating, 0) / reviews.length) : 0;

  const requestReviews = () => {
    // Envoi pas encore fonctionnel → écran unique « bientôt ».
    window.cbComingSoon && window.cbComingSoon({
      title: "Demande d'avis automatique",
      message: "L'envoi des invitations (SMS / email) arrive très bientôt. Vous pouvez déjà choisir le canal et préparer votre message ci-dessus.",
    });
  };

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
      <MktHeader icon="star" title="Avis" sub="Collectez et suivez les avis de vos clients."/>
      <MktCredits/>

      {/* Résumé note moyenne */}
      <div style={{ ..._mktCard, display: "flex", alignItems: "center", gap: 18, flexWrap: "wrap" }}>
        <div>
          <div style={{ fontSize: 34, fontWeight: 700, letterSpacing: "-0.03em", lineHeight: 1 }}>{avg.toFixed(1)}</div>
          <MktStars n={Math.round(avg)} size={15}/>
          <div style={{ fontSize: 12, color: "var(--ink-4)", marginTop: 4 }}>{reviews.length} avis</div>
        </div>
        <div style={{ flex: 1, minWidth: 200, fontSize: 13.5, color: "var(--ink-3)", lineHeight: 1.5 }}>
          Invitez vos clients récents à laisser un avis après leur RDV. Configurez le message ci-dessous.
        </div>
      </div>

      {/* Configurer la demande d'avis */}
      <div style={{ ..._mktCard }}>
        <div style={{ fontWeight: 600, fontSize: 14.5, marginBottom: 12 }}>Message de demande d'avis</div>

        {/* Canal */}
        <div style={{ display: "flex", gap: 8, marginBottom: 10 }}>
          {[{ id: "email", label: "Email (gratuit)", icon: "mail" }, { id: "sms", label: "SMS", icon: "phone" }].map(ch => {
            const on = reqChannel === ch.id;
            return (
              <button key={ch.id} onClick={() => setReqChannel(ch.id)} style={{
                flex: 1, display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 7,
                padding: "10px 12px", borderRadius: 9, cursor: "pointer", fontFamily: "inherit",
                fontSize: 13.5, fontWeight: on ? 620 : 520,
                background: on ? "var(--accent-soft)" : "var(--surface)",
                color: on ? "var(--accent-ink)" : "var(--ink-3)",
                border: on ? "1px solid var(--accent)" : "1px solid var(--line)",
              }}>
                <Icon name={ch.icon} size={15}/>{ch.label}
              </button>
            );
          })}
        </div>

        {/* Conseil quota si SMS choisi */}
        {reqChannel === "sms" && (
          <div style={{
            display: "flex", alignItems: "flex-start", gap: 8, marginBottom: 10,
            padding: "9px 12px", background: "var(--warn-soft, oklch(96% 0.03 55))",
            border: "1px solid var(--gold-soft, #FEF3C7)", borderRadius: 9, fontSize: 12, color: "var(--ink-2)", lineHeight: 1.5,
          }}>
            <span>⚠️</span>
            <span>Le SMS consomme votre quota. Pour une demande d'avis (non essentielle), l'<strong>email gratuit</strong> est souvent préférable — gardez vos SMS pour les rappels de RDV.</span>
          </div>
        )}

        <textarea value={reqMsg} onChange={e => setReqMsg(e.target.value)} rows={2} style={{
          width: "100%", padding: "10px 12px", background: "var(--bg-alt)", border: "1px solid var(--line)",
          borderRadius: 9, fontSize: 13.5, fontFamily: "inherit", color: "var(--ink)", outline: "none", resize: "vertical",
        }}/>
        <div style={{ fontSize: 11, color: "var(--ink-4)", margin: "5px 0 12px" }}>
          Variable : <code>{"{lien}"}</code> = lien pour laisser l'avis.
        </div>
        <button onClick={requestReviews} className="btn btn-accent btn-sm">
          <Icon name="star" size={14}/> Demander un avis aux clients récents
        </button>
      </div>

      {/* Auto-demande */}
      <div style={{ ..._mktCard, display: "flex", alignItems: "center", justifyContent: "space-between", gap: 14 }}>
        <div>
          <div style={{ fontSize: 14, fontWeight: 600 }}>Demande automatique après RDV</div>
          <div style={{ fontSize: 12.5, color: "var(--ink-4)", marginTop: 2 }}>Envoyer l'invitation 2h après chaque RDV terminé.</div>
        </div>
        <button onClick={() => setAuto(auto === "1" ? "" : "1")} aria-pressed={auto === "1"} style={{
          width: 46, height: 27, borderRadius: 999, flexShrink: 0, cursor: "pointer", position: "relative",
          background: auto === "1" ? "var(--accent)" : "var(--line-strong)", border: "none", transition: "background .2s",
        }}>
          <span style={{
            position: "absolute", top: 3, left: auto === "1" ? 22 : 3, width: 21, height: 21,
            borderRadius: "50%", background: "#fff", transition: "left .2s", boxShadow: "0 1px 3px rgba(0,0,0,.2)",
          }}/>
        </button>
      </div>

      {/* Liste des avis */}
      <div>
        <div style={{ fontSize: 11.5, textTransform: "uppercase", letterSpacing: "0.08em", color: "var(--ink-4)", fontWeight: 680, margin: "4px 0 10px" }}>
          Avis reçus
        </div>
        <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
          {reviews.map(r => (
            <div key={r.id} style={{ ..._mktCard }}>
              <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: 10, marginBottom: 6 }}>
                <strong style={{ fontSize: 13.5, color: "var(--ink)" }}>{r.name}</strong>
                <span style={{ fontSize: 11.5, color: "var(--ink-4)" }}>{_mktFmtDate(r.date)}</span>
              </div>
              <MktStars n={r.rating}/>
              <div style={{ fontSize: 13, color: "var(--ink-3)", marginTop: 6, lineHeight: 1.5 }}>{r.text}</div>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
};

/* ---- Bandeau crédits (SMS + emails restants selon la formule) ----
   Affiché en haut des modules Relances et Avis. Les crédits SMS sont
   INCLUS dans l'abonnement (pas de recharge à l'unité) : pour en avoir plus,
   on monte de formule. Pendant la bêta : formule Premium (quota max). */
const MKT_PLAN = { name: "Premium", sms: 500, email: "illimité" };

const MktCredits = () => {
  const [smsCredits] = useLocalJSON("cb_sms_credits", 500);
  const stat = (label, value, sub) => (
    <div style={{ flex: 1, minWidth: 120 }}>
      <div style={{ fontFamily: "var(--ff-display)", fontSize: 22, fontWeight: 700, letterSpacing: "-0.02em", lineHeight: 1 }}>{value}</div>
      <div style={{ fontSize: 12, color: "var(--ink-3)", marginTop: 3, fontWeight: 540 }}>{label}</div>
      <div style={{ fontSize: 11, color: "var(--ink-4)", marginTop: 1 }}>{sub}</div>
    </div>
  );
  return (
    <div style={{ ..._mktCard, marginBottom: 16 }}>
      <div style={{ display: "flex", alignItems: "center", gap: 16, flexWrap: "wrap" }}>
        <span style={{
          width: 40, height: 40, borderRadius: 10, flexShrink: 0,
          background: "var(--accent-soft)", color: "var(--accent-ink)",
          display: "flex", alignItems: "center", justifyContent: "center",
        }}><Icon name="zap" size={19}/></span>
        {stat("SMS restants ce mois", smsCredits, `inclus dans la formule ${MKT_PLAN.name}`)}
        {stat("Emails", MKT_PLAN.email === "illimité" ? "illimités" : MKT_PLAN.email, `inclus dans la formule ${MKT_PLAN.name}`)}
        <div style={{ flexShrink: 0, fontSize: 11.5, color: "var(--ink-4)", maxWidth: 160, lineHeight: 1.45, textAlign: "right" }}>
          Besoin de plus de SMS ? Ils sont inclus selon la formule — montez de formule pour augmenter le quota.
        </div>
      </div>
    </div>
  );
};

/* ---- Onglet Relances (re-engagement automatique SMS/email) ---- */
const RELANCE_DEFAULTS = [
  { id: "inactive", name: "On ne vous a pas vu·e", icon: "clock",
    desc: "Pour les clients sans rendez-vous depuis un moment.",
    type: "inactivity", weeks: 8, sms: true, email: true, active: true,
    msg: "Bonjour {prenom} 🌸 ça fait un moment ! On vous garde un créneau cette semaine ? 👉 {lien}" },
  { id: "birthday", name: "Anniversaire", icon: "gift",
    desc: "Un message (et une petite offre) le mois de l'anniversaire.",
    type: "birthday", weeks: 0, sms: true, email: false, active: true,
    msg: "Joyeux anniversaire {prenom} 🎂 ! -20% sur la prestation de votre choix ce mois-ci." },
  { id: "rebook", name: "Rappel d'entretien", icon: "calendar",
    desc: "Propose de reprendre RDV quelques semaines après le dernier.",
    type: "after", weeks: 4, sms: true, email: true, active: false,
    msg: "Bonjour {prenom}, c'est le moment de votre entretien ✨ Reprenez RDV ici 👉 {lien}" },
];

const MktRelances = ({ data }) => {
  const clients = data.clients || [];
  const appts = data.appointments || [];
  const [rules, setRules] = useLocalJSON("cb_mkt_relances", RELANCE_DEFAULTS);

  const todayOff = (window.cbTodayOffset && window.cbTodayOffset()) || 0;
  // Dernière visite (RDV terminé) par client, en offset de jour.
  const lastVisit = React.useMemo(() => {
    const m = {};
    for (const a of appts) {
      if (a && a.done && a.clientId != null) {
        if (m[a.clientId] == null || a.day > m[a.clientId]) m[a.clientId] = a.day;
      }
    }
    return m;
  }, [appts]);

  const reachFor = (rule) => {
    try {
      if (rule.type === "birthday") {
        const ref = window.cbDateForOffset ? window.cbDateForOffset(todayOff) : new Date();
        const mo = ref.getMonth();
        return clients.filter(c => { if (!c.birthday) return false; const d = new Date(c.birthday); return !isNaN(d) && d.getMonth() === mo; }).length;
      }
      const days = (rule.weeks || 0) * 7;
      return clients.filter(c => { const lv = lastVisit[c.id]; return lv != null && (todayOff - lv) >= days; }).length;
    } catch { return 0; }
  };

  const update = (id, patch) => setRules(prev => prev.map(r => r.id === id ? { ...r, ...patch } : r));
  const activeCount = rules.filter(r => r.active).length;

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
      <MktHeader icon="bell" title="Relances" sub="Faites revenir vos clients automatiquement, par SMS ou email."/>
      <MktCredits/>
      <MktSimNote/>

      <div style={{ ..._mktCard, display: "flex", gap: 12, alignItems: "flex-start" }}>
        <span style={{ fontSize: 20 }}>⚡</span>
        <div style={{ fontSize: 13, color: "var(--ink-3)", lineHeight: 1.5 }}>
          Les relances <strong>activées</strong> partent toutes seules au bon moment ({activeCount} active{activeCount > 1 ? "s" : ""}).
          Les SMS utilisent vos crédits, l'email est gratuit.
        </div>
      </div>

      {rules.map(rule => {
        const reach = reachFor(rule);
        return (
          <div key={rule.id} style={{ ..._mktCard, opacity: rule.active ? 1 : 0.72 }}>
            <div style={{ display: "flex", alignItems: "center", gap: 12, marginBottom: 10 }}>
              <span style={{
                width: 38, height: 38, borderRadius: 9, flexShrink: 0,
                background: "var(--accent-soft)", color: "var(--accent-ink)",
                display: "flex", alignItems: "center", justifyContent: "center",
              }}>
                <Icon name={rule.icon} size={18}/>
              </span>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontWeight: 620, fontSize: 14.5 }}>{rule.name}</div>
                <div style={{ fontSize: 12, color: "var(--ink-4)" }}>{rule.desc}</div>
              </div>
              <button onClick={() => update(rule.id, { active: !rule.active })} aria-pressed={rule.active} style={{
                width: 46, height: 27, borderRadius: 999, flexShrink: 0, cursor: "pointer", position: "relative",
                background: rule.active ? "var(--accent)" : "var(--line-strong)", border: "none", transition: "background .2s",
              }}>
                <span style={{ position: "absolute", top: 3, left: rule.active ? 22 : 3, width: 21, height: 21, borderRadius: "50%", background: "#fff", transition: "left .2s", boxShadow: "0 1px 3px rgba(0,0,0,.2)" }}/>
              </button>
            </div>

            <div style={{ display: "flex", gap: 8, flexWrap: "wrap", alignItems: "center", marginBottom: 10 }}>
              <span style={{ fontSize: 11.5, fontWeight: 600, color: "var(--accent-ink)", background: "var(--accent-soft)", padding: "3px 10px", borderRadius: 999 }}>
                ≈ {reach} client{reach > 1 ? "s" : ""} concerné{reach > 1 ? "s" : ""}
              </span>
              {[{ k: "sms", label: "SMS" }, { k: "email", label: "Email" }].map(ch => (
                <button key={ch.k} onClick={() => update(rule.id, { [ch.k]: !rule[ch.k] })} style={{
                  padding: "4px 11px", borderRadius: 999, cursor: "pointer", fontFamily: "inherit", fontSize: 12, fontWeight: rule[ch.k] ? 600 : 520,
                  background: rule[ch.k] ? "var(--accent)" : "var(--surface)", color: rule[ch.k] ? "#fff" : "var(--ink-3)",
                  border: rule[ch.k] ? "1px solid var(--accent)" : "1px solid var(--line)",
                }}>{ch.label}</button>
              ))}
              {rule.type !== "birthday" && (
                <span style={{ display: "inline-flex", alignItems: "center", gap: 6, fontSize: 12, color: "var(--ink-3)" }}>
                  après
                  <input type="number" min="1" value={rule.weeks} onChange={e => update(rule.id, { weeks: Math.max(1, Number(e.target.value) || 1) })} style={{
                    width: 52, padding: "5px 8px", background: "var(--bg-alt)", border: "1px solid var(--line)", borderRadius: 8, fontSize: 12.5, fontFamily: "inherit", color: "var(--ink)", textAlign: "center",
                  }}/>
                  semaines
                </span>
              )}
            </div>

            <textarea value={rule.msg} onChange={e => update(rule.id, { msg: e.target.value })} rows={2} style={{
              width: "100%", padding: "10px 12px", background: "var(--bg-alt)", border: "1px solid var(--line)", borderRadius: 9, fontSize: 13.5, fontFamily: "inherit", color: "var(--ink)", outline: "none", resize: "vertical",
            }}/>
            <div style={{ fontSize: 11, color: "var(--ink-4)", marginTop: 5 }}>
              Variables : <code>{"{prenom}"}</code> = prénom du client · <code>{"{lien}"}</code> = lien de réservation.
            </div>
          </div>
        );
      })}
    </div>
  );
};

/* ================================================================
   ESPACES DE RÔLE — Ambassadrice / Co-fondatrice / Admin.
   Ce sont des ONGLETS EN PLUS du compte pro (l'utilisatrice garde tout
   son espace pro). Données de démo ; les actions non encore branchées
   ouvrent l'écran unique « bientôt » (window.cbComingSoon).
================================================================ */
const _roleCard = { background: "var(--surface)", border: "1px solid var(--line)", borderRadius: 12, padding: 18 };

const RoleHeader = ({ icon, title, sub }) => (
  <div style={{ ..._roleCard, padding: "16px 18px", marginBottom: 14, display: "flex", alignItems: "center", gap: 14, flexWrap: "wrap" }}>
    <div style={{
      width: 46, height: 46, borderRadius: 11, flexShrink: 0,
      background: "linear-gradient(135deg, var(--accent), var(--accent-ink))",
      color: "#fff", display: "flex", alignItems: "center", justifyContent: "center",
    }}><Icon name={icon} size={22}/></div>
    <div style={{ flex: 1, minWidth: 0 }}>
      <div style={{ fontWeight: 620, fontSize: 16, letterSpacing: "-0.01em" }}>{title}</div>
      <div style={{ fontSize: 12.5, color: "var(--ink-3)", marginTop: 2 }}>{sub}</div>
    </div>
    <span style={{
      flexShrink: 0, fontSize: 11, fontWeight: 600, color: "var(--ink-4)",
      background: "var(--bg-alt)", border: "1px solid var(--line)", borderRadius: 999, padding: "4px 10px",
    }}>+ en plus de votre compte pro</span>
  </div>
);

const RoleStat = ({ value, label, sub, accent }) => (
  <div style={{ ..._roleCard, flex: 1, minWidth: 130 }}>
    <div style={{ fontFamily: "var(--ff-display)", fontSize: 26, fontWeight: 700, letterSpacing: "-0.025em", lineHeight: 1, color: accent ? "var(--accent-ink)" : "var(--ink)" }}>{value}</div>
    <div style={{ fontSize: 12.5, color: "var(--ink-2)", marginTop: 5, fontWeight: 540 }}>{label}</div>
    {sub && <div style={{ fontSize: 11, color: "var(--ink-4)", marginTop: 1 }}>{sub}</div>}
  </div>
);

const _comingSoon = (title, message) => () => window.cbComingSoon && window.cbComingSoon({ title, message });

/* ---- Espace Ambassadrice ---- */
const AppAmbassadrice = ({ data }) => {
  const slug = (data.business && data.business.bookingSlug) || "votre-lien";
  const link = `clientbase.fr/?parrain=${slug}`;
  const copy = () => { try { navigator.clipboard.writeText(link); showToast("Lien copié !"); } catch { showToast("Copie impossible", "warn"); } };
  const refs = [
    { name: "Studio Léa", date: "12 mai", status: "Abonnée" },
    { name: "Sarah Coiffure", date: "9 mai", status: "1er mois d'essai" },
    { name: "Manon Beauté", date: "2 mai", status: "Inscrite (essai)" },
  ];
  const shareKit = [
    "✨ J'utilise ClientBase pour gérer mes RDV et mes clientes, je recommande à 100 % ! −10 % avec mon lien 👉 " + link,
    "Marre de gérer tes RDV dans tous les sens ? Teste ClientBase, −10 % avec mon code 👉 " + link,
  ];
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
      <RoleHeader icon="heart" title="Espace Ambassadrice" sub="Recommandez ClientBase à votre communauté."/>

      {/* Avantage ambassadrice : abonnement Premium offert */}
      <div style={{
        ..._roleCard, display: "flex", alignItems: "center", gap: 14, flexWrap: "wrap",
        background: "linear-gradient(135deg, var(--accent-soft), var(--bg-alt))", border: "1px solid var(--accent-soft-2)",
      }}>
        <span style={{ fontSize: 26 }}>🎁</span>
        <div style={{ flex: 1, minWidth: 200 }}>
          <div style={{ fontWeight: 680, fontSize: 15, color: "var(--ink)" }}>Abonnement <strong>Premium offert</strong></div>
          <div style={{ fontSize: 12.5, color: "var(--ink-3)", marginTop: 2 }}>En tant qu'ambassadrice, vous avez la meilleure formule gratuitement, à vie.</div>
        </div>
        <span style={{ flexShrink: 0, fontSize: 11.5, fontWeight: 700, color: "#fff", background: "var(--accent)", borderRadius: 999, padding: "5px 12px" }}>0 €/mois</span>
      </div>

      <div style={{ display: "flex", gap: 12, flexWrap: "wrap" }}>
        <RoleStat value="3" label="Pros parrainés" sub="depuis le début"/>
        <RoleStat value="1" label="En attente" sub="essai en cours"/>
        <RoleStat value="−10 %" label="Pour vos filleul·e·s" accent sub="avec votre code"/>
      </div>

      <div style={{ ..._roleCard }}>
        <div style={{ fontWeight: 600, fontSize: 14.5, marginBottom: 4 }}>Votre lien & code de parrainage</div>
        <div style={{ fontSize: 12.5, color: "var(--ink-4)", marginBottom: 10 }}>Partagez-le : toute personne qui s'inscrit avec profite de <strong>−10 %</strong>.</div>
        <div style={{ display: "flex", gap: 8, marginBottom: 10 }}>
          <input readOnly value={link} style={{ flex: 1, padding: "10px 12px", background: "var(--bg-alt)", border: "1px solid var(--line)", borderRadius: 9, fontSize: 13, fontFamily: "var(--ff-mono)", color: "var(--ink-2)" }}/>
          <button onClick={copy} className="btn btn-sm btn-ghost" style={{ flexShrink: 0 }}><Icon name="copy" size={14}/> Copier</button>
        </div>
        <div style={{ fontSize: 12.5, color: "var(--ink-3)" }}>Code promo : <strong style={{ fontFamily: "var(--ff-mono)", color: "var(--accent-ink)" }}>LEA10</strong> — <strong>−10 %</strong> pour vos filleul·e·s.</div>
      </div>

      <div style={{ ..._roleCard }}>
        <div style={{ fontWeight: 600, fontSize: 14.5, marginBottom: 12 }}>Vos pros parrainés</div>
        <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
          {refs.map((r, i) => (
            <div key={i} style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 10, padding: "11px 13px", background: "var(--bg-alt)", borderRadius: 10 }}>
              <div><div style={{ fontSize: 13.5, fontWeight: 600 }}>{r.name}</div><div style={{ fontSize: 11.5, color: "var(--ink-4)" }}>{r.date}</div></div>
              <span style={{ fontSize: 12, fontWeight: 600, color: "var(--ink-3)" }}>{r.status}</span>
            </div>
          ))}
        </div>
      </div>

      <div style={{ ..._roleCard }}>
        <div style={{ fontWeight: 600, fontSize: 14.5, marginBottom: 12 }}>Kit de partage</div>
        <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
          {shareKit.map((t, i) => (
            <div key={i} style={{ display: "flex", gap: 10, alignItems: "flex-start", padding: "11px 13px", background: "var(--bg-alt)", borderRadius: 10 }}>
              <div style={{ flex: 1, fontSize: 12.5, color: "var(--ink-2)", lineHeight: 1.5 }}>{t}</div>
              <button onClick={() => { try { navigator.clipboard.writeText(t); showToast("Message copié !"); } catch {} }} className="btn btn-sm btn-ghost" style={{ flexShrink: 0 }}><Icon name="copy" size={13}/></button>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
};

/* ---- Espace Co-fondatrice (marketing) ---- */
const AppCofondatrice = ({ data }) => {
  const [annonce, setAnnonce] = React.useState("");
  const signups = [8, 12, 9, 15, 18, 14]; // 6 dernières semaines (démo)
  const maxS = Math.max(...signups);
  const ambass = [
    { name: "Léa B.", pros: 3 }, { name: "Camille R.", pros: 2 }, { name: "Inès D.", pros: 1 },
  ];
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
      <RoleHeader icon="chart" title="Espace Co-fondatrice" sub="Pilotez le marketing de ClientBase."/>

      <div style={{ display: "flex", gap: 12, flexWrap: "wrap" }}>
        <RoleStat value="128" label="Inscrits" sub="depuis le lancement"/>
        <RoleStat value="+14" label="Nouveaux" accent sub="cette semaine"/>
        <RoleStat value="97" label="Pros actifs" sub="utilisent l'app"/>
      </div>

      <div style={{ ..._roleCard }}>
        <div style={{ fontWeight: 600, fontSize: 14.5, marginBottom: 14 }}>Inscriptions (6 dernières semaines)</div>
        <div style={{ display: "flex", alignItems: "flex-end", gap: 10, height: 90 }}>
          {signups.map((v, i) => (
            <div key={i} style={{ flex: 1, display: "flex", flexDirection: "column", alignItems: "center", gap: 6 }}>
              <div style={{ width: "100%", maxWidth: 34, height: `${(v / maxS) * 70}px`, background: "var(--accent)", borderRadius: "6px 6px 0 0" }}/>
              <span style={{ fontSize: 11, color: "var(--ink-4)", fontVariantNumeric: "tabular-nums" }}>{v}</span>
            </div>
          ))}
        </div>
      </div>

      <div style={{ ..._roleCard }}>
        <div style={{ fontWeight: 600, fontSize: 14.5, marginBottom: 4 }}>Annonce à tous les pros</div>
        <div style={{ fontSize: 12.5, color: "var(--ink-4)", marginBottom: 10 }}>S'affichera en bandeau en haut du dashboard de chaque pro.</div>
        <textarea value={annonce} onChange={e => setAnnonce(e.target.value)} rows={2} placeholder="Ex. 🎉 Nouveau : les cartes cadeaux arrivent ! Découvrez-les dans Ma page."
          style={{ width: "100%", padding: "10px 12px", background: "var(--bg-alt)", border: "1px solid var(--line)", borderRadius: 9, fontSize: 13.5, fontFamily: "inherit", color: "var(--ink)", resize: "vertical", marginBottom: 10 }}/>
        <button onClick={_comingSoon("Publier une annonce", "La diffusion d'annonces à tous les pros arrive très bientôt.")} className="btn btn-accent btn-sm">Publier à tous les pros</button>
      </div>

      <div style={{ ..._roleCard }}>
        <div style={{ fontWeight: 600, fontSize: 14.5, marginBottom: 12 }}>Suivi des ambassadrices</div>
        <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
          {ambass.map((a, i) => (
            <div key={i} style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "10px 13px", background: "var(--bg-alt)", borderRadius: 10 }}>
              <span style={{ fontSize: 13.5, fontWeight: 560 }}>{a.name}</span>
              <span style={{ fontSize: 12.5, color: "var(--ink-3)" }}>{a.pros} pro{a.pros > 1 ? "s" : ""} apporté{a.pros > 1 ? "s" : ""}</span>
            </div>
          ))}
        </div>
      </div>

      <div style={{ ..._roleCard }}>
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 12 }}>
          <div style={{ fontWeight: 600, fontSize: 14.5 }}>Avis & témoignages collectés</div>
          <button onClick={_comingSoon("Export des témoignages", "L'export (visuels prêts pour les réseaux) arrive bientôt.")} className="btn btn-sm btn-ghost">Exporter</button>
        </div>
        <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
          {MKT_SEED_REVIEWS.slice(0, 2).map(r => (
            <div key={r.id} style={{ padding: "11px 13px", background: "var(--bg-alt)", borderRadius: 10 }}>
              <div style={{ display: "flex", justifyContent: "space-between" }}><strong style={{ fontSize: 13 }}>{r.name}</strong><MktStars n={r.rating}/></div>
              <div style={{ fontSize: 12.5, color: "var(--ink-3)", marginTop: 4, lineHeight: 1.5 }}>{r.text}</div>
            </div>
          ))}
        </div>
      </div>

      {/* D'où viennent les inscrits (canaux d'acquisition) */}
      <div style={{ ..._roleCard }}>
        <div style={{ fontWeight: 600, fontSize: 14.5, marginBottom: 12 }}>D'où viennent les inscrits</div>
        <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
          {[
            { label: "Instagram", pct: 46, color: "oklch(60% 0.17 30)" },
            { label: "Ambassadrices", pct: 27, color: "var(--accent)" },
            { label: "Bouche-à-oreille", pct: 18, color: "oklch(60% 0.15 160)" },
            { label: "Autres", pct: 9, color: "var(--ink-4)" },
          ].map((c, i) => (
            <div key={i}>
              <div style={{ display: "flex", justifyContent: "space-between", fontSize: 12.5, marginBottom: 4 }}>
                <span style={{ color: "var(--ink-2)", fontWeight: 540 }}>{c.label}</span>
                <span style={{ color: "var(--ink-4)", fontVariantNumeric: "tabular-nums" }}>{c.pct} %</span>
              </div>
              <div style={{ height: 8, background: "var(--bg-alt)", borderRadius: 4, overflow: "hidden" }}>
                <div style={{ width: `${c.pct}%`, height: "100%", background: c.color, borderRadius: 4 }}/>
              </div>
            </div>
          ))}
        </div>
      </div>

      {/* Codes promo & campagnes */}
      <div style={{ ..._roleCard }}>
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 12, gap: 10, flexWrap: "wrap" }}>
          <div style={{ fontWeight: 600, fontSize: 14.5 }}>Codes promo & campagnes</div>
          <button onClick={_comingSoon("Créer un code promo", "La création de codes promo / campagnes d'acquisition arrive bientôt.")} className="btn btn-sm btn-ghost"><Icon name="plus" size={13}/> Créer</button>
        </div>
        <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
          {[
            { code: "BIENVENUE", desc: "−1 mois offert · lancement", uses: 64 },
            { code: "INSTA10", desc: "−10 % · campagne Instagram", uses: 28 },
          ].map((c, i) => (
            <div key={i} style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "11px 13px", background: "var(--bg-alt)", borderRadius: 10 }}>
              <div>
                <strong style={{ fontFamily: "var(--ff-mono)", fontSize: 13, color: "var(--accent-ink)" }}>{c.code}</strong>
                <div style={{ fontSize: 11.5, color: "var(--ink-4)" }}>{c.desc}</div>
              </div>
              <span style={{ fontSize: 12, color: "var(--ink-3)" }}>{c.uses} utilisations</span>
            </div>
          ))}
        </div>
      </div>

      {/* Bibliothèque de contenu prêt à poster */}
      <div style={{ ..._roleCard }}>
        <div style={{ fontWeight: 600, fontSize: 14.5, marginBottom: 12 }}>Bibliothèque de contenu</div>
        <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
          {[
            "📅 « Fini les RDV notés sur un carnet : passez à ClientBase, votre agenda en ligne. »",
            "💅 « Vos clientes réservent toutes seules, 24h/24. Essayez gratuitement. »",
            "🎁 « Offrez une carte cadeau de votre salon en 1 clic — bientôt sur ClientBase. »",
          ].map((t, i) => (
            <div key={i} style={{ display: "flex", gap: 10, alignItems: "flex-start", padding: "11px 13px", background: "var(--bg-alt)", borderRadius: 10 }}>
              <div style={{ flex: 1, fontSize: 12.5, color: "var(--ink-2)", lineHeight: 1.5 }}>{t}</div>
              <button onClick={() => { try { navigator.clipboard.writeText(t); showToast("Copié !"); } catch {} }} className="btn btn-sm btn-ghost" style={{ flexShrink: 0 }}><Icon name="copy" size={13}/></button>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
};

/* ---- Administration (toi) ---- */
const AppAdminPanel = ({ data }) => {
  const [accounts, setAccounts] = useLocalJSON("cb_admin_accounts", [
    { id: "a1", name: "Ongles by Léa", email: "lea@…", role: "ambassadrice" },
    { id: "a2", name: "Sarah Coiffure", email: "sarah@…", role: "pro" },
    { id: "a3", name: "Studio Manon", email: "manon@…", role: "pro" },
    { id: "a4", name: "Camille (marketing)", email: "camille@…", role: "cofondatrice" },
  ]);
  const setRole = (id, role) => {
    setAccounts(accs => accs.map(a => a.id === id ? { ...a, role } : a));
    showToast("Profil mis à jour (démo)");
  };
  const ROLE_OPTS = [["pro", "Professionnel"], ["ambassadrice", "Ambassadrice"], ["cofondatrice", "Co-fondatrice"], ["admin", "Administrateur"]];
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
      <RoleHeader icon="shield" title="Administration" sub="Gérez les comptes et attribuez les profils."/>

      <div style={{ display: "flex", gap: 12, flexWrap: "wrap" }}>
        <RoleStat value="128" label="Comptes" sub="tous profils"/>
        <RoleStat value="1 840 €" label="MRR" accent sub="revenu mensuel récurrent"/>
        <RoleStat value="2,1 %" label="Churn" sub="ce mois"/>
      </div>

      <div style={{ ..._roleCard }}>
        <div style={{ fontWeight: 600, fontSize: 14.5, marginBottom: 4 }}>Attribuer les profils</div>
        <div style={{ fontSize: 12.5, color: "var(--ink-4)", marginBottom: 12 }}>
          Choisissez le profil de chaque compte. (Démo locale — se branchera sur le backend des comptes.)
        </div>
        <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
          {accounts.map(a => (
            <div key={a.id} style={{ display: "flex", alignItems: "center", gap: 12, padding: "10px 13px", background: "var(--bg-alt)", borderRadius: 10, flexWrap: "wrap" }}>
              <div style={{ flex: 1, minWidth: 140 }}>
                <div style={{ fontSize: 13.5, fontWeight: 600 }}>{a.name}</div>
                <div style={{ fontSize: 11.5, color: "var(--ink-4)" }}>{a.email}</div>
              </div>
              <select value={a.role} onChange={e => setRole(a.id, e.target.value)} style={{
                padding: "8px 10px", background: "var(--surface)", border: "1px solid var(--line)",
                borderRadius: 8, fontSize: 13, fontFamily: "inherit", color: "var(--ink)", cursor: "pointer",
              }}>
                {ROLE_OPTS.map(([v, l]) => <option key={v} value={v}>{l}</option>)}
              </select>
            </div>
          ))}
        </div>
      </div>

      <div style={{ ..._roleCard, display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12, flexWrap: "wrap" }}>
        <div>
          <div style={{ fontWeight: 600, fontSize: 14.5 }}>Réglages de la plateforme</div>
          <div style={{ fontSize: 12.5, color: "var(--ink-4)", marginTop: 2 }}>Formules, quotas SMS/email, prix.</div>
        </div>
        <button onClick={_comingSoon("Réglages plateforme", "Le panneau de réglages global (formules, quotas, prix) arrive bientôt.")} className="btn btn-sm btn-ghost">Ouvrir</button>
      </div>

      {/* SEO & référencement — vue d'ensemble + raccourcis Search Console */}
      <div style={{ ..._roleCard }}>
        <div style={{ display: "flex", alignItems: "center", gap: 12, marginBottom: 14 }}>
          <span style={{
            width: 38, height: 38, borderRadius: 10, flexShrink: 0,
            background: "linear-gradient(135deg, var(--accent), var(--accent-ink))",
            color: "#fff", display: "flex", alignItems: "center", justifyContent: "center",
          }}>🔍</span>
          <div>
            <div style={{ fontWeight: 620, fontSize: 14.5 }}>SEO & référencement</div>
            <div style={{ fontSize: 12, color: "var(--ink-4)" }}>Vue d'ensemble + raccourcis Google Search Console</div>
          </div>
        </div>

        <div style={{ display: "flex", gap: 12, flexWrap: "wrap", marginBottom: 14 }}>
          <RoleStat value="39" label="Pages dans le sitemap"  sub="lastmod 2026-05-27"/>
          <RoleStat value="32" label="Pages indexées Google"   sub="~82 % couvert" accent/>
          <RoleStat value="14,5" label="Position moyenne"      sub="48 dernières heures"/>
        </div>

        <div style={{ fontSize: 11.5, textTransform: "uppercase", letterSpacing: "0.08em", color: "var(--ink-4)", fontWeight: 680, margin: "4px 0 8px" }}>
          Pages prioritaires à indexer
        </div>
        <div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
          {[
            "alternative-planity-prix", "lyon", "metiers",
            "guides/quitter-planity-sans-perdre-clients",
            "guides/auto-entrepreneur-beaute-checklist",
          ].map(slug => {
            const fullUrl = "https://clientbase.fr/" + slug;
            const gscUrl = "https://search.google.com/search-console/inspect?resource_id=https://clientbase.fr/&id=" + encodeURIComponent(fullUrl);
            return (
              <div key={slug} style={{ display: "flex", alignItems: "center", gap: 10, padding: "9px 12px", background: "var(--bg-alt)", borderRadius: 10 }}>
                <span style={{ flex: 1, fontFamily: "var(--ff-mono)", fontSize: 12, color: "var(--ink-2)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>/{slug}</span>
                <a href={gscUrl} target="_blank" rel="noopener" className="btn btn-sm btn-ghost" style={{ flexShrink: 0, textDecoration: "none" }}>
                  <Icon name="external" size={12}/> Indexer
                </a>
              </div>
            );
          })}
        </div>

        <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(180px, 1fr))", gap: 8, marginTop: 14 }}>
          <a href="https://search.google.com/search-console?resource_id=https://clientbase.fr/" target="_blank" rel="noopener" className="btn btn-sm btn-ghost" style={{ textDecoration: "none", justifyContent: "center" }}>
            <Icon name="chart" size={13}/> Search Console
          </a>
          <a href="https://search.google.com/test/rich-results?url=https%3A%2F%2Fclientbase.fr%2Frendez-vous-barbier" target="_blank" rel="noopener" className="btn btn-sm btn-ghost" style={{ textDecoration: "none", justifyContent: "center" }}>
            <Icon name="check" size={13}/> Test Rich Results
          </a>
          <a href="https://pagespeed.web.dev/analysis?url=https%3A%2F%2Fclientbase.fr%2F" target="_blank" rel="noopener" className="btn btn-sm btn-ghost" style={{ textDecoration: "none", justifyContent: "center" }}>
            <Icon name="zap" size={13}/> PageSpeed
          </a>
        </div>

        <div style={{ marginTop: 10, fontSize: 11, color: "var(--ink-4)", lineHeight: 1.5 }}>
          💡 Les chiffres sont indicatifs (mis à jour manuellement). Pour des données live, on branchera l'API Google Search Console dans une prochaine étape.
        </div>
      </div>
    </div>
  );
};

/* ================================================================
   PROMOS, add, cycle status, delete
================================================================ */
const AppPromos = ({ data, actions, openModal }) => (
  <div>
    <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 16 }}>
      <h3 style={{ fontSize: 16 }}>Campagnes en cours</h3>
      <button className="btn btn-sm" style={{ background: "var(--ink)", color: "var(--bg)" }} onClick={() => openModal("promo")}>
        <Icon name="plus" size={14}/> Nouvelle promotion
      </button>
    </div>
    <div className="app-col-3" style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 16, marginBottom: 20 }}>
      {data.promos.map(p => {
        const pColor = {
          "Actif":     { bg: "var(--sage-soft)", c: "oklch(38% 0.08 160)" },
          "Programmé": { bg: "var(--accent-soft)", c: "var(--accent-ink)" },
          "Brouillon": { bg: "var(--bg-alt)", c: "var(--ink-3)" },
        }[p.status];
        return (
          <div key={p.id} style={{ padding: 20, background: "var(--surface)", border: "1px solid var(--line)", borderRadius: 12 }}>
            <div style={{ display: "flex", justifyContent: "space-between", alignItems: "start" }}>
              <Icon name="tag" size={20} style={{ color: "var(--ink-3)" }}/>
              <button onClick={() => actions.togglePromoStatus(p.id)}
                style={{ padding: "3px 8px", fontSize: 10.5, fontWeight: 600, background: pColor.bg, color: pColor.c, border: "none", borderRadius: 4, cursor: "pointer", textTransform: "uppercase", letterSpacing: "0.04em", fontFamily: "inherit" }}>
                {p.status}
              </button>
            </div>
            <h4 style={{ marginTop: 14, fontSize: 15 }}>{p.title}</h4>
            <div style={{ marginTop: 4, fontSize: 12.5, color: "var(--ink-3)" }}>{p.segment}</div>
            <div style={{ marginTop: 14, padding: "8px 10px", background: "var(--bg-alt)", borderRadius: 8, fontSize: 12, color: "var(--ink-2)", fontFamily: "inherit" }}>{p.detail}</div>
            <button onClick={() => actions.deletePromo(p.id)} className="btn btn-sm btn-ghost" style={{ marginTop: 10, width: "100%", height: 28, fontSize: 12, color: "oklch(55% 0.18 25)" }}>
              Supprimer
            </button>
          </div>
        );
      })}
    </div>
    <div style={{ background: "var(--surface)", border: "1px solid var(--line)", borderRadius: 12, padding: 24 }}>
      <h3 style={{ fontSize: 16 }}>Performances des 30 derniers jours</h3>
      <div className="app-kpi-4" style={{ marginTop: 16, display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 16 }}>
        {[["184", "SMS envoyés"], ["43", "Conversions"], ["23%", "Taux de conversion"], ["+612 €", "CA généré"]].map(([v, l]) => (
          <div key={l}>
            <div style={{ fontSize: 12, color: "var(--ink-4)", fontWeight: 500 }}>{l}</div>
            <div style={{ fontFamily: "var(--ff-display)", fontSize: 26, fontWeight: 580, letterSpacing: "-0.025em", marginTop: 4 }}>{v}</div>
          </div>
        ))}
      </div>
    </div>
  </div>
);

/* ================================================================
   STATS, desktop: full dashboard / mobile: one simple tabbed block
================================================================ */
const AppStats = (props) => {
  const isMobile = useIsMobile();
  return isMobile ? <AppStatsMobile {...props}/> : <AppStatsDesktop {...props}/>;
};

/* ====== Helpers stats (données réelles uniquement) ====== */
const _CB_FR_M_SHORT = ["Janv","Févr","Mars","Avril","Mai","Juin","Juil","Août","Sept","Oct","Nov","Déc"];
const _statsForData = (data) => {
  const todayOff = (window.cbTodayOffset && window.cbTodayOffset()) || 0;
  const today = new Date();
  const services = data.services || [];
  const appointments = data.appointments || [];
  const priceFor = (sid) => {
    const s = services.find(x => x.id === sid);
    return s ? Number(s.price) || 0 : 0;
  };

  // Reviens en arrière sur 6 mois (mois courant inclus)
  const months = [];
  for (let k = 5; k >= 0; k--) {
    const d = new Date(today.getFullYear(), today.getMonth() - k, 1);
    months.push({
      key: `${d.getFullYear()}-${d.getMonth()}`,
      label: _CB_FR_M_SHORT[d.getMonth()],
      year: d.getFullYear(),
      month: d.getMonth(),
      v: 0,        // CA réalisé (RDV done)
      count: 0,    // nb RDV done
    });
  }

  // Agrège les RDV done par mois
  for (const a of appointments) {
    if (!a.done) continue;
    const d = window.cbDateForOffset ? window.cbDateForOffset(a.day) : new Date();
    const k = `${d.getFullYear()}-${d.getMonth()}`;
    const m = months.find(x => x.key === k);
    if (m) { m.v += priceFor(a.serviceId); m.count += 1; }
  }

  const thisMonth = months[months.length - 1];
  const lastMonth = months[months.length - 2];
  const thisMonthCA = thisMonth ? thisMonth.v : 0;
  const lastMonthCA = lastMonth ? lastMonth.v : 0;
  const rdvDoneThisMonth = thisMonth ? thisMonth.count : 0;
  const panier = rdvDoneThisMonth > 0 ? thisMonthCA / rdvDoneThisMonth : 0;
  const deltaPct = lastMonthCA > 0
    ? Math.round(((thisMonthCA - lastMonthCA) / lastMonthCA) * 100)
    : (thisMonthCA > 0 ? 100 : 0);

  // Panier moyen mois précédent, pour comparer
  const lastPanier = lastMonth && lastMonth.count > 0 ? lastMonth.v / lastMonth.count : 0;
  const deltaPanier = panier - lastPanier;

  // Top prestations, compte RDV done par service (vrais chiffres)
  const byService = {};
  for (const a of appointments) {
    if (!a.done) continue;
    const s = services.find(x => x.id === a.serviceId);
    if (!s) continue;
    const e = byService[s.id] || (byService[s.id] = { name: s.name, count: 0, rev: 0 });
    e.count += 1;
    e.rev += Number(s.price) || 0;
  }
  const topPrestations = Object.values(byService)
    .sort((a, b) => b.rev - a.rev)
    .slice(0, 5);

  // No-show : RDV passés (day < today) marqués non-done
  let noShow = 0, eligible = 0;
  for (const a of appointments) {
    if (a.day >= todayOff) continue;
    eligible += 1;
    if (!a.done) noShow += 1;
  }
  const noShowPct = eligible > 0 ? Math.round((noShow / eligible) * 1000) / 10 : 0;

  // Le compte est-il vide ?
  const isEmpty = appointments.length === 0;

  return { months, thisMonthCA, lastMonthCA, deltaPct, rdvDoneThisMonth, panier, deltaPanier, topPrestations, noShowPct, isEmpty };
};

const StatsEmptyState = () => (
  <div style={{
    padding: "40px 24px", textAlign: "center",
    background: "var(--surface)", border: "1px solid var(--line)",
    borderRadius: 14,
  }}>
    <div style={{ fontSize: 36, marginBottom: 10 }}>📊</div>
    <div style={{ fontSize: 16, fontWeight: 580, color: "var(--ink)", marginBottom: 6 }}>
      Pas encore de statistiques
    </div>
    <div style={{ fontSize: 13.5, color: "var(--ink-3)", lineHeight: 1.5, maxWidth: 380, margin: "0 auto" }}>
      Vos chiffres apparaîtront ici dès que vous aurez des rendez-vous marqués
      comme « faits » dans l'agenda.
    </div>
  </div>
);

const AppStatsMobile = ({ data }) => {
  const stats = React.useMemo(() => _statsForData(data), [data]);
  if (stats.isEmpty) return <StatsEmptyState/>;

  const series = stats.months.map(m => ({ m: m.label, v: m.v }));
  const [active, setActive] = React.useState(series.length - 1);
  const current = series[active] || series[series.length - 1];
  const maxVal = Math.max(1, ...series.map(d => d.v));
  const prev = active > 0 ? series[active - 1].v : 0;
  const delta = current.v - prev;
  const deltaPct = prev > 0 ? Math.round((delta / prev) * 100) : (current.v > 0 ? 100 : 0);
  const total = series.reduce((s, d) => s + d.v, 0);

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
      {/* Hero CA, uniquement le mois courant, données réelles */}
      <div style={{
        padding: "22px 20px",
        background: "linear-gradient(135deg, var(--accent) 0%, oklch(45% 0.22 288) 100%)",
        borderRadius: 16, color: "white",
      }}>
        <div style={{ fontSize: 12, opacity: 0.85, textTransform: "uppercase", letterSpacing: "0.06em" }}>
          CA · {current.m}
        </div>
        <div style={{
          fontFamily: "var(--ff-display)", fontSize: "clamp(32px, 7vw, 44px)",
          fontWeight: 580, letterSpacing: "-0.03em", marginTop: 4,
          fontVariantNumeric: "tabular-nums",
        }}>{fmtEUR(current.v)}</div>
        {prev > 0 && (
          <div style={{
            marginTop: 10, display: "inline-flex", alignItems: "center", gap: 5,
            fontSize: 12.5, background: "rgba(255,255,255,0.18)",
            padding: "4px 10px", borderRadius: 999,
          }}>
            <Icon name="arrowUp" size={11} stroke={2.4}
              style={{ transform: delta < 0 ? "rotate(180deg)" : "none" }}/>
            {delta >= 0 ? "+" : ""}{deltaPct}% vs mois précédent
          </div>
        )}
      </div>

      {/* Interactive chart, 6 mois réels */}
      <div style={{ padding: 18, background: "var(--surface)", border: "1px solid var(--line)", borderRadius: 14 }}>
        <div style={{ fontSize: 12.5, color: "var(--ink-4)", fontWeight: 500, marginBottom: 14 }}>
          6 derniers mois, touchez une barre
        </div>
        <div style={{ display: "flex", alignItems: "flex-end", gap: 6, height: 140 }}>
          {series.map((d, i) => {
            const h = (d.v / maxVal) * 120;
            const isActive = active === i;
            return (
              <button key={i} onClick={() => setActive(i)}
                style={{
                  flex: 1, display: "flex", flexDirection: "column", alignItems: "center", gap: 6,
                  background: "transparent", border: "none", padding: 0, cursor: "pointer", fontFamily: "inherit",
                }}>
                <div style={{
                  width: "100%", height: Math.max(h, 2),
                  background: isActive
                    ? "linear-gradient(to top, var(--accent) 0%, oklch(65% 0.2 288) 100%)"
                    : "var(--accent-soft-2)",
                  borderRadius: 6, transition: "background .2s, height .25s cubic-bezier(0.22, 1, 0.36, 1)",
                }}/>
                <div style={{
                  fontSize: 10.5, fontWeight: isActive ? 600 : 500,
                  color: isActive ? "var(--accent-ink)" : "var(--ink-4)",
                }}>{d.m}</div>
              </button>
            );
          })}
        </div>
        <div style={{
          marginTop: 16, paddingTop: 14, borderTop: "1px solid var(--line)",
          display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12,
        }}>
          <MobileStat label="Panier moyen" value={fmtEUR(stats.panier)}/>
          <MobileStat label="RDV faits ce mois" value={stats.rdvDoneThisMonth}/>
          <MobileStat label="Total 6 mois" value={fmtEUR(total)}/>
          <MobileStat label="Taux no-show" value={stats.noShowPct + "%"}/>
        </div>
      </div>
    </div>
  );
};

const MobileStat = ({ label, value }) => (
  <div>
    <div style={{ fontSize: 11.5, color: "var(--ink-4)", fontWeight: 500 }}>{label}</div>
    <div style={{
      fontFamily: "var(--ff-display)", fontSize: 20, fontWeight: 580,
      letterSpacing: "-0.025em", marginTop: 2, fontVariantNumeric: "tabular-nums",
    }}>{value}</div>
  </div>
);

const AppStatsDesktop = ({ data }) => {
  const stats = React.useMemo(() => _statsForData(data), [data]);
  const [hoverMonth, setHoverMonth] = React.useState(null);
  const [hoverPresta, setHoverPresta] = React.useState(null);
  const [range, setRange] = React.useState("month");

  if (stats.isEmpty) return <StatsEmptyState/>;

  const monthlyData = stats.months.map(m => ({ m: m.label, v: m.v }));
  const maxMonth = Math.max(1, ...monthlyData.map(d => d.v));
  const topPrestations = stats.topPrestations.map(p => [p.name, p.rev, p.count]);
  const maxRev = Math.max(1, ...topPrestations.map(p => p[1]));

  // === Insights : observations actionnables tirées des vraies données ===
  const insights = (() => {
    const appts = (data.appointments || []).filter(a => a.done);
    if (appts.length === 0) return [];
    const out = [];
    // Jour le plus rentable
    const DAYS = ["Dim", "Lun", "Mar", "Mer", "Jeu", "Ven", "Sam"];
    const dayRev = [0, 0, 0, 0, 0, 0, 0];
    const dayCount = [0, 0, 0, 0, 0, 0, 0];
    appts.forEach(a => {
      const d = window.cbDateForOffset ? window.cbDateForOffset(a.day) : null;
      if (!d) return;
      const svc = (data.services || []).find(s => s.id === a.serviceId);
      const price = svc ? Number(svc.price) || 0 : 0;
      dayRev[d.getDay()] += price;
      dayCount[d.getDay()] += 1;
    });
    const bestDayIdx = dayRev.indexOf(Math.max(...dayRev));
    if (dayRev[bestDayIdx] > 0) {
      out.push({
        icon: "calendar", tone: "oklch(60% 0.17 30)",
        title: `${["Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi"][bestDayIdx]} est votre meilleur jour`,
        text: `${dayCount[bestDayIdx]} RDV faits · ${fmtEUR(dayRev[bestDayIdx])} de CA cumulé`,
      });
    }
    // Heure la plus chargée
    const hourCount = Array.from({ length: 24 }, () => 0);
    appts.forEach(a => { hourCount[Math.floor(a.h) || 0] += 1; });
    const bestHour = hourCount.indexOf(Math.max(...hourCount));
    if (hourCount[bestHour] > 0) {
      out.push({
        icon: "clock", tone: "oklch(58% 0.16 290)",
        title: `Vos RDV sont concentrés vers ${bestHour}h`,
        text: `${hourCount[bestHour]} créneau${hourCount[bestHour] > 1 ? "x" : ""} à cette heure, pensez à ouvrir d'autres plages.`,
      });
    }
    // Top prestation
    if (topPrestations.length > 0) {
      const [name, rev, count] = topPrestations[0];
      out.push({
        icon: "tag", tone: "var(--accent)",
        title: `${name} est votre prestation phare`,
        text: `${count} fois réalisée · ${fmtEUR(rev)} de CA total. Mettez-la en avant sur votre page.`,
      });
    }
    // No-show alerte
    if (stats.noShowPct >= 10) {
      out.push({
        icon: "bell", tone: "oklch(58% 0.18 30)",
        title: `Taux de no-show élevé (${stats.noShowPct}%)`,
        text: `Activez les acomptes ou envoyez un rappel la veille, vous limiterez les absences.`,
      });
    }
    return out.slice(0, 4);
  })();

  return (
    <div>
      {/* En-tête harmonisé + sélecteur de période */}
      <div style={{ marginBottom: 18, display: "flex", alignItems: "flex-start", justifyContent: "space-between", gap: 12, flexWrap: "wrap" }}>
        <div>
          <h3 style={{ fontSize: 22, fontFamily: "var(--ff-display)", fontWeight: 580, letterSpacing: "-0.022em" }}>Vos statistiques</h3>
          <p style={{ fontSize: 14, color: "var(--ink-3)", marginTop: 5, lineHeight: 1.5, maxWidth: 560 }}>
            Suivez votre activité, chiffres clés, tendances et ce qui fonctionne le mieux.
          </p>
        </div>
        <div style={{
          display: "inline-flex", padding: 3, gap: 2,
          background: "var(--bg-alt)", border: "1px solid var(--line)",
          borderRadius: 999,
        }}>
          {[["week","Semaine"],["month","Mois"],["3months","3 mois"],["year","Année"]].map(([k, l]) => {
            const active = range === k;
            return (
              <button key={k} onClick={() => setRange(k)} style={{
                padding: "6px 13px", fontSize: 12.5, fontWeight: active ? 600 : 540,
                background: active ? "var(--surface)" : "transparent",
                color: active ? "var(--ink)" : "var(--ink-3)",
                boxShadow: active ? "var(--sh-1)" : "none",
                border: "none", borderRadius: 999, cursor: "pointer", fontFamily: "inherit",
                transition: "background .15s, color .15s, box-shadow .15s",
              }}>{l}</button>
            );
          })}
        </div>
      </div>

      {/* Hero stat */}
      <div style={{
        padding: "24px 28px", marginBottom: 16,
        background: "linear-gradient(135deg, var(--accent) 0%, oklch(45% 0.22 288) 100%)",
        borderRadius: 16, color: "white",
        display: "flex", justifyContent: "space-between", alignItems: "center", gap: 16, flexWrap: "wrap",
      }}>
        <div>
          <div style={{ fontSize: 12, opacity: 0.85, textTransform: "uppercase", letterSpacing: "0.06em" }}>Chiffre d'affaires ce mois</div>
          <div style={{
            fontFamily: "var(--ff-display)", fontSize: "clamp(32px, 4vw, 44px)",
            fontWeight: 580, letterSpacing: "-0.03em", marginTop: 4,
          }}>{fmtEUR(stats.thisMonthCA)}</div>
          {stats.lastMonthCA > 0 && (
            <div style={{ marginTop: 8, display: "inline-flex", alignItems: "center", gap: 5, fontSize: 13, background: "rgba(255,255,255,0.18)", padding: "4px 10px", borderRadius: 999 }}>
              <Icon name="arrowUp" size={12} stroke={2.4} style={{ transform: stats.deltaPct < 0 ? "rotate(180deg)" : "none" }}/>
              {stats.deltaPct >= 0 ? "+" : ""}{stats.deltaPct}% vs mois dernier
            </div>
          )}
        </div>
        <div style={{
          display: "flex", gap: 4, alignItems: "flex-end", height: 60,
          opacity: 0.95, flexShrink: 0,
        }}>
          {monthlyData.map((d, i) => (
            <div key={i} style={{
              width: 12, height: `${(d.v / maxMonth) * 100}%`,
              background: i === 5 ? "rgba(255,255,255,0.95)" : "rgba(255,255,255,0.4)",
              borderRadius: 3,
            }}/>
          ))}
        </div>
      </div>

      {/* Secondary KPIs, données réelles */}
      <div className="app-kpi-4" style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 10, marginBottom: 18 }}>
        {[
          { l: "RDV faits ce mois", v: stats.rdvDoneThisMonth, d: null, icon: "calendar", tone: "sage" },
          { l: "Panier moyen",      v: fmtEUR(stats.panier),
            d: stats.deltaPanier !== 0 && Math.abs(stats.deltaPanier) >= 0.5
                ? `${stats.deltaPanier > 0 ? "+" : ""}${fmtEUR(stats.deltaPanier)}`
                : null,
            icon: "euro", tone: "accent" },
          { l: "Taux no-show",      v: stats.noShowPct + "%", d: null, icon: "clock", tone: "warn" },
        ].map((k, i) => {
          const tones = {
            sage:   { bg: "var(--sage-soft)",   c: "oklch(38% 0.08 160)" },
            accent: { bg: "var(--accent-soft)", c: "var(--accent-ink)" },
            warn:   { bg: "oklch(97% 0.025 30)",c: "oklch(42% 0.14 30)" },
          }[k.tone];
          return (
            <div key={i} style={{
              padding: 18, background: "var(--surface)", border: "1px solid var(--line)",
              borderRadius: 14, display: "flex", flexDirection: "column", gap: 10,
            }}>
              <div style={{
                width: 32, height: 32, borderRadius: 9,
                background: tones.bg, color: tones.c,
                display: "flex", alignItems: "center", justifyContent: "center",
              }}>
                <Icon name={k.icon} size={15}/>
              </div>
              <div>
                <div style={{ fontFamily: "var(--ff-display)", fontSize: 26, fontWeight: 580, letterSpacing: "-0.03em" }}>{k.v}</div>
                <div style={{ fontSize: 12.5, color: "var(--ink-3)", marginTop: 2 }}>{k.l}</div>
                {k.d && (
                  <div style={{ marginTop: 6, display: "inline-flex", alignItems: "center", gap: 4, fontSize: 11.5, color: "var(--sage)", background: "var(--sage-soft)", padding: "2px 8px", borderRadius: 999 }}>
                    <Icon name="arrowUp" size={10} stroke={2.4} style={{ transform: k.d.startsWith("-") ? "rotate(180deg)" : "none" }}/> {k.d}
                  </div>
                )}
              </div>
            </div>
          );
        })}
      </div>

      {/* Chart + top services */}
      <div className="app-split" style={{ display: "grid", gridTemplateColumns: "1.4fr 1fr", gap: 16 }}>
        <div style={{ padding: 22, background: "var(--surface)", border: "1px solid var(--line)", borderRadius: 14, position: "relative" }}>
          <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 16, gap: 8, flexWrap: "wrap" }}>
            <h3 style={{ fontSize: 15 }}>Chiffre d'affaires</h3>
            <div style={{ fontSize: 12, color: "var(--ink-4)", fontWeight: 500 }}>6 derniers mois</div>
          </div>

          {/* Tooltip */}
          {hoverMonth != null && (
            <div style={{
              position: "absolute", top: 56,
              left: `${((40 + hoverMonth * 76 + 24) / 500) * 100}%`,
              transform: "translateX(-50%)",
              background: "var(--ink)", color: "var(--bg)",
              padding: "6px 10px", borderRadius: 6,
              fontSize: 12, fontWeight: 500,
              pointerEvents: "none",
              whiteSpace: "nowrap",
              zIndex: 2,
            }}>
              {monthlyData[hoverMonth].m} · <strong>{fmtEUR(monthlyData[hoverMonth].v)}</strong>
            </div>
          )}

          <svg viewBox="0 0 500 220" style={{ width: "100%", height: 200 }}>
            <defs>
              <linearGradient id="bar" x1="0" x2="0" y1="0" y2="1">
                <stop offset="0%" stopColor="var(--accent)" stopOpacity="1"/>
                <stop offset="100%" stopColor="var(--accent)" stopOpacity="0.3"/>
              </linearGradient>
            </defs>
            {/* Gridlines basés sur le max réel */}
            {[0, 0.33, 0.66, 1].map((p) => {
              const y = 190 - p * 160;
              return <line key={p} x1="30" x2="490" y1={y} y2={y} stroke="var(--line)" strokeDasharray="2,4"/>;
            })}
            {monthlyData.map((d, i) => {
              const h = (d.v / maxMonth) * 160;
              const x = 40 + i * 76;
              const isLast = i === monthlyData.length - 1;
              const isHover = hoverMonth === i;
              return (
                <g key={i} onMouseEnter={() => setHoverMonth(i)} onMouseLeave={() => setHoverMonth(null)}
                   style={{ cursor: "pointer" }}>
                  {/* Invisible wider hit-area */}
                  <rect x={x - 14} y={30} width={76} height={170} fill="transparent"/>
                  <rect x={x} y={190 - h} width={48} height={h} rx={6}
                    fill={isLast ? "url(#bar)" : (isHover ? "var(--accent)" : "var(--accent-soft-2)")}
                    style={{ transition: "fill 0.15s" }}/>
                  <text x={x + 24} y={208} fontSize="11" fill={isHover ? "var(--accent-ink)" : "var(--ink-3)"} textAnchor="middle" fontWeight={isLast || isHover ? "600" : "500"}>
                    {d.m}
                  </text>
                </g>
              );
            })}
          </svg>
        </div>

        <div style={{ padding: 22, background: "var(--surface)", border: "1px solid var(--line)", borderRadius: 14 }}>
          <h3 style={{ fontSize: 15, marginBottom: 16 }}>Top prestations</h3>
          <div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
            {topPrestations.map(([name, rev, count], i) => {
              const isHover = hoverPresta === i;
              const pct = (rev / maxRev) * 100;
              return (
                <div key={name}
                  onMouseEnter={() => setHoverPresta(i)}
                  onMouseLeave={() => setHoverPresta(null)}
                  style={{
                    padding: "8px 10px",
                    background: isHover ? "var(--bg-alt)" : "transparent",
                    borderRadius: 8, cursor: "pointer",
                    transition: "background 0.15s",
                  }}>
                  <div style={{ display: "flex", justifyContent: "space-between", fontSize: 13, marginBottom: 5, gap: 8 }}>
                    <span style={{ color: "var(--ink)", fontWeight: 500, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
                      <span style={{ color: "var(--ink-4)", marginRight: 6, fontWeight: 500 }}>#{i + 1}</span>
                      {name}
                    </span>
                    <span style={{ fontFamily: "inherit", fontWeight: 600, flexShrink: 0, color: isHover ? "var(--accent-ink)" : "var(--ink)" }}>{fmtEUR(rev)}</span>
                  </div>
                  <div style={{ height: 6, background: "var(--bg-alt)", borderRadius: 10, overflow: "hidden" }}>
                    <div style={{
                      width: `${pct}%`, height: "100%",
                      background: (isHover || i === 0) ? "var(--accent)" : "var(--accent-soft-2)",
                      borderRadius: 10,
                      transition: "background 0.15s",
                    }}/>
                  </div>
                  {isHover && (
                    <div style={{ fontSize: 11.5, color: "var(--ink-4)", marginTop: 4 }}>
                      {count} prestation{count > 1 ? "s" : ""} · moy. {fmtEUR(rev / Math.max(count, 1))}
                    </div>
                  )}
                </div>
              );
            })}
          </div>
        </div>
      </div>

      {/* === Stats avancées (desktop only) === */}
      <div style={{ marginTop: 16, display: "grid", gridTemplateColumns: "1.4fr 1fr", gap: 16 }} className="app-split">
        <BookingsHeatmap data={data}/>
        <TopClientsCard data={data}/>
      </div>

      {/* === Insights actionnables, basés sur vos vraies données === */}
      {insights.length > 0 && (
        <div style={{
          marginTop: 16, padding: 22,
          background: "linear-gradient(135deg, var(--accent-soft) 0%, oklch(98% 0.015 280) 100%)",
          border: "1px solid var(--accent-soft-2)", borderRadius: 14,
        }}>
          <div style={{ display: "flex", alignItems: "center", gap: 9, marginBottom: 12 }}>
            <span style={{
              width: 28, height: 28, borderRadius: 9,
              background: "var(--accent)", color: "#fff",
              display: "inline-flex", alignItems: "center", justifyContent: "center",
            }}>
              <Icon name="sparkle" size={14}/>
            </span>
            <h3 style={{ fontSize: 16, margin: 0, fontFamily: "var(--ff-display)", fontWeight: 580, letterSpacing: "-0.012em" }}>
              Ce qu'on a remarqué
            </h3>
          </div>
          <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(260px, 1fr))", gap: 10 }}>
            {insights.map((ins, i) => (
              <div key={i} style={{
                padding: "12px 14px", background: "var(--surface)",
                border: "1px solid var(--line)", borderRadius: 11,
                display: "flex", gap: 10, alignItems: "flex-start",
              }}>
                <span style={{
                  width: 32, height: 32, borderRadius: 9, flexShrink: 0,
                  background: `color-mix(in oklab, ${ins.tone} 14%, transparent)`,
                  color: ins.tone,
                  display: "inline-flex", alignItems: "center", justifyContent: "center",
                }}>
                  <Icon name={ins.icon} size={14}/>
                </span>
                <div style={{ minWidth: 0 }}>
                  <div style={{ fontSize: 13.5, fontWeight: 580, color: "var(--ink)", lineHeight: 1.35 }}>
                    {ins.title}
                  </div>
                  <div style={{ fontSize: 12.5, color: "var(--ink-3)", marginTop: 3, lineHeight: 1.45 }}>
                    {ins.text}
                  </div>
                </div>
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  );
};

/* ======= Heatmap des réservations (jour × heure) ======= */
const BookingsHeatmap = ({ data }) => {
  const DAYS = ["Lun", "Mar", "Mer", "Jeu", "Ven", "Sam", "Dim"];
  const HOURS = Array.from({ length: 11 }, (_, i) => 9 + i); // 9h → 19h
  const grid = React.useMemo(() => {
    const g = DAYS.map(() => HOURS.map(() => 0));
    const appts = data.appointments || [];
    appts.forEach(a => {
      const d = window.cbDateForOffset ? window.cbDateForOffset(a.day) : null;
      if (!d) return;
      const dayIdx = (d.getDay() + 6) % 7; // lundi=0
      const hourIdx = Math.floor(a.h) - 9;
      if (hourIdx < 0 || hourIdx >= HOURS.length) return;
      g[dayIdx][hourIdx] += 1;
    });
    return g;
  }, [data.appointments]);

  const max = Math.max(1, ...grid.flat());

  return (
    <div style={{
      padding: 22, background: "var(--surface)", border: "1px solid var(--line)",
      borderRadius: 14,
    }}>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", marginBottom: 14 }}>
        <h3 style={{ fontSize: 15, margin: 0 }}>Heures les plus demandées</h3>
        <div style={{ fontSize: 11.5, color: "var(--ink-4)" }}>tous RDV confondus</div>
      </div>
      <div style={{
        display: "grid",
        gridTemplateColumns: `36px repeat(${HOURS.length}, 1fr)`,
        gap: 3, alignItems: "center",
      }}>
        {/* Header heures */}
        <div></div>
        {HOURS.map(h => (
          <div key={h} style={{
            textAlign: "center", fontSize: 9.5, color: "var(--ink-4)",
            fontFamily: "var(--ff-text)", fontWeight: 540,
          }}>{h}h</div>
        ))}
        {/* Lignes */}
        {DAYS.map((day, di) => (
          <React.Fragment key={day}>
            <div style={{
              fontSize: 11, color: "var(--ink-3)", fontWeight: 540,
              fontFamily: "var(--ff-text)", textTransform: "uppercase", letterSpacing: "0.04em",
            }}>{day}</div>
            {HOURS.map((_, hi) => {
              const v = grid[di][hi];
              const intensity = v === 0 ? 0 : 0.15 + (v / max) * 0.85;
              return (
                <div key={hi} title={`${day} ${HOURS[hi]}h : ${v} RDV`}
                  style={{
                    aspectRatio: "1 / 1",
                    background: v === 0
                      ? "var(--bg-alt)"
                      : `color-mix(in oklab, var(--accent) ${Math.round(intensity * 100)}%, transparent)`,
                    borderRadius: 4,
                    cursor: v > 0 ? "default" : "default",
                    transition: "transform .15s ease",
                  }}
                  onMouseEnter={e => v > 0 && (e.currentTarget.style.transform = "scale(1.15)")}
                  onMouseLeave={e => (e.currentTarget.style.transform = "scale(1)")}/>
              );
            })}
          </React.Fragment>
        ))}
      </div>
      <div style={{
        display: "flex", justifyContent: "flex-end", alignItems: "center",
        gap: 6, marginTop: 12, fontSize: 10.5, color: "var(--ink-4)",
        fontFamily: "var(--ff-text)",
      }}>
        <span>Moins</span>
        {[0.15, 0.35, 0.55, 0.75, 1].map((p, i) => (
          <div key={i} style={{
            width: 12, height: 12, borderRadius: 3,
            background: `color-mix(in oklab, var(--accent) ${Math.round(p * 100)}%, transparent)`,
          }}/>
        ))}
        <span>Plus</span>
      </div>
    </div>
  );
};

/* ======= Top clientes par CA ======= */
const TopClientsCard = ({ data }) => {
  const top = React.useMemo(() => {
    const services = data.services || [];
    const priceFor = id => {
      const s = services.find(x => x.id === id);
      return s ? Number(s.price) || 0 : 0;
    };
    const byClient = {};
    (data.appointments || []).forEach(a => {
      if (!a.done || !a.clientId) return;
      const c = byClient[a.clientId] || (byClient[a.clientId] = { id: a.clientId, ca: 0, count: 0 });
      c.ca += priceFor(a.serviceId);
      c.count += 1;
    });
    return Object.values(byClient)
      .map(c => ({
        ...c,
        client: (data.clients || []).find(x => x.id === c.id),
      }))
      .filter(c => !!c.client)
      .sort((a, b) => b.ca - a.ca)
      .slice(0, 5);
  }, [data.appointments, data.clients, data.services]);

  return (
    <div style={{
      padding: 22, background: "var(--surface)", border: "1px solid var(--line)",
      borderRadius: 14,
    }}>
      <h3 style={{ fontSize: 15, marginBottom: 14 }}>Top 5 clientes · CA</h3>
      {top.length === 0 ? (
        <div style={{ fontSize: 13, color: "var(--ink-4)", textAlign: "center", padding: "30px 10px" }}>
          Pas encore assez de RDV faits.
        </div>
      ) : (
        <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
          {top.map((c, i) => (
            <div key={c.id} style={{
              display: "flex", alignItems: "center", gap: 11,
              padding: "6px 0", borderBottom: i < top.length - 1 ? "1px solid var(--line)" : "none",
            }}>
              <span style={{
                fontFamily: "var(--ff-text)", fontSize: 12,
                color: "var(--ink-4)", fontWeight: 540, flexShrink: 0, width: 18,
              }}>#{i + 1}</span>
              <Avatar client={c.client} size={32}/>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{
                  fontSize: 13, fontWeight: 540, color: "var(--ink)",
                  overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
                }}>{c.client.name}</div>
                <div style={{ fontSize: 11.5, color: "var(--ink-4)" }}>
                  {c.count} RDV · moy. {fmtEUR(c.ca / c.count)}
                </div>
              </div>
              <div style={{
                fontFamily: "var(--ff-display)", fontSize: 15, fontWeight: 580,
                color: "var(--accent-ink)", flexShrink: 0,
              }}>{fmtEUR(c.ca)}</div>
            </div>
          ))}
        </div>
      )}
    </div>
  );
};

/* ======= Comparaison période sur période ======= */

/* ================================================================
   FACTURES, toggle paid, add, delete, French-compliant detail view
================================================================ */

// Full French-compliant invoice detail (printable)
const InvoiceDetailModal = ({ invoice, data, onClose }) => {
  if (!invoice) return null;
  const b = data.business || {};
  const c = findClient(data, invoice.clientId);
  const isCredit = (invoice.amount < 0) || !!invoice.creditOf;
  // TVA, utilise le taux figé sur la facture si présent (snapshot à l'émission),
  // sinon retombe sur le statut courant du business.
  const vatRate = invoice.vatRate != null && invoice.vatRate > 0
    ? Number(invoice.vatRate)
    : (b.vatStatus === "applicable" ? Number(b.vatRate || 0) : 0);
  const vatActive = vatRate > 0;
  const amountTTC = invoice.amount;
  const amountHT = vatActive ? +(amountTTC / (1 + vatRate / 100)).toFixed(2) : amountTTC;
  const vatAmount = vatActive ? +(amountTTC - amountHT).toFixed(2) : 0;
  const dueLabel = invoice.dueDate || (b.paymentTermsDays > 0 ? `Sous ${b.paymentTermsDays} jours` : "À réception");

  const missingFields = [];
  if (!b.address) missingFields.push("adresse");
  if (!b.siret)   missingFields.push("SIRET");

  const handlePrint = () => window.print();

  return (
    <Modal open={!!invoice} onClose={onClose} wide
      title={`Facture ${invoice.number || invoice.id}`}
      footer={
        <>
          <button className="btn btn-ghost" onClick={onClose}>Fermer</button>
          <button className="btn btn-primary" onClick={handlePrint}>
            <Icon name="invoice" size={14}/> Imprimer / PDF
          </button>
        </>
      }>
      {missingFields.length > 0 && (
        <div style={{
          marginBottom: 16, padding: "10px 14px",
          background: "var(--warn-soft)", border: "1px solid var(--warn-soft-2)",
          borderRadius: 8, fontSize: 13, color: "var(--warn-ink)", lineHeight: 1.5,
        }}>
          <strong>Champs manquants pour conformité&nbsp;:</strong> {missingFields.join(", ")}. Ajoutez-les dans <em>Paramètres → Informations légales</em>.
        </div>
      )}

      {/* Printable invoice body */}
      <div id="cb-invoice-print" style={{
        padding: "28px 32px", background: "white",
        border: "1px solid var(--line)", borderRadius: 10,
        fontFamily: "var(--ff-text)", color: "#111",
        lineHeight: 1.5, fontSize: 13.5,
      }}>
        {/* Header: emitter + invoice meta */}
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 24, marginBottom: 24 }}>
          <div>
            <div style={{ fontFamily: "var(--ff-display)", fontSize: 18, fontWeight: 600, letterSpacing: "-0.02em" }}>
              {b.name || "Votre activité"}
            </div>
            <div style={{ fontSize: 12.5, marginTop: 6, color: "#555", whiteSpace: "pre-line" }}>
              {b.owner}{b.legalForm ? ` · ${b.legalForm}` : ""}{"\n"}
              {b.address || ", adresse à renseigner —"}{"\n"}
              {[b.postalCode, b.city].filter(Boolean).join(" ")}{"\n"}
              {b.phone ? `Tél : ${b.phone}` : ""}{b.contactEmail ? `${b.phone ? " · " : ""}${b.contactEmail}` : ""}
            </div>
            <div style={{ fontSize: 11.5, marginTop: 8, color: "#777", fontFamily: "inherit" }}>
              SIRET&nbsp;: {b.siret || ", à renseigner —"}{b.vatNumber ? ` · TVA : ${b.vatNumber}` : ""}
              {b.rcsRm ? <><br/>{b.rcsRm}</> : null}
            </div>
          </div>
          <div style={{ textAlign: "right" }}>
            <div style={{ fontFamily: "var(--ff-display)", fontSize: 28, fontWeight: 700, letterSpacing: "-0.025em", textTransform: "uppercase" }}>
              {isCredit ? "Avoir" : "Facture"}
            </div>
            <div style={{ fontSize: 13, marginTop: 8, fontFamily: "inherit" }}>
              N° {invoice.number || invoice.id}
            </div>
            {invoice.creditOf && (
              <div style={{ fontSize: 12, color: "#777", marginTop: 2, fontFamily: "inherit" }}>
                Annule la facture {invoice.creditOf}
              </div>
            )}
            <div style={{ fontSize: 12.5, color: "#555", marginTop: 4 }}>
              Date d'émission&nbsp;: {invoice.date}
            </div>
            <div style={{ fontSize: 12.5, color: "#555" }}>
              Date de prestation&nbsp;: {invoice.prestationDate || invoice.date}
            </div>
            <div style={{ fontSize: 12.5, color: "#555" }}>
              Date d'échéance&nbsp;: {dueLabel}
            </div>
          </div>
        </div>

        {/* Recipient */}
        <div style={{ padding: "12px 14px", background: "#f7f7f9", borderRadius: 8, marginBottom: 20, fontSize: 12.5 }}>
          <div style={{ fontSize: 11, color: "#777", textTransform: "uppercase", letterSpacing: "0.06em", marginBottom: 4 }}>
            Facturé à
          </div>
          <div style={{ fontWeight: 600 }}>{c ? c.name : "—"}</div>
          {c && c.email && <div style={{ color: "#555" }}>{c.email}</div>}
          {c && c.phone && <div style={{ color: "#555" }}>{c.phone}</div>}
        </div>

        {/* Line items */}
        <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 12.5, marginBottom: 16 }}>
          <thead>
            <tr style={{ borderBottom: "2px solid #333", textAlign: "left" }}>
              <th style={{ padding: "8px 4px", fontWeight: 600 }}>Désignation</th>
              <th style={{ padding: "8px 4px", fontWeight: 600, textAlign: "center" }}>Qté</th>
              <th style={{ padding: "8px 4px", fontWeight: 600, textAlign: "right" }}>Prix unit. HT</th>
              {vatActive && <th style={{ padding: "8px 4px", fontWeight: 600, textAlign: "right" }}>TVA</th>}
              <th style={{ padding: "8px 4px", fontWeight: 600, textAlign: "right" }}>Total HT</th>
            </tr>
          </thead>
          <tbody>
            <tr style={{ borderBottom: "1px solid #ddd" }}>
              <td style={{ padding: "10px 4px" }}>
                {invoice.designation || "Prestation de service"}
                <div style={{ fontSize: 11.5, color: "#777", marginTop: 2 }}>
                  {invoice.prestationDate ? `Réalisée le ${invoice.prestationDate}` : ""}
                </div>
              </td>
              <td style={{ padding: "10px 4px", textAlign: "center", fontFamily: "inherit" }}>1</td>
              <td style={{ padding: "10px 4px", textAlign: "right", fontFamily: "inherit" }}>{amountHT.toFixed(2)} €</td>
              {vatActive && <td style={{ padding: "10px 4px", textAlign: "right", fontFamily: "inherit" }}>{vatRate}%</td>}
              <td style={{ padding: "10px 4px", textAlign: "right", fontFamily: "inherit", fontWeight: 600 }}>{amountHT.toFixed(2)} €</td>
            </tr>
          </tbody>
        </table>

        {/* Totals */}
        <div style={{ display: "flex", justifyContent: "flex-end", marginBottom: 24 }}>
          <div style={{ minWidth: 260, fontSize: 12.5 }}>
            <div style={{ display: "flex", justifyContent: "space-between", padding: "4px 0" }}>
              <span style={{ color: "#555" }}>Total HT</span>
              <span style={{ fontFamily: "inherit" }}>{amountHT.toFixed(2)} €</span>
            </div>
            {vatActive ? (
              <div style={{ display: "flex", justifyContent: "space-between", padding: "4px 0" }}>
                <span style={{ color: "#555" }}>TVA ({vatRate}%)</span>
                <span style={{ fontFamily: "inherit" }}>{vatAmount.toFixed(2)} €</span>
              </div>
            ) : (
              <div style={{ fontSize: 11, color: "#777", padding: "4px 0", fontStyle: "italic" }}>
                TVA non applicable, art. 293 B du CGI
              </div>
            )}
            <div style={{ display: "flex", justifyContent: "space-between", padding: "10px 0", borderTop: "2px solid #333", marginTop: 4, fontSize: 15, fontWeight: 700 }}>
              <span>Total TTC</span>
              <span style={{ fontFamily: "inherit" }}>{amountTTC.toFixed(2)} €</span>
            </div>
          </div>
        </div>

        {/* Payment terms + legal mentions */}
        <div style={{ paddingTop: 16, borderTop: "1px solid #ddd", fontSize: 11.5, color: "#555", lineHeight: 1.6 }}>
          <div style={{ fontWeight: 600, color: "#333", marginBottom: 4 }}>Modalités de paiement</div>
          <div>Échéance&nbsp;: {dueLabel}. Moyens acceptés&nbsp;: {b.paymentMethods || "—"}.</div>
          {b.iban && <div style={{ fontFamily: "inherit", fontSize: 11 }}>IBAN&nbsp;: {b.iban}</div>}
          <div style={{ marginTop: 4, fontSize: 11, color: "#777" }}>
            Aucun escompte n'est accordé en cas de paiement anticipé.
          </div>

          <div style={{ marginTop: 12, fontSize: 11, color: "#777" }}>
            En cas de retard de paiement, des pénalités sont applicables au taux de la Banque centrale européenne + 10 points.
            Indemnité forfaitaire pour frais de recouvrement&nbsp;: 40 € (art. L441-10 du Code de commerce).
          </div>

          {(b.mediatorName || b.mediatorUrl) && (
            <div style={{ marginTop: 10, fontSize: 11, color: "#777" }}>
              Médiateur de la consommation&nbsp;: {b.mediatorName || "—"}{b.mediatorUrl ? ` · ${b.mediatorUrl}` : ""} (art. L612-1 du Code de la consommation).
            </div>
          )}

          {invoice.paid && !isCredit && (
            <div style={{
              marginTop: 14, display: "inline-block",
              padding: "4px 12px", background: "oklch(88% 0.08 160)",
              color: "oklch(30% 0.1 160)", borderRadius: 999,
              fontSize: 12, fontWeight: 600,
            }}>✓ PAYÉE</div>
          )}
        </div>
      </div>
    </Modal>
  );
};

const InvoiceCardMobile = ({ f, data, actions, onView }) => {
  const c = findClient(data, f.clientId);
  return (
    <div style={{
      background: "var(--surface)", border: "1px solid var(--line)",
      borderRadius: 12, padding: 14, display: "flex", flexDirection: "column", gap: 10,
    }}>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", gap: 8 }}>
        <div style={{ fontFamily: "inherit", fontSize: 11.5, color: "var(--ink-4)", letterSpacing: "0.04em" }}>
          {f.number || f.id}
        </div>
        <div style={{ fontFamily: "var(--ff-display)", fontSize: 20, fontWeight: 580, letterSpacing: "-0.02em" }}>
          {fmtEUR(f.amount)}
        </div>
      </div>
      <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
        {c && <Avatar client={c} size={32}/>}
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontWeight: 520, fontSize: 14, color: "var(--ink)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
            {c ? c.name : "—"}
          </div>
          <div style={{ fontSize: 12, color: "var(--ink-4)", marginTop: 1 }}>{f.date}</div>
        </div>
      </div>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: 8, paddingTop: 10, borderTop: "1px solid var(--line)" }}>
        <button onClick={() => actions.toggleInvoicePaid(f.id)} style={{
          padding: "5px 10px", fontSize: 11.5, fontWeight: 600, borderRadius: 6,
          background: f.paid ? "var(--sage-soft)" : "var(--warn-soft)",
          color: f.paid ? "oklch(38% 0.08 160)" : "var(--warn-ink)",
          border: "none", cursor: "pointer", fontFamily: "inherit",
          textTransform: "uppercase", letterSpacing: "0.04em",
        }}>{f.paid ? "Payée" : "En attente"}</button>
        <div style={{ display: "flex", gap: 6 }}>
          <button onClick={onView} className="btn btn-sm btn-ghost" style={{ height: 32, fontSize: 12.5 }}>Voir</button>
          {!(f.creditOf || f.amount < 0) && (
            <button onClick={() => actions.deleteInvoice(f.id)} aria-label="Annuler par avoir" title="Annuler par avoir (la facture ne peut pas être supprimée)" className="btn btn-sm btn-ghost" style={{ height: 32, width: 32, padding: 0, color: "oklch(55% 0.18 25)" }}>
              <Icon name="close" size={14}/>
            </button>
          )}
        </div>
      </div>
    </div>
  );
};

const AppFactures = ({ data, actions, openModal, setMod }) => {
  const isMobile = useIsMobile();
  const [viewId, setViewId] = React.useState(null);
  const b = data.business || {};
  const legalReady = b.address && b.siret;
  const viewInvoice = viewId ? data.invoices.find(i => i.id === viewId) : null;
  // Overlay « bêta » dismissable : prévient que les factures ne sont pas
  // garanties conformes pendant la bêta. Un clic « J'ai compris » mémorise
  // la dismissal en localStorage.
  const [betaDismissed, setBetaDismissed] = React.useState(() => {
    try { return localStorage.getItem("cb_factu_beta_ack") === "1"; } catch { return false; }
  });
  const dismissBeta = () => {
    setBetaDismissed(true);
    try { localStorage.setItem("cb_factu_beta_ack", "1"); } catch {}
  };

  return (
    <div style={{ position: "relative" }}>
      {/* Overlay bêta dismissable */}
      {!betaDismissed && (
        <div style={{
          position: "absolute", inset: 0, zIndex: 10,
          display: "flex", alignItems: "flex-start", justifyContent: "center",
          paddingTop: "12vh",
          pointerEvents: "none",
        }}>
          <div style={{
            pointerEvents: "auto",
            padding: "26px 32px",
            background: "var(--surface)",
            border: "1px solid var(--accent-soft-2)",
            borderRadius: 20,
            boxShadow: "0 24px 60px -20px rgba(15,18,30,0.3)",
            textAlign: "center", maxWidth: 460,
          }}>
            <div style={{
              width: 56, height: 56, borderRadius: 16, margin: "0 auto 14px",
              background: "linear-gradient(135deg, oklch(70% 0.14 60) 0%, oklch(58% 0.16 50) 100%)",
              color: "#fff", display: "inline-flex", alignItems: "center", justifyContent: "center",
              boxShadow: "0 14px 30px -10px oklch(60% 0.15 55)",
              fontSize: 26,
            }}>⚠️</div>
            <h3 style={{
              margin: 0, fontFamily: "var(--ff-display)", fontSize: 20, fontWeight: 600,
              letterSpacing: "-0.022em", color: "var(--ink)",
            }}>
              Factures en version bêta
            </h3>
            <p style={{
              margin: "8px 0 0", fontSize: 13.5, color: "var(--ink-3)", lineHeight: 1.55,
            }}>
              Vous pouvez émettre des factures dès maintenant, mais pendant la bêta
              nous ne garantissons pas leur conformité légale française à 100&nbsp;%.
              Vérifiez vos mentions légales (SIRET, adresse, TVA) avant de
              transmettre une facture à une cliente.
            </p>
            <button onClick={dismissBeta} className="btn btn-primary btn-sm cb-press-feedback"
              style={{ marginTop: 16 }}>
              J'ai compris, continuer
            </button>
          </div>
        </div>
      )}

      {/* Contenu de la page, flouté tant que l'overlay est visible */}
      <div style={!betaDismissed ? { filter: "blur(4px)", pointerEvents: "none", userSelect: "none", opacity: 0.85 } : {}}>
      {/* En-tête harmonisé (Factures / Acomptes / Stock) */}
      <div style={{ marginBottom: 18, display: "flex", alignItems: "flex-start", justifyContent: "space-between", gap: 12, flexWrap: "wrap" }}>
        <div>
          <h3 style={{ fontSize: 22, fontFamily: "var(--ff-display)", fontWeight: 580, letterSpacing: "-0.022em" }}>Vos factures</h3>
          <p style={{ fontSize: 14, color: "var(--ink-3)", marginTop: 5, lineHeight: 1.5, maxWidth: 560 }}>
            Émettez et suivez vos factures conformes, recherchez, marquez comme payées, annulez par avoir.
          </p>
        </div>
        <div style={{ display: "flex", gap: 8, flexShrink: 0 }}>
          <button className="btn btn-ghost btn-sm cb-press-feedback"
            onClick={() => setMod && setMod("settings")}
            title="Réglages factures (mentions légales, TVA…)">
            <Icon name="settings" size={13}/>{isMobile ? "" : " Paramètres"}
          </button>
          <button className="btn btn-primary btn-sm cb-press-feedback" onClick={() => openModal("invoice")}>
            <Icon name="plus" size={13}/>{isMobile ? "" : " Nouvelle facture"}
          </button>
        </div>
      </div>

      {!legalReady && (
        <div style={{
          padding: "12px 14px", marginBottom: 14,
          background: "var(--warn-soft)", border: "1px solid var(--warn-soft-2)",
          borderRadius: 10, fontSize: 13, color: "var(--warn-ink)", lineHeight: 1.5,
          display: "flex", gap: 10, alignItems: "center", flexWrap: "wrap",
        }}>
          <Icon name="shield" size={15} style={{ flexShrink: 0 }}/>
          <div style={{ flex: 1, minWidth: 200 }}>
            <strong>Factures non conformes&nbsp;:</strong> ajoutez votre adresse et SIRET dans les paramètres pour qu'elles respectent la loi française.
          </div>
        </div>
      )}
      {/* Bandeau bêta : on prévient que les factures ne sont pas certifiées
          100% conformes pendant la phase bêta (audit légal en cours). */}
      <div style={{
        padding: "12px 14px", marginBottom: 14,
        background: "var(--accent-soft)", border: "1px solid var(--accent-soft-2)",
        borderRadius: 10, fontSize: 13, color: "var(--accent-ink)", lineHeight: 1.5,
        display: "flex", gap: 10, alignItems: "center", flexWrap: "wrap",
      }}>
        <Icon name="sparkle" size={15} style={{ flexShrink: 0 }}/>
        <div style={{ flex: 1, minWidth: 200 }}>
          <strong>Pendant la bêta&nbsp;:</strong> nous ne pouvons pas garantir que les factures soient 100&nbsp;% conformes (audit légal en cours). Vérifiez avec votre comptable pour les déclarations officielles.
        </div>
      </div>
      {data.invoices.length === 0 ? (
        <div style={{
          padding: "40px 20px", textAlign: "center",
          background: "var(--bg-alt)", border: "1px dashed var(--line-strong)",
          borderRadius: 12, color: "var(--ink-3)",
        }}>
          <div style={{ fontSize: 32, marginBottom: 8 }}>🧾</div>
          <div style={{ fontSize: 14.5, fontWeight: 520, color: "var(--ink-2)" }}>Vous n'avez aucune facture pour l'instant</div>
          <div style={{ fontSize: 13, marginTop: 6 }}>Cliquez sur « Nouvelle facture » pour en émettre une.</div>
        </div>
      ) : isMobile ? (
        <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
          {data.invoices.map(f => (
            <InvoiceCardMobile key={f.id} f={f} data={data} actions={actions} onView={() => setViewId(f.id)}/>
          ))}
        </div>
      ) : (
        <FacturesSplitDesktop
          invoices={data.invoices}
          data={data}
          actions={actions}
          onView={(id) => setViewId(id)}
        />
      )}

      <InvoiceDetailModal invoice={viewInvoice} data={data} onClose={() => setViewId(null)}/>
      </div>{/* fin contenu (flouté pendant overlay) */}
    </div>
  );
};

/* === Vue desktop split-screen : liste à gauche + aperçu à droite === */
const FacturesSplitDesktop = ({ invoices, data, actions, onView }) => {
  const [selectedId, setSelectedId] = React.useState(() => invoices[0] ? invoices[0].id : null);
  const [filter, setFilter] = React.useState("all"); // all | paid | unpaid
  const q = filter === "all" ? invoices : invoices.filter(f => filter === "paid" ? f.paid : !f.paid);
  React.useEffect(() => {
    if (!q.find(f => f.id === selectedId)) {
      setSelectedId(q[0] ? q[0].id : null);
    }
  }, [q, selectedId]);
  const selected = selectedId ? data.invoices.find(f => f.id === selectedId) : null;
  const selectedClient = selected ? findClient(data, selected.clientId) : null;

  // KPIs en haut
  const totalPaid = data.invoices.filter(f => f.paid).reduce((s, f) => s + Number(f.amount || 0), 0);
  const totalUnpaid = data.invoices.filter(f => !f.paid).reduce((s, f) => s + Number(f.amount || 0), 0);

  return (
    <>
      {/* KPIs compacts */}
      <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(150px, 1fr))", gap: 10, marginBottom: 14 }}>
        {[
          { label: "Émises", value: data.invoices.length, tone: "var(--accent)" },
          { label: "CA encaissé", value: fmtEUR(totalPaid), tone: "oklch(55% 0.12 160)" },
          { label: "En attente", value: fmtEUR(totalUnpaid), tone: "oklch(60% 0.17 30)" },
        ].map(k => (
          <div key={k.label} style={{
            padding: "12px 14px", background: "var(--surface)",
            border: "1px solid var(--line)", borderRadius: 12,
            borderLeft: `3px solid ${k.tone}`,
          }}>
            <div style={{ fontFamily: "var(--ff-display)", fontSize: 19, fontWeight: 600, letterSpacing: "-0.025em", color: "var(--ink)" }}>{k.value}</div>
            <div style={{ fontSize: 11.5, color: "var(--ink-3)", marginTop: 2 }}>{k.label}</div>
          </div>
        ))}
      </div>

      {/* Filtres */}
      <div style={{ display: "flex", gap: 6, marginBottom: 12, flexWrap: "wrap" }}>
        {[["all", `Toutes (${invoices.length})`], ["paid", "Payées"], ["unpaid", "En attente"]].map(([k, l]) => (
          <button key={k} onClick={() => setFilter(k)} style={{
            padding: "6px 12px",
            background: filter === k ? "var(--ink)" : "var(--surface)",
            color: filter === k ? "var(--bg)" : "var(--ink-2)",
            border: `1px solid ${filter === k ? "var(--ink)" : "var(--line)"}`,
            borderRadius: 999, fontSize: 12.5, fontWeight: 540, cursor: "pointer", fontFamily: "inherit",
          }}>{l}</button>
        ))}
      </div>

      <div style={{
        display: "grid",
        gridTemplateColumns: "minmax(280px, 1fr) minmax(360px, 440px)",
        gap: 16, alignItems: "start",
      }}>
        {/* Liste */}
        <div style={{
          background: "var(--surface)", border: "1px solid var(--line)",
          borderRadius: 12, overflow: "hidden",
        }}>
          {q.length === 0 ? (
            <div style={{ padding: "30px 20px", textAlign: "center", color: "var(--ink-4)", fontSize: 13 }}>
              Aucune facture dans ce filtre.
            </div>
          ) : q.map((f, i) => {
            const c = findClient(data, f.clientId);
            const isActive = f.id === selectedId;
            const isCredit = (f.amount < 0) || !!f.creditOf;
            return (
              <button key={f.id} onClick={() => setSelectedId(f.id)}
                style={{
                  width: "100%", padding: "12px 14px",
                  background: isActive ? "var(--accent-soft)" : "transparent",
                  border: "none",
                  borderBottom: i < q.length - 1 ? "1px solid var(--line)" : "none",
                  borderLeft: `3px solid ${isActive ? "var(--accent)" : "transparent"}`,
                  cursor: "pointer", fontFamily: "inherit", textAlign: "left",
                  display: "flex", alignItems: "center", gap: 12,
                  transition: "background .15s",
                }}
                onMouseEnter={e => { if (!isActive) e.currentTarget.style.background = "var(--bg-alt)"; }}
                onMouseLeave={e => { if (!isActive) e.currentTarget.style.background = "transparent"; }}>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{
                    display: "flex", alignItems: "center", gap: 8, fontSize: 12, color: "var(--ink-4)",
                    marginBottom: 2,
                  }}>
                    <span>{f.number || f.id}</span>
                    {isCredit && (
                      <span style={{
                        padding: "1px 6px", fontSize: 9.5, fontWeight: 700,
                        background: "var(--bg-alt)", color: "var(--ink-3)",
                        borderRadius: 4, textTransform: "uppercase", letterSpacing: "0.04em",
                      }}>Avoir</span>
                    )}
                  </div>
                  <div style={{
                    fontWeight: isActive ? 600 : 540, fontSize: 13.5,
                    color: isActive ? "var(--accent-ink)" : "var(--ink)",
                    overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
                  }}>{c ? c.name : "—"}</div>
                  <div style={{ fontSize: 11.5, color: "var(--ink-4)", marginTop: 2 }}>{f.date}</div>
                </div>
                <div style={{ textAlign: "right", flexShrink: 0 }}>
                  <div style={{
                    fontFamily: "var(--ff-display)", fontSize: 15, fontWeight: 600,
                    color: f.amount < 0 ? "oklch(55% 0.18 25)" : "var(--ink)",
                    letterSpacing: "-0.02em",
                  }}>{fmtEUR(f.amount)}</div>
                  <span style={{
                    display: "inline-block", marginTop: 4,
                    padding: "2px 7px", fontSize: 10, fontWeight: 600, borderRadius: 4,
                    background: f.paid ? "var(--sage-soft)" : "var(--warn-soft)",
                    color: f.paid ? "oklch(38% 0.08 160)" : "var(--warn-ink)",
                    textTransform: "uppercase", letterSpacing: "0.04em",
                  }}>{f.paid ? "Payée" : "En attente"}</span>
                </div>
              </button>
            );
          })}
        </div>

        {/* Aperçu */}
        <div style={{ position: "sticky", top: 16 }}>
          {selected ? (
            <div style={{
              background: "var(--surface)", border: "1px solid var(--line)",
              borderRadius: 14, overflow: "hidden",
              animation: "cbFiche .25s cubic-bezier(.22,1,.36,1) both",
            }}>
              <div style={{
                padding: "12px 16px", borderBottom: "1px solid var(--line)",
                background: "var(--bg-alt)",
                display: "flex", justifyContent: "space-between", alignItems: "center", gap: 10,
              }}>
                <div style={{ fontSize: 11.5, color: "var(--ink-4)", fontWeight: 600, letterSpacing: "0.06em", textTransform: "uppercase" }}>
                  Aperçu facture
                </div>
                <div style={{ display: "flex", gap: 6 }}>
                  <button onClick={() => actions.toggleInvoicePaid(selected.id)}
                    className="btn btn-sm btn-ghost" style={{ height: 30, fontSize: 12 }}>
                    {selected.paid ? "Marquer non payée" : "Marquer payée"}
                  </button>
                  <button onClick={() => onView(selected.id)}
                    className="btn btn-sm btn-primary" style={{ height: 30, fontSize: 12 }}>
                    <Icon name="invoice" size={12}/> Voir / PDF
                  </button>
                </div>
              </div>
              <div style={{ padding: "18px 20px", display: "flex", flexDirection: "column", gap: 14 }}>
                {/* En-tête : numéro + statut */}
                <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 12, flexWrap: "wrap" }}>
                  <div>
                    <div style={{ fontFamily: "var(--ff-display)", fontSize: 22, fontWeight: 600, letterSpacing: "-0.025em" }}>
                      {(selected.amount < 0 || selected.creditOf) ? "Avoir " : "Facture "}{selected.number || selected.id}
                    </div>
                    <div style={{ fontSize: 12.5, color: "var(--ink-3)", marginTop: 2 }}>
                      Émise le {selected.date}
                    </div>
                  </div>
                  <span style={{
                    padding: "4px 10px", fontSize: 11, fontWeight: 700, borderRadius: 6,
                    background: selected.paid ? "var(--sage-soft)" : "var(--warn-soft)",
                    color: selected.paid ? "oklch(38% 0.08 160)" : "var(--warn-ink)",
                    textTransform: "uppercase", letterSpacing: "0.04em",
                  }}>{selected.paid ? "Payée" : "En attente"}</span>
                </div>

                {/* Client */}
                {selectedClient && (
                  <div style={{
                    padding: "12px 14px", background: "var(--bg-alt)",
                    border: "1px solid var(--line)", borderRadius: 10,
                    display: "flex", alignItems: "center", gap: 12,
                  }}>
                    <Avatar client={selectedClient} size={36}/>
                    <div style={{ minWidth: 0 }}>
                      <div style={{ fontWeight: 580, fontSize: 13.5, color: "var(--ink)" }}>{selectedClient.name}</div>
                      <div style={{ fontSize: 11.5, color: "var(--ink-4)", marginTop: 1 }}>
                        {selectedClient.email || selectedClient.phone || "—"}
                      </div>
                    </div>
                  </div>
                )}

                {/* Détail */}
                <div>
                  <div style={{ fontSize: 12.5, fontWeight: 580, color: "var(--ink-2)", marginBottom: 6 }}>
                    Désignation
                  </div>
                  <div style={{
                    padding: "10px 12px", background: "var(--bg-alt)",
                    border: "1px solid var(--line)", borderRadius: 9,
                    fontSize: 13, color: "var(--ink-2)",
                  }}>
                    {selected.designation || "Prestation de service"}
                    {selected.prestationDate && (
                      <div style={{ fontSize: 11.5, color: "var(--ink-4)", marginTop: 3 }}>
                        Réalisée le {selected.prestationDate}
                      </div>
                    )}
                  </div>
                </div>

                {/* Total */}
                <div style={{
                  padding: "12px 14px",
                  background: "linear-gradient(135deg, var(--accent-soft) 0%, oklch(98% 0.015 280) 100%)",
                  border: "1px solid var(--accent-soft-2)", borderRadius: 11,
                  display: "flex", justifyContent: "space-between", alignItems: "center",
                }}>
                  <span style={{ fontSize: 12.5, color: "var(--ink-3)", fontWeight: 540, textTransform: "uppercase", letterSpacing: "0.05em" }}>
                    Total
                  </span>
                  <span style={{ fontFamily: "var(--ff-display)", fontSize: 24, fontWeight: 600, letterSpacing: "-0.025em", color: "var(--accent-ink)" }}>
                    {fmtEUR(selected.amount)}
                  </span>
                </div>

                {/* Action danger */}
                {!selected.creditOf && selected.amount > 0 && (
                  <button onClick={() => { if (window.confirm("Annuler cette facture par un avoir comptable ?")) actions.deleteInvoice(selected.id); }}
                    className="btn btn-sm btn-ghost"
                    style={{ color: "oklch(55% 0.18 25)", borderColor: "oklch(86% 0.06 25)", border: "1px solid oklch(86% 0.06 25)", alignSelf: "flex-start" }}>
                    Annuler par avoir
                  </button>
                )}
              </div>
            </div>
          ) : (
            <div style={{
              padding: "40px 24px", textAlign: "center",
              background: "var(--surface)", border: "1px dashed var(--line-strong)",
              borderRadius: 14, color: "var(--ink-4)",
            }}>
              <div style={{ fontSize: 26, marginBottom: 8 }}>🧾</div>
              <div style={{ fontSize: 13.5, color: "var(--ink-3)" }}>
                Sélectionnez une facture pour voir son aperçu.
              </div>
            </div>
          )}
        </div>
      </div>
    </>
  );
};

/* ================================================================
   STOCK, adjust quantities, add, delete
================================================================ */
const STOCK_STATUS_COLOR = {
  ok:       ["var(--sage-soft)", "oklch(38% 0.08 160)", "Suffisant"],
  low:      ["oklch(95% 0.05 60)", "oklch(45% 0.13 60)", "À commander"],
  critical: ["oklch(96% 0.04 30)", "oklch(48% 0.18 30)", "Critique"],
};
const stockStatusOf = (it) => {
  const min = Number(it.min) || 0;
  if (it.qty >= min) return "ok";
  return it.qty < Math.ceil(min / 2) ? "critical" : "low";
};

// Catégories produits, repères pour s'y retrouver quand le stock grossit.
const STOCK_CATEGORIES = ["Consommables", "Vernis & gels", "Soins", "Matériel", "Accueil & déco", "Autre"];

const stockBtnStyle = {
  width: 38, height: 38, background: "transparent", border: "none",
  cursor: "pointer", color: "var(--ink-2)", fontSize: 19,
  borderRadius: 7, fontFamily: "inherit",
};

const StockCard = ({ it, actions, openModal }) => {
  const status = stockStatusOf(it);
  const stColor = STOCK_STATUS_COLOR[status];
  const min = Number(it.min) || 0;
  const value = (Number(it.qty) || 0) * (Number(it.price) || 0);
  const pct = min ? Math.min(100, (it.qty / min) * 100) : 100;
  return (
    <div style={{
      background: "var(--surface)",
      border: "1px solid " + (status !== "ok" ? stColor[1] + "44" : "var(--line)"),
      borderLeft: `3px solid ${status === "ok" ? "var(--sage)" : stColor[1]}`,
      borderRadius: 12, padding: 14,
      display: "flex", flexDirection: "column", gap: 12,
    }}>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 10 }}>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontWeight: 560, fontSize: 14.5, color: "var(--ink)" }}>{it.name}</div>
          <div style={{ display: "flex", gap: 8, flexWrap: "wrap", alignItems: "center", marginTop: 4 }}>
            {it.category && (
              <span style={{
                fontSize: 11, padding: "2px 8px", background: "var(--bg-alt)",
                border: "1px solid var(--line)", borderRadius: 999, color: "var(--ink-3)",
              }}>{it.category}</span>
            )}
            <span style={{ fontSize: 11.5, color: "var(--ink-4)" }}>{fmtEUR(it.price)} / unité</span>
          </div>
        </div>
        <span style={{
          padding: "3px 8px", fontSize: 10.5, fontWeight: 600, borderRadius: 5,
          background: stColor[0], color: stColor[1], flexShrink: 0,
          textTransform: "uppercase", letterSpacing: "0.03em",
        }}>{stColor[2]}</span>
      </div>

      {/* Jauge vers le seuil */}
      <div>
        <div style={{ display: "flex", justifyContent: "space-between", fontSize: 12, marginBottom: 5 }}>
          <span style={{ color: "var(--ink-2)", fontWeight: 540 }}>
            <strong style={{ fontSize: 15, color: "var(--ink)" }}>{it.qty}</strong> en stock
          </span>
          <span style={{ color: "var(--ink-4)" }}>seuil&nbsp;: {min}</span>
        </div>
        <div style={{ height: 6, background: "var(--bg-alt)", borderRadius: 999, overflow: "hidden" }}>
          <div style={{
            width: `${pct}%`, height: "100%", borderRadius: 999,
            background: status === "ok" ? "var(--sage)" : stColor[1],
            transition: "width .3s ease",
          }}/>
        </div>
        <div style={{ fontSize: 11, color: "var(--ink-4)", marginTop: 5 }}>
          Valeur immobilisée : <strong style={{ color: "var(--ink-3)" }}>{fmtEUR(value)}</strong>
          {it.supplier && <> · Fournisseur : {it.supplier}</>}
        </div>
      </div>

      {/* Contrôles */}
      <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 8, flexWrap: "wrap" }}>
        <div style={{
          display: "flex", alignItems: "center",
          border: "1px solid var(--line)", borderRadius: 10, padding: 3, background: "var(--bg-alt)",
        }}>
          <button onClick={() => actions.adjustStock(it.id, -1)} aria-label="Diminuer" style={stockBtnStyle}>−</button>
          <span style={{ fontSize: 15, fontWeight: 600, minWidth: 40, textAlign: "center", color: "var(--ink)" }}>{it.qty}</span>
          <button onClick={() => actions.adjustStock(it.id, +1)} aria-label="Augmenter" style={stockBtnStyle}>+</button>
        </div>
        <div style={{ display: "flex", gap: 6 }}>
          <button onClick={() => actions.adjustStock(it.id, +5)} className="btn btn-sm btn-ghost" style={{ height: 34, fontSize: 12 }}>+5</button>
          <button onClick={() => openModal("stock", { edit: it })} aria-label="Modifier"
            className="btn btn-sm btn-ghost" style={{ height: 34, width: 34, padding: 0 }}>
            <Icon name="settings" size={13}/>
          </button>
          <button onClick={() => actions.deleteStockItem(it.id)} aria-label="Supprimer"
            className="btn btn-sm btn-ghost" style={{ height: 34, width: 34, padding: 0, color: "oklch(55% 0.18 25)" }}>
            <Icon name="trash" size={13}/>
          </button>
        </div>
      </div>
    </div>
  );
};

const AppStock = ({ data, actions, openModal, setMod }) => {
  const stock = data.stock || [];
  const [search, setSearch] = React.useState("");
  const [cat, setCat] = React.useState("all");
  const [settingsOpen, setSettingsOpen] = React.useState(false);
  // Préférences stock, persistées en localStorage (le backend n'en a pas besoin pour l'instant)
  const [stockPrefs, setStockPrefs] = React.useState(() => {
    try {
      const raw = localStorage.getItem("cb_stock_prefs");
      const parsed = raw ? JSON.parse(raw) : null;
      return { defaultMin: 5, notifLow: true, notifCritical: true, ...(parsed || {}) };
    } catch {
      return { defaultMin: 5, notifLow: true, notifCritical: true };
    }
  });
  const setPref = (k, v) => {
    setStockPrefs(prev => {
      const next = { ...prev, [k]: v };
      try { localStorage.setItem("cb_stock_prefs", JSON.stringify(next)); } catch {}
      return next;
    });
  };

  const totalValue = stock.reduce((s, it) => s + (Number(it.qty) || 0) * (Number(it.price) || 0), 0);
  const low = stock.filter(s => stockStatusOf(s) !== "ok");
  const critical = stock.filter(s => stockStatusOf(s) === "critical");
  const cats = Array.from(new Set(stock.map(s => s.category).filter(Boolean)));

  const q = search.trim().toLowerCase();
  const rank = { critical: 0, low: 1, ok: 2 };
  const filtered = stock
    .filter(s => cat === "all" || s.category === cat)
    .filter(s => !q || s.name.toLowerCase().includes(q) || (s.supplier || "").toLowerCase().includes(q))
    .sort((a, b) => {
      const r = rank[stockStatusOf(a)] - rank[stockStatusOf(b)];
      return r !== 0 ? r : a.name.localeCompare(b.name);
    });

  const copyShoppingList = async () => {
    const lines = low
      .slice()
      .sort((a, b) => rank[stockStatusOf(a)] - rank[stockStatusOf(b)])
      .map(it => `• ${it.name}, ${it.qty} en stock (seuil ${Number(it.min) || 0})${it.supplier ? `, ${it.supplier}` : ""}`);
    const text = `Liste de réappro, ${(data.business && data.business.businessName) || "ClientBase"}\n\n${lines.join("\n")}`;
    try {
      if (navigator.clipboard) await navigator.clipboard.writeText(text);
      showToast("Liste de réappro copiée");
    } catch { showToast("Impossible de copier", "warn"); }
  };

  return (
    <div>
      {/* En-tête */}
      <div style={{ marginBottom: 18, display: "flex", alignItems: "flex-start", justifyContent: "space-between", gap: 12, flexWrap: "wrap" }}>
        <div>
          <h3 style={{ fontSize: 22, fontFamily: "var(--ff-display)", fontWeight: 580, letterSpacing: "-0.022em" }}>Votre stock</h3>
          <p style={{ fontSize: 14, color: "var(--ink-3)", marginTop: 5, lineHeight: 1.5, maxWidth: 560 }}>
            Suivez vos produits, repérez ce qui est à commander, gardez un œil sur la valeur immobilisée.
          </p>
        </div>
        <div style={{ display: "flex", gap: 8, flexShrink: 0 }}>
          <button className="btn btn-ghost btn-sm cb-press-feedback"
            onClick={() => setSettingsOpen(v => !v)}
            title="Paramètres du stock">
            <Icon name="settings" size={13}/> {settingsOpen ? "Fermer" : "Paramètres"}
          </button>
          <button className="btn btn-primary btn-sm cb-press-feedback" onClick={() => openModal("stock")}>
            <Icon name="plus" size={13}/> Nouveau produit
          </button>
        </div>
      </div>

      {/* Panneau Paramètres stock, préférences spécifiques au module */}
      {settingsOpen && (
        <div style={{
          marginBottom: 18, padding: "18px 20px",
          background: "var(--surface)", border: "1px solid var(--accent-soft-2)",
          borderRadius: 14, boxShadow: "var(--sh-2)",
        }}>
          <div style={{ fontSize: 14.5, fontWeight: 600, marginBottom: 4 }}>Paramètres du stock</div>
          <div style={{ fontSize: 12.5, color: "var(--ink-4)", marginBottom: 14 }}>
            Réglez le comportement par défaut. Ces préférences sont locales à votre navigateur pour l'instant.
          </div>
          <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(220px, 1fr))", gap: 14 }}>
            <div>
              <label style={{ fontSize: 12.5, color: "var(--ink-3)", display: "block", marginBottom: 5, fontWeight: 540 }}>
                Seuil bas par défaut (nouveau produit)
              </label>
              <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
                <input type="number" min="0" value={stockPrefs.defaultMin}
                  onChange={e => setPref("defaultMin", Math.max(0, +e.target.value || 0))}
                  style={{
                    flex: 1, padding: "9px 12px",
                    background: "var(--bg-alt)", border: "1px solid var(--line)",
                    borderRadius: 9, fontSize: 14, fontFamily: "inherit", color: "var(--ink)", outline: "none",
                  }}/>
                <span style={{ fontSize: 12, color: "var(--ink-4)" }}>unités</span>
              </div>
              <div style={{ fontSize: 11.5, color: "var(--ink-4)", marginTop: 4, lineHeight: 1.4 }}>
                Utilisé comme valeur pré-remplie quand vous créez un produit.
              </div>
            </div>
            <label style={{
              display: "flex", alignItems: "flex-start", gap: 10, cursor: "pointer",
              padding: "10px 12px", background: "var(--bg-alt)",
              border: "1px solid var(--line)", borderRadius: 10,
            }}>
              <input type="checkbox" checked={!!stockPrefs.notifLow}
                onChange={e => setPref("notifLow", e.target.checked)}
                style={{ marginTop: 2, accentColor: "var(--accent)", width: 16, height: 16 }}/>
              <span>
                <span style={{ fontSize: 13, fontWeight: 580, color: "var(--ink)" }}>Alerte stock bas</span>
                <span style={{ display: "block", fontSize: 11.5, color: "var(--ink-4)", marginTop: 2, lineHeight: 1.4 }}>
                  M'avertir quand un produit passe sous son seuil.
                </span>
              </span>
            </label>
            <label style={{
              display: "flex", alignItems: "flex-start", gap: 10, cursor: "pointer",
              padding: "10px 12px", background: "var(--bg-alt)",
              border: "1px solid var(--line)", borderRadius: 10,
            }}>
              <input type="checkbox" checked={!!stockPrefs.notifCritical}
                onChange={e => setPref("notifCritical", e.target.checked)}
                style={{ marginTop: 2, accentColor: "var(--accent)", width: 16, height: 16 }}/>
              <span>
                <span style={{ fontSize: 13, fontWeight: 580, color: "var(--ink)" }}>Alerte critique</span>
                <span style={{ display: "block", fontSize: 11.5, color: "var(--ink-4)", marginTop: 2, lineHeight: 1.4 }}>
                  Plus marquée quand un produit passe sous la moitié du seuil.
                </span>
              </span>
            </label>
          </div>
        </div>
      )}

      {/* KPIs */}
      {stock.length > 0 && (
        <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(150px, 1fr))", gap: 10, marginBottom: 18 }}>
          {[
            { label: "Produits", value: stock.length, icon: "box", tone: "var(--accent)" },
            { label: "Valeur du stock", value: fmtEUR(totalValue), icon: "euro", tone: "oklch(55% 0.12 160)" },
            { label: "À commander", value: low.length, icon: "bell", tone: "oklch(62% 0.16 60)" },
            { label: "En rupture / critique", value: critical.length, icon: "close", tone: "oklch(58% 0.18 30)" },
          ].map(kpi => (
            <div key={kpi.label} style={{ padding: "14px 16px", background: "var(--surface)", border: "1px solid var(--line)", borderRadius: 13 }}>
              <div style={{
                width: 28, height: 28, borderRadius: 8, marginBottom: 9,
                background: `color-mix(in oklab, ${kpi.tone} 14%, transparent)`, color: kpi.tone,
                display: "flex", alignItems: "center", justifyContent: "center",
              }}>
                <Icon name={kpi.icon} size={14}/>
              </div>
              <div style={{ fontFamily: "var(--ff-display)", fontSize: 22, fontWeight: 580, letterSpacing: "-0.025em" }}>{kpi.value}</div>
              <div style={{ fontSize: 12, color: "var(--ink-3)", marginTop: 1 }}>{kpi.label}</div>
            </div>
          ))}
        </div>
      )}

      {/* À commander, panneau prioritaire */}
      {low.length > 0 && (
        <div style={{
          padding: 16, background: "oklch(98% 0.02 60)",
          border: "1px solid oklch(88% 0.06 60)", borderRadius: 14, marginBottom: 18,
        }}>
          <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 10, flexWrap: "wrap" }}>
            <div style={{
              width: 32, height: 32, borderRadius: 9, background: "oklch(70% 0.15 60)", color: "#fff",
              display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0,
            }}>
              <Icon name="bell" size={15}/>
            </div>
            <div style={{ flex: 1, minWidth: 140 }}>
              <div style={{ fontWeight: 580, fontSize: 14, color: "oklch(38% 0.12 60)" }}>
                {low.length} produit{low.length > 1 ? "s" : ""} à réapprovisionner
              </div>
            </div>
            <button onClick={copyShoppingList} className="btn btn-sm btn-ghost" style={{ height: 32, fontSize: 12.5 }}>
              <Icon name="copy" size={12}/> Copier la liste
            </button>
          </div>
          <div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>
            {low.map(it => (
              <span key={it.id} style={{
                fontSize: 12, padding: "4px 10px", background: "var(--surface)",
                border: "1px solid oklch(88% 0.06 60)", borderRadius: 999, color: "oklch(40% 0.10 60)",
              }}>
                {it.name} <strong>· {it.qty}</strong>
              </span>
            ))}
          </div>
        </div>
      )}

      {/* Recherche + filtre catégorie */}
      {stock.length > 0 && (
        <div style={{ display: "flex", gap: 10, marginBottom: 14, flexWrap: "wrap", alignItems: "center" }}>
          <div style={{ position: "relative", flex: 1, minWidth: 180 }}>
            <Icon name="search" size={14} style={{ position: "absolute", left: 11, top: "50%", transform: "translateY(-50%)", color: "var(--ink-4)" }}/>
            <input value={search} onChange={e => setSearch(e.target.value)}
              placeholder="Rechercher un produit, un fournisseur…"
              style={{
                width: "100%", padding: "9px 12px 9px 32px", background: "var(--surface)",
                border: "1px solid var(--line)", borderRadius: 9, fontSize: 13.5,
                fontFamily: "inherit", color: "var(--ink)", outline: "none",
              }}/>
          </div>
          {cats.length > 0 && (
            <div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>
              {["all", ...cats].map(c => (
                <button key={c} onClick={() => setCat(c)} style={{
                  padding: "6px 12px",
                  background: cat === c ? "var(--ink)" : "var(--surface)",
                  color: cat === c ? "var(--bg)" : "var(--ink-2)",
                  border: `1px solid ${cat === c ? "var(--ink)" : "var(--line)"}`,
                  borderRadius: 999, fontSize: 12.5, fontWeight: 540, cursor: "pointer", fontFamily: "inherit",
                }}>
                  {c === "all" ? "Toutes" : c}
                </button>
              ))}
            </div>
          )}
        </div>
      )}

      {/* Liste produits */}
      {stock.length === 0 ? (
        <div style={{
          padding: "40px 20px", textAlign: "center",
          background: "var(--bg-alt)", border: "1px dashed var(--line-strong)",
          borderRadius: 12, color: "var(--ink-3)",
        }}>
          <div style={{ fontSize: 32, marginBottom: 8 }}>📦</div>
          <div style={{ fontSize: 14.5, fontWeight: 520, color: "var(--ink-2)" }}>Vous n'avez aucun produit pour l'instant</div>
          <div style={{ fontSize: 13, marginTop: 6 }}>Cliquez sur « Nouveau produit » pour commencer.</div>
        </div>
      ) : filtered.length === 0 ? (
        <div style={{
          padding: "30px 20px", textAlign: "center",
          background: "var(--bg-alt)", border: "1px dashed var(--line-strong)",
          borderRadius: 12, color: "var(--ink-4)", fontSize: 13.5,
        }}>
          Aucun produit ne correspond à votre recherche.
        </div>
      ) : (
        <StockSplitDesktop
          items={filtered}
          actions={actions}
          openModal={openModal}
        />
      )}
    </div>
  );
};

/* === Vue desktop split-screen : liste compacte + carte détaillée === */
const StockSplitDesktop = ({ items, actions, openModal }) => {
  const [selectedId, setSelectedId] = React.useState(() => items[0] ? items[0].id : null);
  React.useEffect(() => {
    if (!items.find(it => it.id === selectedId)) {
      setSelectedId(items[0] ? items[0].id : null);
    }
  }, [items, selectedId]);
  const selected = selectedId ? items.find(it => it.id === selectedId) : null;
  return (
    <div style={{
      display: "grid",
      gridTemplateColumns: "minmax(260px, 1fr) minmax(340px, 440px)",
      gap: 16, alignItems: "start",
    }}>
      {/* Liste compacte */}
      <div style={{
        background: "var(--surface)", border: "1px solid var(--line)",
        borderRadius: 12, overflow: "hidden",
      }}>
        {items.map((it, i) => {
          const status = stockStatusOf(it);
          const stColor = STOCK_STATUS_COLOR[status];
          const isActive = it.id === selectedId;
          const min = Number(it.min) || 0;
          const pct = min ? Math.min(100, (it.qty / min) * 100) : 100;
          return (
            <button key={it.id} onClick={() => setSelectedId(it.id)}
              style={{
                width: "100%", padding: "12px 14px",
                background: isActive ? "var(--accent-soft)" : "transparent",
                border: "none",
                borderBottom: i < items.length - 1 ? "1px solid var(--line)" : "none",
                borderLeft: `3px solid ${isActive ? "var(--accent)" : (status === "ok" ? "var(--sage)" : stColor[1])}`,
                cursor: "pointer", fontFamily: "inherit", textAlign: "left",
                display: "flex", alignItems: "center", gap: 12,
                transition: "background .15s",
              }}
              onMouseEnter={e => { if (!isActive) e.currentTarget.style.background = "var(--bg-alt)"; }}
              onMouseLeave={e => { if (!isActive) e.currentTarget.style.background = "transparent"; }}>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{
                  fontWeight: isActive ? 600 : 540, fontSize: 13.5,
                  color: isActive ? "var(--accent-ink)" : "var(--ink)",
                  overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
                }}>{it.name}</div>
                <div style={{
                  fontSize: 11.5, color: "var(--ink-4)", marginTop: 2,
                  display: "flex", alignItems: "center", gap: 6, flexWrap: "wrap",
                }}>
                  {it.category && <span>{it.category}</span>}
                  <span>{fmtEUR(it.price)} / unité</span>
                </div>
                <div style={{ marginTop: 5, display: "flex", alignItems: "center", gap: 6 }}>
                  <div style={{ flex: 1, height: 4, background: "var(--bg-alt)", borderRadius: 999, overflow: "hidden" }}>
                    <div style={{
                      width: `${pct}%`, height: "100%",
                      background: status === "ok" ? "var(--sage)" : stColor[1],
                    }}/>
                  </div>
                  <span style={{ fontSize: 10.5, color: "var(--ink-4)" }}>{it.qty}/{min || "—"}</span>
                </div>
              </div>
              <span style={{
                padding: "2px 7px", fontSize: 9.5, fontWeight: 700, borderRadius: 4,
                background: stColor[0], color: stColor[1],
                textTransform: "uppercase", letterSpacing: "0.04em", flexShrink: 0,
              }}>{stColor[2]}</span>
            </button>
          );
        })}
      </div>

      {/* Détail produit */}
      <div style={{ position: "sticky", top: 16 }}>
        {selected ? (
          <div key={selected.id} style={{ animation: "cbFiche .25s cubic-bezier(.22,1,.36,1) both" }}>
            <StockCard it={selected} actions={actions} openModal={openModal}/>
          </div>
        ) : (
          <div style={{
            padding: "40px 24px", textAlign: "center",
            background: "var(--surface)", border: "1px dashed var(--line-strong)",
            borderRadius: 14, color: "var(--ink-4)",
          }}>
            <div style={{ fontSize: 26, marginBottom: 8 }}>📦</div>
            <div style={{ fontSize: 13.5, color: "var(--ink-3)" }}>
              Sélectionnez un produit pour voir le détail.
            </div>
          </div>
        )}
      </div>
    </div>
  );
};

/* ================================================================
   PRISE DE RDV, public booking link + settings
================================================================ */
const DAY_FULL_LABELS = ["Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche"];

// Migrate legacy bookingSettings → schedule if missing
const resolveSchedule = (bs) => {
  if (bs.schedule) return bs.schedule;
  const daysOpen = bs.daysOpen || [1, 2, 3, 4, 5, 6];
  const h = bs.hours || { start: 9, end: 18 };
  const s = {};
  for (let i = 1; i <= 7; i++) {
    s[i] = { open: daysOpen.includes(i), start: h.start, end: h.end };
  }
  return s;
};

/* ============ SERVICES, gestion des prestations proposées ============ */
const AppServices = ({ data, actions }) => {
  const services = data.services || [];
  const categories = (data.serviceCategories || []).slice().sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
  const [editing, setEditing] = React.useState(null); // null | "new" | service.id
  const [draft, setDraft] = React.useState({ name: "", price: "", priceFrom: false, duration: "1", bufferMin: "", description: "", active: true, categoryId: null });
  const [catEditing, setCatEditing] = React.useState(null); // null | "new" | catId
  const CAT_DRAFT_INIT = { name: "", colorId: null, description: "" };
  const [catDraft, setCatDraft] = React.useState(CAT_DRAFT_INIT);
  // Drag & drop : id de la prestation en cours de déplacement
  const [draggingId, setDraggingId] = React.useState(null);
  const onDropService = (svcId, targetCategoryId) => {
    setDraggingId(null);
    const svc = services.find(s => s.id === svcId);
    if (!svc) return;
    const curCat = svc.categoryId || null;
    if (curCat === targetCategoryId) return;
    actions.updateService(svcId, { categoryId: targetCategoryId });
    showToast("Prestation déplacée");
  };

  const startNew = (categoryId = null) => {
    setDraft({ name: "", price: "", priceFrom: false, duration: "1", bufferMin: "", description: "", active: true, categoryId });
    setEditing("new");
  };
  const startEdit = (s) => {
    setDraft({
      name: s.name, price: String(s.price ?? ""), priceFrom: !!s.priceFrom,
      duration: String(s.duration ?? "1"),
      bufferMin: s.bufferMin != null ? String(s.bufferMin) : "",
      description: s.description || "", active: s.active !== false,
      categoryId: s.categoryId || null,
    });
    setEditing(s.id);
  };
  const cancel = () => setEditing(null);
  const save = () => {
    if (!draft.name.trim()) { showToast("Donnez un nom à votre prestation", "warn"); return; }
    // bufferMin : minutes de battement spécifiques à CETTE prestation. Si vide,
    // on retombe sur le délai global (business.policyBufferMin) au moment du
    // calcul (agenda + créneaux de réservation).
    const bufRaw = String(draft.bufferMin || "").trim();
    const payload = {
      name: draft.name.trim(),
      price: Number(draft.price) || 0,
      priceFrom: !!draft.priceFrom,
      duration: Number(draft.duration) || 1,
      bufferMin: bufRaw === "" ? null : Math.max(0, parseInt(bufRaw, 10) || 0),
      description: draft.description.trim(),
      active: draft.active,
      categoryId: draft.categoryId || null,
    };
    if (editing === "new") {
      // Calcule l'order : dernier + 1 dans cette catégorie
      const sib = services.filter(s => (s.categoryId || null) === payload.categoryId);
      const maxOrd = sib.reduce((m, s) => Math.max(m, s.order ?? 0), 0);
      payload.order = maxOrd + 1;
      actions.addService(payload);
    } else {
      actions.updateService(editing, payload);
    }
    setEditing(null);
  };

  const fmtDuration = (h) => {
    if (!h && h !== 0) return "—";
    const hh = Math.floor(h);
    const mm = Math.round((h - hh) * 60);
    if (hh === 0) return `${mm} min`;
    if (mm === 0) return `${hh} h`;
    return `${hh} h ${String(mm).padStart(2, "0")}`;
  };

  // Groupe les services par catégorie (catégories triées par order, puis "Sans catégorie" en dernier)
  const groups = React.useMemo(() => {
    const byId = {};
    categories.forEach(c => { byId[c.id] = { cat: c, items: [] }; });
    byId["__none__"] = { cat: null, items: [] };
    services.forEach(s => {
      const k = s.categoryId && byId[s.categoryId] ? s.categoryId : "__none__";
      byId[k].items.push(s);
    });
    // Tri intra-catégorie par order
    Object.values(byId).forEach(g => g.items.sort((a, b) => (a.order ?? 0) - (b.order ?? 0)));
    // Ordre : catégories triées, puis "sans catégorie"
    const out = categories.map(c => byId[c.id]);
    if (byId["__none__"].items.length > 0) out.push(byId["__none__"]);
    return out;
  }, [categories, services]);

  const saveCategoryDraft = () => {
    const name = (catDraft.name || "").trim();
    if (!name) { setCatEditing(null); return; }
    const payload = { name, colorId: catDraft.colorId, description: (catDraft.description || "").trim() };
    if (catEditing === "new") {
      actions.addServiceCategory(payload);
    } else if (catEditing) {
      actions.updateServiceCategory(catEditing, payload);
    }
    setCatEditing(null);
    setCatDraft(CAT_DRAFT_INIT);
  };

  return (
    <>
      {/* CSS du module Prestations, desktop = drag, mobile = select */}
      <style>{`
        @media (min-width: 721px) {
          .cb-presta-cat-select { display: none !important; }
        }
        @media (max-width: 720px) {
          .cb-presta-actions {
            width: 100%; justify-content: flex-end !important;
            margin-top: 4px;
          }
          .cb-presta-cat-select {
            flex: 1 1 100%; height: 36px !important; font-size: 13.5px !important;
            margin-bottom: 4px;
          }
        }
      `}</style>
    <div>
      <div style={{ marginBottom: 18, display: "flex", alignItems: "flex-start", justifyContent: "space-between", gap: 12, flexWrap: "wrap" }}>
        <div>
          <h3 style={{ fontSize: 22, fontFamily: "var(--ff-display)", fontWeight: 580, letterSpacing: "-0.022em" }}>Vos prestations</h3>
          <p style={{ fontSize: 14, color: "var(--ink-3)", marginTop: 5, lineHeight: 1.5, maxWidth: 560 }}>
            Organisez par catégories, réordonnez avec les flèches. L'ordre est respecté sur votre page de réservation publique.
          </p>
        </div>
        <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
          <button onClick={() => { setCatDraft(CAT_DRAFT_INIT); setCatEditing("new"); }}
            className="btn btn-ghost btn-sm cb-press-feedback">
            <Icon name="plus" size={13}/> Catégorie
          </button>
          {editing === null && (
            <button onClick={() => startNew()} className="btn btn-primary btn-sm cb-press-feedback">
              <Icon name="plus" size={13}/> Prestation
            </button>
          )}
        </div>
      </div>

      {/* Formulaire édition catégorie (nom + couleur + descriptif) */}
      {catEditing && (
        <div style={{
          marginBottom: 16, padding: 18,
          background: "var(--surface)", border: "1px solid var(--accent-soft-2)",
          borderRadius: 14, boxShadow: "var(--sh-2)",
          display: "flex", flexDirection: "column", gap: 12,
        }}>
          <div style={{ fontSize: 14, fontWeight: 580 }}>
            {catEditing === "new" ? "Nouvelle catégorie" : "Modifier la catégorie"}
          </div>
          <div>
            <label style={{ fontSize: 12.5, color: "var(--ink-3)", display: "block", marginBottom: 4 }}>
              Nom de la catégorie *
            </label>
            <input type="text" value={catDraft.name || ""}
              placeholder="ex. Ongles, Maquillage, Soins visage"
              onChange={e => setCatDraft({ ...catDraft, name: e.target.value })}
              onKeyDown={e => { if (e.key === "Enter") saveCategoryDraft(); if (e.key === "Escape") setCatEditing(null); }}
              autoFocus
              style={{
                width: "100%", padding: "10px 12px",
                background: "var(--bg-alt)", border: "1px solid var(--line)", borderRadius: 9,
                fontSize: 14, fontFamily: "inherit", color: "var(--ink)", outline: "none",
              }}/>
          </div>

          <div>
            <label style={{ fontSize: 12.5, color: "var(--ink-3)", display: "block", marginBottom: 6 }}>
              Couleur de la catégorie
            </label>
            <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
              {CATEGORY_PALETTE.map((p, i) => {
                const active = catDraft.colorId === i;
                return (
                  <button key={i} onClick={() => setCatDraft({ ...catDraft, colorId: i })}
                    aria-label={`Couleur ${i + 1}`}
                    style={{
                      width: 32, height: 32, borderRadius: 10,
                      background: p.solid, border: active ? "2px solid var(--ink)" : "2px solid transparent",
                      boxShadow: active ? `0 0 0 2px var(--surface) inset, 0 5px 12px -3px ${p.solid}` : "var(--sh-1)",
                      cursor: "pointer", padding: 0,
                    }}/>
                );
              })}
              {catDraft.colorId != null && (
                <button onClick={() => setCatDraft({ ...catDraft, colorId: null })}
                  className="btn btn-sm btn-ghost"
                  style={{ height: 32, fontSize: 12 }}>
                  Auto
                </button>
              )}
            </div>
          </div>

          <div>
            <label style={{ fontSize: 12.5, color: "var(--ink-3)", display: "block", marginBottom: 4 }}>
              Description (optionnelle)
            </label>
            <textarea value={catDraft.description || ""}
              placeholder="Quelques mots sur cette catégorie, visible sur votre page de réservation"
              maxLength={140}
              rows={2}
              onChange={e => setCatDraft({ ...catDraft, description: e.target.value })}
              style={{
                width: "100%", padding: "9px 12px",
                background: "var(--bg-alt)", border: "1px solid var(--line)", borderRadius: 9,
                fontSize: 13, fontFamily: "inherit", color: "var(--ink)",
                resize: "vertical", minHeight: 56, outline: "none",
              }}/>
          </div>

          <div style={{ display: "flex", gap: 8 }}>
            <button onClick={saveCategoryDraft} className="btn btn-accent btn-sm">
              {catEditing === "new" ? "Créer la catégorie" : "Enregistrer"}
            </button>
            <button onClick={() => setCatEditing(null)} className="btn btn-ghost btn-sm">Annuler</button>
          </div>
        </div>
      )}

      {/* Form add/edit service */}
      {editing !== null && (
        <div style={{
          marginBottom: 18, padding: 18,
          background: "var(--surface)", border: `1px solid var(--accent-soft-2)`,
          borderRadius: 14, boxShadow: "var(--sh-2)",
        }}>
          <div style={{ fontSize: 14, fontWeight: 580, marginBottom: 14 }}>
            {editing === "new" ? "Nouvelle prestation" : "Modifier"}
          </div>
          <div style={{ display: "grid", gap: 12 }}>
            <ServiceField label="Nom de la prestation *" value={draft.name}
              onChange={v => setDraft({ ...draft, name: v })}
              placeholder="ex. Coupe + brushing"/>
            <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }} className="app-split">
              <div>
                <ServiceField label={draft.priceFrom ? "Prix de départ (€)" : "Prix (€)"} type="number" value={draft.price}
                  onChange={v => setDraft({ ...draft, price: v })}
                  placeholder="45"/>
                <label style={{
                  display: "inline-flex", alignItems: "center", gap: 8,
                  marginTop: 8, cursor: "pointer", fontSize: 12.5, color: "var(--ink-2)",
                }}>
                  <input type="checkbox" checked={!!draft.priceFrom}
                    onChange={e => setDraft({ ...draft, priceFrom: e.target.checked })}
                    style={{ width: 15, height: 15, accentColor: "var(--accent)" }}/>
                  « À partir de », prix variable selon la cliente
                </label>
              </div>
              {/* Durée libre (h + min) avec raccourcis. Plus pratique que les
                  heures décimales (« 2.5 » = pas évident). */}
              {(() => {
                const dNum = Number(draft.duration) || 0;
                const dH = Math.floor(dNum);
                const dM = Math.round((dNum - dH) * 60);
                const setDur = (h, m) => setDraft({ ...draft, duration: String(h + m / 60) });
                const presets = [0.25, 0.5, 0.75, 1, 1.5, 2, 3, 4];
                const fmtPreset = (h) => h < 1
                  ? `${Math.round(h * 60)} min`
                  : (h % 1 === 0 ? `${h} h` : `${Math.floor(h)} h ${Math.round((h % 1) * 60)}`);
                return (
                  <div>
                    <label style={{ display: "block", fontSize: 12.5, color: "var(--ink-3)", fontWeight: 540, marginBottom: 6 }}>Durée</label>
                    <div style={{ display: "flex", gap: 6, flexWrap: "wrap", marginBottom: 8 }}>
                      {presets.map(h => {
                        const on = Math.abs(dNum - h) < 0.005;
                        return (
                          <button key={h} type="button" onClick={() => setDraft({ ...draft, duration: String(h) })} style={{
                            padding: "6px 11px", borderRadius: 999, cursor: "pointer", fontFamily: "inherit",
                            fontSize: 12.5, fontWeight: on ? 640 : 520,
                            background: on ? "var(--accent)" : "var(--surface)",
                            color: on ? "#fff" : "var(--ink-2)",
                            border: on ? "1px solid var(--accent)" : "1px solid var(--line)",
                          }}>{fmtPreset(h)}</button>
                        );
                      })}
                    </div>
                    <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                      <span style={{ fontSize: 12.5, color: "var(--ink-3)" }}>ou exact :</span>
                      <input type="number" min="0" max="23" value={dH}
                        onChange={e => setDur(Math.max(0, Math.min(23, parseInt(e.target.value, 10) || 0)), dM)}
                        style={{ width: 64, padding: "7px 9px", border: "1px solid var(--line)", borderRadius: 8, fontSize: 13, fontFamily: "inherit", color: "var(--ink)", background: "var(--surface)" }}/>
                      <span style={{ fontSize: 13, color: "var(--ink-2)" }}>h</span>
                      <input type="number" min="0" max="59" step="5" value={dM}
                        onChange={e => setDur(dH, Math.max(0, Math.min(59, parseInt(e.target.value, 10) || 0)))}
                        style={{ width: 64, padding: "7px 9px", border: "1px solid var(--line)", borderRadius: 8, fontSize: 13, fontFamily: "inherit", color: "var(--ink)", background: "var(--surface)" }}/>
                      <span style={{ fontSize: 13, color: "var(--ink-2)" }}>min</span>
                    </div>
                  </div>
                );
              })()}
              <ServiceField label="Délai après ce RDV (min)" type="number" value={draft.bufferMin}
                onChange={v => setDraft({ ...draft, bufferMin: v })}
                placeholder="ex : 15 — vide = délai global"/>
            </div>
            {/* Sélecteur de catégorie */}
            <div>
              <label style={{ fontSize: 12.5, color: "var(--ink-3)", display: "block", marginBottom: 4 }}>
                Catégorie
              </label>
              <select value={draft.categoryId || ""}
                onChange={e => setDraft({ ...draft, categoryId: e.target.value || null })}
                style={{ ...fieldInputStyle, cursor: "pointer" }}>
                <option value="">, Sans catégorie —</option>
                {categories.map(c => (
                  <option key={c.id} value={c.id}>{c.name}</option>
                ))}
              </select>
            </div>
            <div>
              <label style={{ fontSize: 12.5, color: "var(--ink-3)", display: "block", marginBottom: 4 }}>
                Description (optionnel, visible par vos clients)
              </label>
              <textarea value={draft.description} onChange={e => setDraft({ ...draft, description: e.target.value })}
                placeholder="Ce qui est inclus, conditions particulières…"
                rows={2}
                style={{ ...fieldInputStyle, resize: "vertical", minHeight: 60 }}/>
            </div>
            <label style={{ display: "flex", alignItems: "center", gap: 8, cursor: "pointer", fontSize: 14 }}>
              <input type="checkbox" checked={draft.active} onChange={e => setDraft({ ...draft, active: e.target.checked })}
                style={{ width: 16, height: 16, accentColor: "var(--accent)", cursor: "pointer" }}/>
              <span>Visible sur la page de réservation</span>
            </label>
          </div>
          <div style={{ display: "flex", gap: 8, marginTop: 14, flexWrap: "wrap" }}>
            <button onClick={save} className="btn btn-accent btn-sm">
              {editing === "new" ? "Ajouter" : "Enregistrer"}
            </button>
            <button onClick={cancel} className="btn btn-ghost btn-sm">Annuler</button>
          </div>
        </div>
      )}

      {/* Liste groupée par catégorie */}
      {services.length === 0 ? (
        <div style={{
          padding: "40px 20px", textAlign: "center",
          background: "var(--bg-alt)", border: "1px dashed var(--line-strong)",
          borderRadius: 12, color: "var(--ink-3)",
        }}>
          <div style={{ fontSize: 32, marginBottom: 8 }}>✦</div>
          <div style={{ fontSize: 14.5, fontWeight: 520, color: "var(--ink-2)" }}>Aucune prestation pour le moment</div>
          <div style={{ fontSize: 13, marginTop: 6 }}>Cliquez sur « Prestation » pour commencer.</div>
        </div>
      ) : (
        <div style={{ display: "flex", flexDirection: "column", gap: 18 }}>
          {groups.map((g, gi) => (
            <ServiceCategoryBlock key={g.cat ? g.cat.id : "__none__"}
              category={g.cat}
              colorIdx={gi}
              items={g.items}
              categories={categories}
              isFirst={gi === 0}
              isLast={gi === groups.length - 1}
              fmtDuration={fmtDuration}
              actions={actions}
              startEdit={startEdit}
              startNewIn={(cId) => startNew(cId)}
              onEditCat={(c) => { setCatDraft({ name: c.name, colorId: c.colorId, description: c.description || "" }); setCatEditing(c.id); }}
              draggingId={draggingId}
              setDraggingId={setDraggingId}
              onDropService={onDropService}
            />
          ))}
        </div>
      )}
    </div>
    </>
  );
};

// Palette pour différencier visuellement les catégories de prestations.
const CATEGORY_PALETTE = [
  { solid: "oklch(55% 0.22 278)", tint: "oklch(97% 0.025 278)", ink: "oklch(38% 0.18 278)" }, // indigo
  { solid: "oklch(60% 0.16 340)", tint: "oklch(97% 0.025 340)", ink: "oklch(42% 0.14 340)" }, // rose
  { solid: "oklch(55% 0.12 160)", tint: "oklch(96% 0.025 160)", ink: "oklch(38% 0.10 160)" }, // sage
  { solid: "oklch(60% 0.17 30)",  tint: "oklch(97% 0.025 30)",  ink: "oklch(42% 0.14 30)"  }, // orange
  { solid: "oklch(55% 0.15 200)", tint: "oklch(96% 0.03 200)",  ink: "oklch(38% 0.12 200)" }, // teal
  { solid: "oklch(58% 0.16 290)", tint: "oklch(97% 0.03 290)",  ink: "oklch(40% 0.16 290)" }, // lavande
  { solid: "oklch(60% 0.15 50)",  tint: "oklch(97% 0.03 50)",   ink: "oklch(42% 0.14 50)"  }, // ambre
  { solid: "oklch(58% 0.16 240)", tint: "oklch(96% 0.03 240)",  ink: "oklch(40% 0.14 240)" }, // bleu
];

/* === Bloc d'une catégorie (avec ses prestations) === */
const ServiceCategoryBlock = ({ category, colorIdx = 0, items, categories = [], isFirst, isLast, fmtDuration, actions, startEdit, startNewIn, onEditCat, draggingId, setDraggingId, onDropService }) => {
  const isNone = !category;
  // colorId choisi par l'utilisateur, sinon repère auto par position
  const palIdx = (category && category.colorId != null) ? category.colorId : colorIdx;
  const pal = isNone ? null : CATEGORY_PALETTE[palIdx % CATEGORY_PALETTE.length];
  const targetCatId = category ? category.id : null;
  const [isDragOver, setIsDragOver] = React.useState(false);
  const accentColor = pal ? pal.solid : "var(--accent)";
  const catActionBtn = {
    height: 34, width: 34, padding: 0,
    display: "inline-flex", alignItems: "center", justifyContent: "center",
    background: pal ? `color-mix(in oklab, ${pal.solid} 10%, var(--surface))` : "var(--surface)",
    border: `1px solid ${pal ? `color-mix(in oklab, ${pal.solid} 25%, var(--line))` : "var(--line)"}`,
    borderRadius: 9, cursor: "pointer", fontFamily: "inherit",
    color: pal ? pal.ink : "var(--ink-2)", fontSize: 14, fontWeight: 540,
    transition: "background .15s, border-color .15s, color .15s",
  };
  return (
    <div
      onDragOver={(e) => {
        if (!draggingId) return;
        e.preventDefault();
        e.dataTransfer.dropEffect = "move";
        if (!isDragOver) setIsDragOver(true);
      }}
      onDragLeave={(e) => {
        // ne réagit qu'aux sorties vraies (relatedTarget hors du bloc)
        if (e.currentTarget.contains(e.relatedTarget)) return;
        setIsDragOver(false);
      }}
      onDrop={(e) => {
        e.preventDefault();
        const id = e.dataTransfer.getData("text/plain");
        setIsDragOver(false);
        if (id && onDropService) onDropService(id, targetCatId);
      }}
      style={{
        background: pal ? pal.tint : "var(--bg-alt)",
        border: isDragOver
          ? `2px dashed ${accentColor}`
          : "1px solid " + (pal ? `color-mix(in oklab, ${pal.solid} 22%, var(--line))` : "var(--line)"),
        borderRadius: 18, padding: isDragOver ? "17px 19px" : "18px 20px",
        boxShadow: isDragOver ? `0 0 0 4px color-mix(in oklab, ${accentColor} 14%, transparent)` : "none",
        transition: "border-color .15s, box-shadow .15s",
      }}>
      {/* En-tête de catégorie */}
      <div style={{
        display: "flex", alignItems: "center", gap: 10,
        marginBottom: 14, paddingBottom: 12,
        borderBottom: `1px solid ${pal ? `color-mix(in oklab, ${pal.solid} 18%, var(--line))` : "var(--line)"}`,
        flexWrap: "wrap",
      }}>
        <span style={{
          width: 5, height: 22, borderRadius: 2,
          background: pal ? pal.solid : "var(--ink-4)",
          flexShrink: 0,
        }}/>
        <span style={{
          fontFamily: "var(--ff-display)", fontSize: 17.5, fontWeight: 580,
          color: pal ? pal.ink : "var(--ink-3)", letterSpacing: "-0.015em",
          fontStyle: isNone ? "italic" : "normal",
        }}>
          {isNone ? "Sans catégorie" : category.name}
        </span>
        <span style={{
          fontSize: 11, color: pal ? pal.ink : "var(--ink-4)", fontWeight: 600,
          padding: "3px 9px",
          background: pal ? `color-mix(in oklab, ${pal.solid} 14%, var(--surface))` : "var(--surface)",
          border: `1px solid ${pal ? `color-mix(in oklab, ${pal.solid} 25%, var(--line))` : "var(--line)"}`,
          borderRadius: 999,
        }}>
          {items.length} prestation{items.length > 1 ? "s" : ""}
        </span>

        {/* Actions catégorie (sauf "Sans catégorie"), plus grandes sur desktop */}
        {!isNone && (
          <div style={{ marginLeft: "auto", display: "flex", gap: 6 }}>
            <button onClick={() => actions.moveServiceCategory(category.id, "up")}
              disabled={isFirst} title="Monter"
              style={{ ...catActionBtn, opacity: isFirst ? 0.4 : 1, cursor: isFirst ? "not-allowed" : "pointer" }}>
              ↑
            </button>
            <button onClick={() => actions.moveServiceCategory(category.id, "down")}
              disabled={isLast} title="Descendre"
              style={{ ...catActionBtn, opacity: isLast ? 0.4 : 1, cursor: isLast ? "not-allowed" : "pointer" }}>
              ↓
            </button>
            <button onClick={() => onEditCat(category)} title="Modifier"
              style={catActionBtn}>
              <Icon name="settings" size={14}/>
            </button>
            <button onClick={() => actions.deleteServiceCategory(category.id)} title="Supprimer la catégorie"
              style={{ ...catActionBtn, color: "oklch(55% 0.18 25)", borderColor: "oklch(86% 0.06 25)" }}>
              <Icon name="trash" size={14}/>
            </button>
          </div>
        )}
      </div>

      {/* Description optionnelle */}
      {!isNone && category.description && (
        <div style={{
          marginBottom: 12, padding: "8px 12px",
          background: pal ? `color-mix(in oklab, ${pal.solid} 5%, var(--surface))` : "var(--surface)",
          border: `1px solid ${pal ? `color-mix(in oklab, ${pal.solid} 14%, var(--line))` : "var(--line)"}`,
          borderRadius: 9,
          fontSize: 12.5, color: pal ? pal.ink : "var(--ink-2)", lineHeight: 1.45,
        }}>
          {category.description}
        </div>
      )}

      {/* Prestations de cette catégorie, grille responsive 2 cols desktop, 1 col mobile */}
      <div style={{
        display: "grid",
        gridTemplateColumns: "repeat(auto-fill, minmax(min(340px, 100%), 1fr))",
        gap: 8,
      }}>
        {items.map((s, idx) => {
          const active = s.active !== false;
          const isFirstInCat = idx === 0;
          const isLastInCat = idx === items.length - 1;
          return (
            <div key={s.id}
              draggable={true}
              onDragStart={(e) => {
                e.dataTransfer.setData("text/plain", s.id);
                e.dataTransfer.effectAllowed = "move";
                if (setDraggingId) setDraggingId(s.id);
              }}
              onDragEnd={() => setDraggingId && setDraggingId(null)}
              style={{
                padding: "12px 14px",
                background: "var(--surface)",
                border: `1px solid var(--line)`,
                borderLeft: active
                  ? `3px solid ${pal ? pal.solid : "var(--accent)"}`
                  : `3px solid var(--ink-4)`,
                borderRadius: 10,
                opacity: draggingId === s.id ? 0.4 : (active ? 1 : 0.62),
                display: "flex", gap: 10, alignItems: "center", flexWrap: "wrap",
                transition: "box-shadow .15s, opacity .15s",
                cursor: "grab",
              }}
              onMouseEnter={e => { e.currentTarget.style.boxShadow = "0 4px 12px -6px rgba(15,18,30,0.12)"; }}
              onMouseLeave={e => { e.currentTarget.style.boxShadow = "none"; }}>
              {/* Poignée drag, glisser pour déplacer dans une autre catégorie */}
              <div title="Glisser pour déplacer dans une autre catégorie" aria-label="Déplacer"
                style={{
                  display: "inline-flex", alignItems: "center", justifyContent: "center",
                  width: 22, height: 32, flexShrink: 0,
                  color: "var(--ink-4)", cursor: "grab", userSelect: "none",
                  fontSize: 16, lineHeight: 1, letterSpacing: "-2px",
                  borderRadius: 6,
                  transition: "background .15s, color .15s",
                }}
                onMouseEnter={e => { e.currentTarget.style.background = "var(--bg-alt)"; e.currentTarget.style.color = "var(--ink-2)"; }}
                onMouseLeave={e => { e.currentTarget.style.background = "transparent"; e.currentTarget.style.color = "var(--ink-4)"; }}
                aria-hidden>
                ⠿
              </div>

              <div style={{ flex: 1, minWidth: 180 }}>
                <div style={{ display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
                  <span style={{ fontSize: 14, fontWeight: 580, color: "var(--ink)" }}>{s.name}</span>
                  {!active && (
                    <span style={{
                      padding: "2px 8px", background: "var(--bg-alt)",
                      border: "1px solid var(--line)", borderRadius: 999,
                      fontSize: 10.5, fontWeight: 600, color: "var(--ink-3)",
                    }}>Masquée</span>
                  )}
                </div>
                <div style={{ fontSize: 12.5, color: "var(--ink-3)", marginTop: 2, display: "flex", gap: 12, flexWrap: "wrap" }}>
                  <span>{fmtDuration(s.duration)}</span>
                  <span style={{ fontWeight: 580, color: pal ? pal.ink : "var(--accent-ink)" }}>
                    {s.price
                      ? (s.priceFrom ? `À partir de ${s.price} €` : `${s.price} €`)
                      : "Gratuit"}
                  </span>
                </div>
                {s.description && (
                  <div style={{ fontSize: 12.5, color: "var(--ink-3)", marginTop: 4, lineHeight: 1.45 }}>
                    {s.description}
                  </div>
                )}
              </div>
              <div className="cb-presta-actions" style={{ display: "flex", gap: 4, alignItems: "center", flexWrap: "wrap" }}>
                {/* Sélecteur de catégorie, visible uniquement sur mobile (desktop = drag) */}
                {categories.length > 0 && (
                  <select
                    className="cb-presta-cat-select"
                    value={s.categoryId || ""}
                    onChange={(e) => actions.updateService(s.id, { categoryId: e.target.value || null })}
                    onClick={(e) => e.stopPropagation()}
                    title="Changer de catégorie"
                    style={{
                      height: 32, padding: "0 10px", fontSize: 13,
                      background: "var(--bg-alt)", border: "1px solid var(--line)",
                      borderRadius: 8, color: "var(--ink-2)", fontFamily: "inherit",
                      cursor: "pointer",
                    }}>
                    <option value="">, Sans catégorie —</option>
                    {categories.map(c => (
                      <option key={c.id} value={c.id}>{c.name}</option>
                    ))}
                  </select>
                )}
                <button onClick={() => actions.toggleServiceActive(s.id)} title={active ? "Masquer" : "Afficher"}
                  className="btn btn-sm btn-ghost" style={{ height: 28, fontSize: 11.5 }}>
                  {active ? "Masquer" : "Afficher"}
                </button>
                <button onClick={() => startEdit(s)} title="Modifier"
                  className="btn btn-sm btn-ghost" style={{ height: 28, width: 28, padding: 0 }}>
                  <Icon name="settings" size={12}/>
                </button>
                <button onClick={() => actions.deleteService(s.id)} title="Supprimer"
                  className="btn btn-sm btn-ghost" style={{ height: 28, width: 28, padding: 0, color: "oklch(55% 0.18 25)" }}>
                  <Icon name="trash" size={12}/>
                </button>
              </div>
            </div>
          );
        })}
        {/* Bouton "ajouter dans cette catégorie", span pleine largeur de la grille */}
        {!isNone && (
          <button onClick={() => startNewIn(category.id)}
            style={{
              gridColumn: "1 / -1",
              padding: "10px 12px", background: "transparent",
              border: "1px dashed var(--line-strong)", borderRadius: 9,
              fontSize: 12.5, color: "var(--ink-4)", cursor: "pointer", fontFamily: "inherit",
              display: "flex", alignItems: "center", justifyContent: "center", gap: 6,
              transition: "color .15s, border-color .15s",
            }}
            onMouseEnter={e => { e.currentTarget.style.color = "var(--accent-ink)"; e.currentTarget.style.borderColor = "var(--accent)"; }}
            onMouseLeave={e => { e.currentTarget.style.color = "var(--ink-4)"; e.currentTarget.style.borderColor = "var(--line-strong)"; }}>
            <Icon name="plus" size={12}/> Ajouter une prestation dans « {category.name} »
          </button>
        )}
      </div>
    </div>
  );
};

const ServiceField = ({ label, value, onChange, placeholder, type = "text", step }) => (
  <div>
    <label style={{ fontSize: 12.5, color: "var(--ink-3)", display: "block", marginBottom: 4 }}>{label}</label>
    <input type={type} step={step} value={value} onChange={e => onChange(e.target.value)} placeholder={placeholder}
      style={fieldInputStyle}/>
  </div>
);

const AppBookings = ({ data, actions, setMod }) => {
  const [slug, setSlug] = React.useState(() => cbAuth.ensureBookingSlug() || "");
  const [editingSlug, setEditingSlug] = React.useState(false);
  const [draftSlug, setDraftSlug] = React.useState(slug);
  const [copied, setCopied] = React.useState(false);
  const [newVac, setNewVac] = React.useState({ start: "", end: "", reason: "" });
  const bs = data.bookingSettings || {};
  const schedule = resolveSchedule(bs);
  const vacations = bs.vacations || [];
  const url = `${window.location.origin}/?book=${slug}`;
  // Gate : la page publique est inactive tant que le pro n'a pas validé sa
  // politique de réservation. On le détecte via business.policyConfiguredAt
  // (posé par RPC mark_policy_configured depuis BookingPolicyEditor).
  const policyConfigured = !!(data.business && data.business.policyConfiguredAt);
  const goToPolicy = () => {
    if (typeof window !== "undefined") window.cbInitialSettingsTab = "booking";
    if (setMod) setMod("settings");
  };

  const saveSlug = () => {
    const newSlug = cbAuth.updateBookingSlug(draftSlug);
    setSlug(newSlug);
    setEditingSlug(false);
    showToast("Lien mis à jour");
  };

  const copy = async () => {
    try {
      if (navigator.clipboard) await navigator.clipboard.writeText(url);
      else {
        const ta = document.createElement("textarea");
        ta.value = url; document.body.appendChild(ta); ta.select();
        document.execCommand("copy"); document.body.removeChild(ta);
      }
      setCopied(true);
      setTimeout(() => setCopied(false), 1800);
      showToast("Lien copié");
    } catch (e) {
      showToast("Impossible de copier, sélectionnez à la main", "warn");
    }
  };

  const updateDay = (isoIdx, patch) => {
    const next = { ...schedule, [isoIdx]: { ...schedule[isoIdx], ...patch } };
    actions.updateBookingSettings({ schedule: next });
  };

  const addVacation = () => {
    if (!newVac.start || !newVac.end) {
      showToast("Date de début et fin requises", "warn");
      return;
    }
    if (newVac.end < newVac.start) {
      showToast("La date de fin doit être après la date de début", "warn");
      return;
    }
    const v = {
      id: "v_" + Math.random().toString(36).slice(2, 10),
      start: newVac.start, end: newVac.end,
      reason: newVac.reason.trim() || "Fermeture",
    };
    actions.updateBookingSettings({ vacations: [...vacations, v] });
    setNewVac({ start: "", end: "", reason: "" });
    showToast("Période ajoutée");
  };

  const removeVacation = (id) => {
    actions.updateBookingSettings({ vacations: vacations.filter(v => v.id !== id) });
  };

  // Compteur RDV reçus via le lien public
  const publicBookingsCount = (data.appointments || []).filter(a => a.publicBooking).length;

  const slugBtnLight = {
    padding: "5px 11px", fontSize: 12, fontWeight: 600, fontFamily: "inherit",
    background: "#fff", color: "var(--accent)", border: "none",
    borderRadius: 7, cursor: "pointer",
  };
  const slugBtnGhost = {
    padding: "5px 11px", fontSize: 12, fontWeight: 600, fontFamily: "inherit",
    background: "rgba(255,255,255,0.18)", color: "#fff",
    border: "1px solid rgba(255,255,255,0.3)", borderRadius: 7, cursor: "pointer",
  };

  return (
    <div>
      {/* Bandeau d'alerte : tant que la politique de réservation n'est pas
          validée, le pro voit cette alerte EN HAUT et le hero (lien public)
          est flouté + cliquable pour rappeler qu'il faut passer par la
          configuration avant d'activer la page de RDV publique. */}
      {!policyConfigured && (
        <div role="alert" className="cb-policy-alert" style={{
          padding: "14px 16px", marginBottom: 14,
          background: "var(--warn-soft)", border: "1px solid var(--warn-soft-2)",
          borderRadius: 12,
        }}>
          {/* Layout responsive : sur desktop, l'icône + le bloc texte + le
              bouton sont sur 1 ligne. Sur mobile (<640px), tout s'empile :
              icône + texte en colonne, puis bouton full-width en-dessous. */}
          <style>{`
            .cb-policy-alert {
              display: grid;
              grid-template-columns: 36px 1fr auto;
              align-items: center;
              gap: 12px 14px;
            }
            .cb-policy-alert-btn {
              white-space: nowrap;
            }
            @media (max-width: 640px) {
              .cb-policy-alert {
                grid-template-columns: 36px 1fr;
                grid-template-areas:
                  "icon text"
                  "cta  cta";
                gap: 10px;
                padding: 12px 14px;
              }
              .cb-policy-alert-icon { grid-area: icon; }
              .cb-policy-alert-text { grid-area: text; }
              .cb-policy-alert-btn  {
                grid-area: cta;
                width: 100% !important;
                margin-top: 4px;
              }
              .cb-policy-alert-title { font-size: 13.5px !important; }
              .cb-policy-alert-sub   { font-size: 12px !important; }
            }
          `}</style>
          <span className="cb-policy-alert-icon" style={{
            width: 36, height: 36, borderRadius: 10, flexShrink: 0,
            background: "var(--warn-ink)", color: "#fff",
            display: "inline-flex", alignItems: "center", justifyContent: "center",
            fontSize: 18, fontWeight: 700,
          }}>!</span>
          <div className="cb-policy-alert-text" style={{ minWidth: 0 }}>
            <div className="cb-policy-alert-title"
              style={{ fontSize: 14, fontWeight: 600, color: "var(--warn-ink)", letterSpacing: "-0.01em", lineHeight: 1.3 }}>
              Politiques de réservation à configurer
            </div>
            <div className="cb-policy-alert-sub"
              style={{ fontSize: 12.5, color: "var(--ink-2)", marginTop: 4, lineHeight: 1.5 }}>
              Avant d'activer votre lien public, renseignez vos délais d'annulation et de modification ainsi que vos règles générales.
            </div>
          </div>
          <button onClick={goToPolicy}
            className="btn btn-accent btn-md cb-press-feedback cb-policy-alert-btn">
            Configurer →
          </button>
        </div>
      )}

      {/* ===== HERO : le lien public mis en avant, version compacte =====
          Le bouton Aperçu est remonté en haut à droite (pill compacte) pour réduire la hauteur du bloc.
          Si policy non validée : flou + interactions désactivées (gate). */}
      <div style={{
        position: "relative", overflow: "hidden",
        background: "linear-gradient(135deg, var(--accent) 0%, oklch(48% 0.20 300) 100%)",
        borderRadius: 20, padding: "20px 22px", marginBottom: 18, color: "#fff",
        filter: policyConfigured ? "none" : "blur(4px) saturate(0.6)",
        pointerEvents: policyConfigured ? "auto" : "none",
        userSelect: policyConfigured ? "auto" : "none",
        transition: "filter .25s ease",
      }}>
        <div aria-hidden style={{ position: "absolute", top: -60, right: -30, width: 170, height: 170, borderRadius: "50%", background: "rgba(255,255,255,0.10)" }}/>
        <div aria-hidden style={{ position: "absolute", bottom: -80, right: 120, width: 130, height: 130, borderRadius: "50%", background: "rgba(255,255,255,0.06)" }}/>
        <div style={{ position: "relative" }}>
          {/* Bouton Aperçu : pill compacte épinglée en haut à droite */}
          <a href={url} target="_blank" rel="noopener noreferrer" style={{
            position: "absolute", top: 0, right: 0,
            display: "inline-flex", alignItems: "center", gap: 5,
            padding: "5px 11px", background: "rgba(255,255,255,0.18)",
            color: "#fff", border: "1px solid rgba(255,255,255,0.28)",
            borderRadius: 999, fontSize: 12, fontWeight: 600, textDecoration: "none",
            backdropFilter: "blur(6px)", WebkitBackdropFilter: "blur(6px)",
          }}>
            <Icon name="external" size={11}/> Aperçu
          </a>

          <div style={{ display: "flex", alignItems: "flex-start", justifyContent: "space-between", gap: 16, flexWrap: "wrap", paddingRight: 88 }}>
            <div style={{ minWidth: 0 }}>
              <h3 style={{ margin: 0, color: "#fff", fontFamily: "var(--ff-display)", fontSize: "clamp(19px, 2.4vw, 24px)", fontWeight: 580, letterSpacing: "-0.025em" }}>
                Votre page de réservation
              </h3>
              <p style={{ margin: "5px 0 0", color: "#fff", fontSize: 13, opacity: 0.9, lineHeight: 1.5, maxWidth: 440 }}>
                Partagez ce lien, vos clientes réservent en autonomie, 24h/24.
                {publicBookingsCount > 0 && (
                  <> · <strong style={{ fontWeight: 600 }}>{publicBookingsCount}</strong> RDV reçus</>
                )}
              </p>
            </div>
          </div>

          {/* URL row */}
          <div style={{
            display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap",
            marginTop: 14, padding: "9px 13px",
            background: "rgba(255,255,255,0.16)", border: "1px solid rgba(255,255,255,0.25)",
            borderRadius: 12,
          }}>
            <Icon name="link" size={14} style={{ flexShrink: 0, opacity: 0.9 }}/>
            <span style={{ flex: 1, fontSize: 13, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", minWidth: 100 }}>{url}</span>
            <button onClick={copy} className="cb-press-feedback" style={{
              display: "inline-flex", alignItems: "center", gap: 6,
              padding: "6px 12px", background: "#fff",
              color: copied ? "var(--sage)" : "var(--accent)",
              border: "none", borderRadius: 8, cursor: "pointer", fontFamily: "inherit",
              fontSize: 12.5, fontWeight: 600, flexShrink: 0,
            }}>
              <Icon name={copied ? "check" : "copy"} size={12}/> {copied ? "Copié" : "Copier"}
            </button>
          </div>

          {/* Ligne identifiant */}
          <div style={{ marginTop: 10, display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap", fontSize: 12.5 }}>
            <span style={{ opacity: 0.85 }}>Identifiant personnalisé&nbsp;:</span>
            {editingSlug ? (
              <>
                <input value={draftSlug} onChange={e => setDraftSlug(e.target.value)} style={{
                  padding: "5px 10px", fontSize: 13,
                  background: "rgba(255,255,255,0.92)", border: "none",
                  borderRadius: 6, color: "var(--ink)", outline: "none", minWidth: 180,
                  fontFamily: "inherit",
                }}/>
                <button onClick={saveSlug} style={slugBtnLight}>Enregistrer</button>
                <button onClick={() => { setEditingSlug(false); setDraftSlug(slug); }} style={slugBtnGhost}>Annuler</button>
              </>
            ) : (
              <>
                <span style={{ padding: "3px 10px", background: "rgba(255,255,255,0.2)", borderRadius: 6, fontWeight: 600 }}>{slug}</span>
                <button onClick={() => { setDraftSlug(slug); setEditingSlug(true); }} style={slugBtnGhost}>Modifier</button>
              </>
            )}
          </div>
        </div>
      </div>

      {/* Layout 2 colonnes desktop : horaires à gauche, fermetures + réglages à droite */}
      <div className="app-split" style={{
        display: "grid", gridTemplateColumns: "1fr minmax(0, 384px)", gap: 18, alignItems: "start",
      }}>
      {/* ===== Colonne gauche : horaires ===== */}
      <div style={{ display: "flex", flexDirection: "column", gap: 18 }}>

      {/* Per-day schedule, chaque jour tient sur une seule ligne, plus aéré */}
      <div style={{
        padding: 20, background: "var(--surface)",
        border: "1px solid var(--line)", borderRadius: 14,
      }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 16, flexWrap: "wrap", gap: 10 }}>
          <div>
            <div style={{ fontSize: 17, fontWeight: 600, letterSpacing: "-0.012em" }}>Vos horaires</div>
            <div style={{ fontSize: 13, color: "var(--ink-4)", marginTop: 3 }}>
              Définissez quand vous acceptez les réservations en ligne.
            </div>
          </div>
          <label style={{
            display: "inline-flex", alignItems: "center", gap: 9,
            padding: "7px 12px", background: "var(--bg-alt)",
            border: "1px solid var(--line)", borderRadius: 10,
            fontSize: 13.5, color: "var(--ink-3)",
          }}>
            Créneaux toutes les
            <select value={bs.slotDuration || 30} onChange={e => actions.updateBookingSettings({ slotDuration: +e.target.value })}
              style={{
                padding: "4px 8px", fontSize: 13.5, fontFamily: "inherit",
                background: "var(--surface)", border: "1px solid var(--line)",
                borderRadius: 7, color: "var(--ink)", outline: "none",
              }}>
              {[15, 30, 45, 60].map(m => <option key={m} value={m}>{m} min</option>)}
            </select>
          </label>
        </div>

        <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
          {[1, 2, 3, 4, 5, 6, 7].map(isoIdx => {
            const day = schedule[isoIdx] || { open: false, start: 9, end: 18, break: false, breakStart: 12.5, breakEnd: 13.5 };
            const hourSelectStyle = {
              padding: "6px 10px", fontSize: 14, fontFamily: "inherit",
              background: "var(--surface)", border: "1px solid var(--line)",
              borderRadius: 8, color: "var(--ink)", outline: "none",
            };
            return (
              <div key={isoIdx} style={{
                padding: "13px 16px",
                background: day.open ? "var(--bg-alt)" : "transparent",
                border: "1px solid var(--line)",
                borderLeft: `3px solid ${day.open ? "var(--accent)" : "var(--line-strong)"}`,
                borderRadius: 11,
                display: "flex", alignItems: "center", gap: 14, flexWrap: "wrap",
                transition: "background .15s, border-color .15s",
              }}>
                <div style={{ minWidth: 100, fontSize: 14.5, fontWeight: 580, color: day.open ? "var(--ink)" : "var(--ink-4)" }}>
                  {DAY_FULL_LABELS[isoIdx - 1]}
                </div>
                <ToggleSwitch checked={day.open} onChange={(v) => updateDay(isoIdx, { open: v })}/>
                {day.open ? (
                  <>
                    <div style={{ display: "flex", alignItems: "center", gap: 7 }}>
                      <select value={day.start} onChange={e => updateDay(isoIdx, { start: +e.target.value })} style={hourSelectStyle}>
                        {Array.from({ length: 28 }, (_, i) => 6 + i * 0.5).map(h => (
                          <option key={h} value={h}>{fmtH(h)}</option>
                        ))}
                      </select>
                      <span style={{ fontSize: 13, color: "var(--ink-4)" }}>→</span>
                      <select value={day.end} onChange={e => updateDay(isoIdx, { end: +e.target.value })} style={hourSelectStyle}>
                        {Array.from({ length: 28 }, (_, i) => 7 + i * 0.5).map(h => (
                          <option key={h} value={h}>{fmtH(h)}</option>
                        ))}
                      </select>
                    </div>
                    <span aria-hidden style={{ width: 1, height: 22, background: "var(--line)" }}/>
                    <label style={{
                      display: "inline-flex", alignItems: "center", gap: 8,
                      cursor: "pointer", color: "var(--ink-2)", fontSize: 13.5,
                    }}>
                      <input type="checkbox" checked={!!day.break}
                        onChange={e => updateDay(isoIdx, { break: e.target.checked })}
                        style={{ accentColor: "var(--accent)", width: 16, height: 16 }}/>
                      <Icon name="clock" size={13} style={{ color: "var(--ink-3)" }}/>
                      Pause
                    </label>
                    {day.break && (
                      <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
                        <select value={day.breakStart || 12.5} onChange={e => updateDay(isoIdx, { breakStart: +e.target.value })} style={hourSelectStyle}>
                          {Array.from({ length: 16 }, (_, i) => 10 + i * 0.5).map(h => (
                            <option key={h} value={h}>{fmtH(h)}</option>
                          ))}
                        </select>
                        <span style={{ fontSize: 13, color: "var(--ink-4)" }}>→</span>
                        <select value={day.breakEnd || 13.5} onChange={e => updateDay(isoIdx, { breakEnd: +e.target.value })} style={hourSelectStyle}>
                          {Array.from({ length: 16 }, (_, i) => 11 + i * 0.5).map(h => (
                            <option key={h} value={h}>{fmtH(h)}</option>
                          ))}
                        </select>
                      </div>
                    )}
                  </>
                ) : (
                  <span style={{ fontSize: 13.5, color: "var(--ink-4)", fontStyle: "italic" }}>Fermé ce jour</span>
                )}
              </div>
            );
          })}
        </div>
      </div>

      </div>{/* ===== fin colonne gauche : horaires ===== */}

      {/* ===== Colonne droite : fermetures + réglages ===== */}
      <div style={{ display: "flex", flexDirection: "column", gap: 18 }}>

      {/* Vacations / closures */}
      <div style={{
        padding: 20, background: "var(--surface)",
        border: "1px solid var(--line)", borderRadius: 14,
      }}>
        <div style={{ fontSize: 15, fontWeight: 520, marginBottom: 6 }}>Périodes de fermeture</div>
        <div style={{ fontSize: 13, color: "var(--ink-3)", marginBottom: 14, lineHeight: 1.5 }}>
          Vacances, congés, événements exceptionnels. Ces périodes sont visibles par vos clients sur la page de réservation, elles ne pourront pas réserver dessus.
        </div>

        {vacations.length > 0 && (
          <div style={{ display: "flex", flexDirection: "column", gap: 8, marginBottom: 14 }}>
            {vacations.map(v => (
              <div key={v.id} style={{
                display: "flex", alignItems: "center", gap: 12,
                padding: "10px 14px", background: "var(--bg-alt)",
                border: "1px solid var(--line)", borderRadius: 10,
              }}>
                <div style={{
                  width: 32, height: 32, borderRadius: 8,
                  background: "var(--warn-soft)", color: "var(--warn-ink)",
                  display: "flex", alignItems: "center", justifyContent: "center",
                  flexShrink: 0,
                }}>
                  <Icon name="calendar" size={15}/>
                </div>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ fontSize: 13.5, fontWeight: 520, color: "var(--ink)" }}>{v.reason}</div>
                  <div style={{ fontSize: 12, color: "var(--ink-4)", marginTop: 1 }}>
                    Du {new Date(v.start).toLocaleDateString("fr-FR")} au {new Date(v.end).toLocaleDateString("fr-FR")}
                  </div>
                </div>
                <button onClick={() => removeVacation(v.id)} aria-label="Supprimer" className="btn btn-sm btn-ghost" style={{ height: 30, width: 30, padding: 0, color: "oklch(55% 0.18 25)" }}>
                  <Icon name="close" size={13}/>
                </button>
              </div>
            ))}
          </div>
        )}

        {/* Formulaire compact, une seule ligne fluide */}
        <div style={{
          padding: 12, background: "var(--bg-alt)",
          border: "1px dashed var(--line-strong)", borderRadius: 10,
          display: "flex", gap: 8, flexWrap: "wrap", alignItems: "flex-end",
        }} className="app-split">
          <div style={{ flex: "1 1 130px", minWidth: 0 }}>
            <label style={{ fontSize: 11.5, color: "var(--ink-4)", display: "block", marginBottom: 3 }}>Du</label>
            <input type="date" value={newVac.start} onChange={e => setNewVac({ ...newVac, start: e.target.value })}
              style={{ ...fieldInputStyle, padding: "8px 10px", fontSize: 13 }}/>
          </div>
          <div style={{ flex: "1 1 130px", minWidth: 0 }}>
            <label style={{ fontSize: 11.5, color: "var(--ink-4)", display: "block", marginBottom: 3 }}>Au</label>
            <input type="date" value={newVac.end} onChange={e => setNewVac({ ...newVac, end: e.target.value })}
              style={{ ...fieldInputStyle, padding: "8px 10px", fontSize: 13 }}/>
          </div>
          <div style={{ flex: "2 1 180px", minWidth: 0 }}>
            <label style={{ fontSize: 11.5, color: "var(--ink-4)", display: "block", marginBottom: 3 }}>Motif (optionnel)</label>
            <input type="text" value={newVac.reason} onChange={e => setNewVac({ ...newVac, reason: e.target.value })}
              placeholder="Vacances d'été, formation…"
              style={{ ...fieldInputStyle, padding: "8px 10px", fontSize: 13 }}/>
          </div>
          <button onClick={addVacation} className="btn btn-primary btn-sm cb-press-feedback" style={{ flexShrink: 0 }}>
            <Icon name="plus" size={13}/> Ajouter
          </button>
        </div>
      </div>

      {/* Lien vers Ma page, le réseau social s'y configure désormais */}
      <div style={{
        padding: "14px 16px", background: "var(--bg-alt)",
        border: "1px dashed var(--line-strong)", borderRadius: 12,
        fontSize: 12.5, color: "var(--ink-3)", lineHeight: 1.5,
        display: "flex", alignItems: "flex-start", gap: 10,
      }}>
        <Icon name="eye" size={15} style={{ color: "var(--accent-ink)", flexShrink: 0, marginTop: 1 }}/>
        <span>
          Le <strong style={{ color: "var(--ink-2)" }}>réseau social demandé à la réservation</strong> se
          règle maintenant depuis <strong style={{ color: "var(--ink-2)" }}>Ma page</strong>, section « Sections & contacts ».
        </span>
      </div>

      {/* Beta disclaimer, caché en mobile pour alléger */}
      <div className="cb-bookings-note" style={{
        padding: "10px 14px", background: "var(--bg-alt)",
        border: "1px dashed var(--line-strong)", borderRadius: 10,
        fontSize: 12, color: "var(--ink-3)", lineHeight: 1.5,
      }}>
        <strong style={{ color: "var(--ink-2)" }}>Note bêta&nbsp;:</strong> pendant la bêta, les réservations fonctionnent sur le même navigateur que vous. La synchro serveur arrive au lancement officiel.
      </div>

      </div>{/* ===== fin colonne droite ===== */}
      </div>{/* ===== fin layout 2 colonnes ===== */}
    </div>
  );
};

const ToggleSwitch = ({ checked, onChange }) => (
  <button onClick={() => onChange(!checked)} aria-pressed={checked} style={{
    width: 44, height: 24, borderRadius: 999, border: "none",
    background: checked ? "var(--accent)" : "var(--line-strong)",
    cursor: "pointer", padding: 2, flexShrink: 0,
    transition: "background 0.15s",
  }}>
    <div style={{
      width: 20, height: 20, borderRadius: "50%", background: "white",
      boxShadow: "var(--sh-1)",
      transform: checked ? "translateX(20px)" : "translateX(0)",
      transition: "transform 0.15s",
    }}/>
  </button>
);

/* ================================================================
   SETTINGS
================================================================ */
// Tons des sections de paramètres, alignés sur CB_TONE pour la cohérence
// avec la sidebar. Activité réutilise l'indigo de "Mon activité" sidebar ;
// Compte réutilise la lavande de "Mon compte".
const SETTINGS_TONES = {
  business: { ...CB_TONE.activity, icon: "sparkle" }, // indigo (= sidebar Mon activité)
  legal:    { ...CB_TONE.legal,    icon: "shield"  }, // bleu
  account:  { ...CB_TONE.account,  icon: "users"   }, // lavande (= sidebar Mon compte)
  contact:  { ...CB_TONE.contact,  icon: "mail"    }, // teal
  data:     { ...CB_TONE.data,     icon: "box"     }, // peach
};


const SettingsSection = ({ title, subtitle, tone, children, style, accordion, open, onToggle, anchorId }) => {
  const t = SETTINGS_TONES[tone] || SETTINGS_TONES.business;

  // Mode accordéon (mobile) : carte blanche neutre, accent uniquement quand ouvert.
  if (accordion) {
    return (
      <div id={anchorId} style={{
        marginBottom: 10,
        scrollMarginTop: 12,
        background: "var(--surface)",
        border: `1px solid ${open ? (t.dot || "var(--accent-soft-2)") : "var(--line)"}`,
        borderRadius: 14, overflow: "hidden",
        transition: "border-color .25s ease, box-shadow .25s ease",
        boxShadow: open ? "0 8px 22px -12px rgba(15,18,30,0.14)" : "none",
      }}>
        <button onClick={onToggle} style={{
          width: "100%", padding: "14px 14px",
          display: "flex", alignItems: "center", gap: 12,
          background: "transparent",
          border: "none", cursor: "pointer", fontFamily: "inherit", textAlign: "left",
        }}>
          <div style={{
            width: 38, height: 38, borderRadius: 11, flexShrink: 0,
            background: open ? (t.dot || "var(--accent)") : t.bg,
            color: open ? "white" : t.ink,
            display: "flex", alignItems: "center", justifyContent: "center",
            transition: "background .25s, color .25s",
            boxShadow: open ? `0 4px 10px -3px ${t.dot || "var(--accent)"}` : "none",
          }}>
            <Icon name={t.icon} size={16}/>
          </div>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontSize: 14.5, fontWeight: 600, color: "var(--ink)", letterSpacing: "-0.01em" }}>
              {title}
            </div>
            {subtitle && (
              <div style={{ fontSize: 12, color: "var(--ink-4)", marginTop: 2 }}>
                {subtitle}
              </div>
            )}
          </div>
          <div style={{
            width: 28, height: 28, borderRadius: 8, flexShrink: 0,
            color: "var(--ink-4)",
            display: "flex", alignItems: "center", justifyContent: "center",
            transition: "transform .3s cubic-bezier(0.22, 1, 0.36, 1)",
            transform: open ? "rotate(180deg)" : "rotate(0)",
          }}>
            <Icon name="arrow" size={14} style={{ transform: "rotate(90deg)" }}/>
          </div>
        </button>
        <div style={{
          display: "grid",
          gridTemplateRows: open ? "1fr" : "0fr",
          opacity: open ? 1 : 0,
          transition: "grid-template-rows .32s cubic-bezier(0.22, 1, 0.36, 1), opacity .25s ease",
        }}>
          <div style={{ overflow: "hidden" }}>
            <div style={{
              padding: "4px 16px 18px",
              borderTop: "1px solid var(--line)",
              marginTop: 4,
              display: "flex", flexDirection: "column", gap: 14,
              ...style,
            }}>{children}</div>
          </div>
        </div>
      </div>
    );
  }

  // Mode desktop classique : carte surface avec header intégré.
  return (
    <div style={{
      marginBottom: 20,
      background: "var(--surface)", border: "1px solid var(--line)",
      borderRadius: 18, overflow: "hidden",
      borderTop: `3px solid ${t.dot || t.ink || "var(--accent)"}`,
      boxShadow: "0 1px 3px rgba(15,18,30,0.04)",
    }}>
      {/* Header de section intégré, avec une fine bande colorée */}
      <div style={{
        display: "flex", alignItems: "center", gap: 14,
        padding: "18px 24px",
        borderBottom: "1px solid var(--line)",
        background: `color-mix(in oklab, ${t.bg} 35%, var(--surface))`,
      }}>
        <div style={{
          width: 40, height: 40, borderRadius: 12,
          background: t.dot || t.ink, color: "#fff",
          display: "flex", alignItems: "center", justifyContent: "center",
          flexShrink: 0,
          boxShadow: `0 6px 14px -5px ${t.dot || t.ink}`,
        }}>
          <Icon name={t.icon} size={17}/>
        </div>
        <div>
          <h3 style={{
            fontSize: 18, fontWeight: 600, letterSpacing: "-0.018em", lineHeight: 1.2,
            fontFamily: "var(--ff-display)",
          }}>{title}</h3>
          {subtitle && <div style={{ fontSize: 13, color: "var(--ink-3)", marginTop: 3 }}>{subtitle}</div>}
        </div>
      </div>
      <div style={{
        padding: 26,
        display: "flex", flexDirection: "column", gap: 16,
        ...style,
      }}>{children}</div>
    </div>
  );
};

/* Avatar partagé, affiche la photo si présente, sinon les initiales avec gradient coloré */
const ProAvatar = ({ url, initials, hue = 30, size = 32, ring = false }) => {
  const ringStyle = ring ? {
    boxShadow: `0 0 0 2px var(--surface), 0 0 0 4px color-mix(in oklab, oklch(70% 0.14 ${hue}) 50%, transparent)`,
  } : {};
  if (url) {
    return (
      <img src={url} alt={initials || ""} style={{
        width: size, height: size, borderRadius: size / 2,
        objectFit: "cover", flexShrink: 0, display: "block",
        ...ringStyle,
      }}/>
    );
  }
  return (
    <div style={{
      width: size, height: size, borderRadius: size / 2,
      background: `linear-gradient(135deg, oklch(80% 0.12 ${hue}), oklch(68% 0.16 ${(hue + 40) % 360}))`,
      color: "white", display: "flex", alignItems: "center", justifyContent: "center",
      fontSize: size * 0.38, fontWeight: 600, flexShrink: 0,
      letterSpacing: "-0.01em",
      boxShadow: `0 4px 10px -4px oklch(70% 0.14 ${hue})`,
      ...ringStyle,
    }}>
      {initials || "?"}
    </div>
  );
};

/* Uploader : lit un fichier, le redimensionne à 256 px, stocke en data URL */
const AvatarUploader = ({ currentUrl, initials, hue, onChange }) => {
  const inputRef = React.useRef(null);
  const [busy, setBusy] = React.useState(false);

  const handleFile = async (file) => {
    if (!file) return;
    if (!file.type.startsWith("image/")) { showToast("Choisissez une image", "warn"); return; }
    if (file.size > 5 * 1024 * 1024) { showToast("Image trop lourde (max 5 Mo)", "warn"); return; }
    setBusy(true);
    try {
      const dataUrl = await resizeImage(file, 256);
      onChange(dataUrl);
      showToast("Photo mise à jour");
    } catch (e) {
      console.error(e);
      showToast("Impossible de lire l'image", "warn");
    } finally {
      setBusy(false);
    }
  };

  return (
    <div style={{
      display: "flex", alignItems: "center", gap: 16,
      padding: "10px 12px", background: "var(--bg-alt)",
      border: "1px solid var(--line)", borderRadius: 12,
    }}>
      <ProAvatar url={currentUrl} initials={initials} hue={hue} size={64} ring/>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ fontSize: 13.5, fontWeight: 520, color: "var(--ink)" }}>
          Photo de profil
        </div>
        <div style={{ fontSize: 12, color: "var(--ink-4)", marginTop: 2, lineHeight: 1.4 }}>
          Apparaît sur votre page publique de réservation et sur vos factures.
        </div>
        <div style={{ display: "flex", gap: 6, marginTop: 8, flexWrap: "wrap" }}>
          <input
            ref={inputRef} type="file" accept="image/*"
            onChange={e => handleFile(e.target.files && e.target.files[0])}
            style={{ display: "none" }}
          />
          <button type="button" className="btn btn-sm btn-ghost"
            disabled={busy} onClick={() => inputRef.current && inputRef.current.click()}>
            <Icon name="sparkle" size={12}/> {busy ? "Envoi…" : (currentUrl ? "Changer" : "Ajouter une photo")}
          </button>
          {currentUrl && (
            <button type="button" className="btn btn-sm btn-ghost"
              onClick={() => { onChange(""); showToast("Photo retirée", "warn"); }}
              style={{ color: "oklch(55% 0.18 25)" }}>
              Retirer
            </button>
          )}
        </div>
      </div>
    </div>
  );
};

/* Resize + encode as base64 data URL. Returns a square ~maxSize portrait. */
const resizeImage = (file, maxSize) => new Promise((resolve, reject) => {
  const reader = new FileReader();
  reader.onerror = reject;
  reader.onload = () => {
    const img = new Image();
    img.onerror = reject;
    img.onload = () => {
      const size = maxSize;
      const s = Math.min(img.width, img.height);
      const sx = (img.width - s) / 2;
      const sy = (img.height - s) / 2;
      const canvas = document.createElement("canvas");
      canvas.width = size; canvas.height = size;
      const ctx = canvas.getContext("2d");
      ctx.drawImage(img, sx, sy, s, s, 0, 0, size, size);
      resolve(canvas.toDataURL("image/jpeg", 0.85));
    };
    img.src = reader.result;
  };
  reader.readAsDataURL(file);
});

/* === Composant : personnalisation des couleurs du menu === */
// Dispositions colorées prêtes-à-l'emploi : 4 palettes (1 par catégorie) qui
// vont bien ensemble. L'utilisateur en choisit une d'un clic, ça applique
// les 4 d'un coup.
// 6 dispositions volontairement très différentes : chacune occupe sa propre
// zone du cercle chromatique (cool / warm / earth / pastel / jewel / mono).
const APPEARANCE_PRESETS = [
  { id: "original",  label: "Original",   palettes: { activity: "indigo",  clients: "rose",   business: "sage",   account: "lavande" } },
  { id: "ocean",     label: "Océan",      palettes: { activity: "bleu",    clients: "teal",   business: "lavande", account: "indigo"  } },
  { id: "terre",     label: "Terre",      palettes: { activity: "ambre",   clients: "orange", business: "sage",   account: "rose"    } },
  { id: "pastel",    label: "Pastel",     palettes: { activity: "rose",    clients: "lavande", business: "sage",  account: "ambre"   } },
  { id: "audacieux", label: "Audacieux",  palettes: { activity: "orange",  clients: "rose",   business: "indigo", account: "teal"    } },
  { id: "contraste", label: "Contraste",  palettes: { activity: "indigo",  clients: "orange", business: "teal",   account: "rose"    } },
];

// Teintes proposées pour la photo de profil (avatar à initiales)
const PROFILE_HUES = [
  { id: 30,  label: "Pêche"   },
  { id: 340, label: "Rose"    },
  { id: 278, label: "Indigo"  },
  { id: 245, label: "Bleu"    },
  { id: 200, label: "Teal"    },
  { id: 160, label: "Sage"    },
  { id: 60,  label: "Ambre"   },
  { id: 290, label: "Lavande" },
];

const AppearanceCustomizer = ({ data, actions }) => {
  const [choices, setChoices] = React.useState(() => {
    try {
      const raw = localStorage.getItem("cb_group_palettes");
      return raw ? JSON.parse(raw) : {};
    } catch { return {}; }
  });
  const [prefs, setPrefs] = React.useState(() => {
    try {
      const raw = localStorage.getItem("cb_display_prefs");
      return raw ? JSON.parse(raw) : { startModule: "accueil" };
    } catch { return { startModule: "accueil" }; }
  });
  const setPref = (key, val) => {
    const next = { ...prefs, [key]: val };
    setPrefs(next);
    try { localStorage.setItem("cb_display_prefs", JSON.stringify(next)); } catch {}
    showToast("Préférence enregistrée");
  };

  const pickPalette = (groupId, paletteId) => {
    const next = { ...choices, [groupId]: paletteId };
    setChoices(next);
    try { localStorage.setItem("cb_group_palettes", JSON.stringify(next)); } catch {}
    try { window.dispatchEvent(new Event("cb-palette-change")); } catch {}
  };

  const applyPreset = (presetId) => {
    const p = APPEARANCE_PRESETS.find(x => x.id === presetId);
    if (!p) return;
    setChoices(p.palettes);
    try { localStorage.setItem("cb_group_palettes", JSON.stringify(p.palettes)); } catch {}
    try { window.dispatchEvent(new Event("cb-palette-change")); } catch {}
    showToast(`Disposition « ${p.label} » appliquée`);
  };

  const resetAll = () => {
    setChoices({});
    try { localStorage.removeItem("cb_group_palettes"); } catch {}
    try { window.dispatchEvent(new Event("cb-palette-change")); } catch {}
    showToast("Couleurs réinitialisées");
  };

  // Quelle preset est actif (toutes les couleurs correspondent) ?
  const activePresetId = (() => {
    for (const p of APPEARANCE_PRESETS) {
      const allMatch = Object.entries(p.palettes).every(([gid, pid]) => (choices[gid] || gid === "activity" && "indigo" || gid === "clients" && "rose" || gid === "business" && "sage" || gid === "account" && "lavande") === pid);
      // Simplification : compare avec defaults
      const matchesPreset = APP_MODULE_GROUPS.every(g => (choices[g.id] || g.defaultPalette) === p.palettes[g.id]);
      if (matchesPreset) return p.id;
    }
    return null;
  })();

  const currentHue = (data && data.business && data.business.hue != null) ? Number(data.business.hue) : 30;
  const setHue = (h) => {
    actions.updateBusiness({ hue: h });
  };

  // Options du module de démarrage
  const startModuleOptions = APP_MODULES.filter(m =>
    ["accueil", "agenda", "clients", "stats", "factures"].includes(m.id)
  );

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 22 }}>
      {/* === 6 dispositions prêtes-à-l'emploi === */}
      <div>
        <div style={{
          fontSize: 11, fontWeight: 600, color: "var(--ink-4)",
          textTransform: "uppercase", letterSpacing: "0.06em", marginBottom: 12,
        }}>
          Dispositions de couleurs
        </div>
        <div style={{ fontSize: 12.5, color: "var(--ink-3)", marginBottom: 14, lineHeight: 1.5 }}>
          Six combinaisons pensées pour aller ensemble. Un clic applique les
          quatre couleurs de catégories d'un coup.
        </div>
        <div style={{
          display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(170px, 1fr))", gap: 12,
        }}>
          {APPEARANCE_PRESETS.map(p => {
            const isActive = activePresetId === p.id;
            // Fond doux du preset : dégradé sur les 2 premières couleurs pour bien caractériser chaque ambiance.
            const pals = APP_MODULE_GROUPS.map(g => CB_GROUP_PALETTES[p.palettes[g.id]]);
            const cardBg = `linear-gradient(135deg, ${pals[0].bg} 0%, ${pals[1].bg} 50%, ${pals[3].bg} 100%)`;
            return (
              <button key={p.id} onClick={() => applyPreset(p.id)}
                className="cb-press-feedback"
                style={{
                  padding: "14px 14px 12px", textAlign: "left",
                  background: isActive ? cardBg : "var(--surface)",
                  border: `1.5px solid ${isActive ? pals[0].solid : "var(--line)"}`,
                  borderRadius: 14, cursor: "pointer", fontFamily: "inherit",
                  display: "flex", flexDirection: "column", gap: 10,
                  boxShadow: isActive ? `0 8px 18px -8px ${pals[0].solid}` : "0 1px 2px rgba(15,18,30,0.04)",
                  transition: "border-color .2s, background .25s, box-shadow .2s",
                  position: "relative", overflow: "hidden",
                }}
                onMouseEnter={e => { if (!isActive) { e.currentTarget.style.background = cardBg; e.currentTarget.style.borderColor = pals[0].solid; } }}
                onMouseLeave={e => { if (!isActive) { e.currentTarget.style.background = "var(--surface)"; e.currentTarget.style.borderColor = "var(--line)"; } }}>
                {/* 4 grandes pastilles colorées (une par catégorie) */}
                <div style={{ display: "flex", gap: 6 }}>
                  {pals.map((pal, i) => (
                    <span key={i} aria-hidden style={{
                      flex: 1, height: 34, borderRadius: 8,
                      background: `linear-gradient(160deg, ${pal.solid}, color-mix(in oklab, ${pal.solid} 75%, black))`,
                      boxShadow: `0 4px 10px -4px ${pal.solid}, inset 0 1px 0 rgba(255,255,255,0.22)`,
                    }}/>
                  ))}
                </div>
                <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 6 }}>
                  <span style={{
                    fontSize: 13.5, fontWeight: 600, letterSpacing: "-0.01em",
                    color: "var(--ink)",
                  }}>
                    {p.label}
                  </span>
                  {isActive && (
                    <span style={{
                      display: "inline-flex", alignItems: "center", gap: 4,
                      padding: "3px 8px", borderRadius: 999,
                      background: pals[0].solid, color: "#fff",
                      fontSize: 10.5, fontWeight: 700,
                    }}>
                      <Icon name="check" size={10} stroke={2.8}/> Actif
                    </span>
                  )}
                </div>
              </button>
            );
          })}
        </div>
      </div>

      {/* === Couleur de la photo de profil === */}
      {data && data.business && (
        <div>
          <div style={{
            fontSize: 11, fontWeight: 600, color: "var(--ink-4)",
            textTransform: "uppercase", letterSpacing: "0.06em", marginBottom: 12,
          }}>
            Couleur de la photo de profil
          </div>
          <div style={{ fontSize: 12.5, color: "var(--ink-3)", marginBottom: 14, lineHeight: 1.5 }}>
            La teinte utilisée pour le fond de votre avatar quand aucune photo n'est définie.
          </div>
          <div style={{ display: "flex", alignItems: "center", gap: 16, flexWrap: "wrap" }}>
            <ProAvatar
              url={data.business.avatar}
              initials={data.business.initials || (data.business.owner || "?").split(" ").map(x => x[0]).slice(0, 2).join("").toUpperCase() || "CB"}
              hue={currentHue} size={64}
            />
            <div style={{ display: "flex", gap: 7, flexWrap: "wrap" }}>
              {PROFILE_HUES.map(h => {
                const isActive = Math.abs(currentHue - h.id) < 5;
                return (
                  <button key={h.id} onClick={() => setHue(h.id)} title={h.label}
                    aria-label={h.label}
                    style={{
                      width: 36, height: 36, borderRadius: 11,
                      background: `linear-gradient(135deg, oklch(80% 0.10 ${h.id}), oklch(60% 0.18 ${h.id}))`,
                      border: isActive ? "2px solid var(--ink)" : "2px solid transparent",
                      boxShadow: isActive
                        ? `0 0 0 2px var(--surface) inset, 0 6px 14px -4px oklch(60% 0.18 ${h.id})`
                        : "var(--sh-1)",
                      cursor: "pointer", padding: 0,
                    }}/>
                );
              })}
            </div>
          </div>
        </div>
      )}

      {/* === Module au démarrage === */}
      <div>
        <div style={{
          fontSize: 11, fontWeight: 600, color: "var(--ink-4)",
          textTransform: "uppercase", letterSpacing: "0.06em", marginBottom: 12,
        }}>
          Démarrage
        </div>
        <div style={{
          display: "flex", alignItems: "center", justifyContent: "space-between",
          gap: 14, padding: "12px 14px", background: "var(--bg-alt)",
          border: "1px solid var(--line)", borderRadius: 11, flexWrap: "wrap",
        }}>
          <div style={{ minWidth: 0 }}>
            <div style={{ fontSize: 13.5, fontWeight: 540, color: "var(--ink)" }}>Module au démarrage</div>
            <div style={{ fontSize: 11.5, color: "var(--ink-4)", marginTop: 1 }}>
              Quelle page s'ouvre quand vous lancez l'app
            </div>
          </div>
          <select value={prefs.startModule}
            onChange={e => setPref("startModule", e.target.value)}
            style={{
              padding: "8px 12px", fontSize: 13, fontFamily: "inherit",
              background: "var(--surface)", border: "1px solid var(--line-strong)",
              borderRadius: 9, color: "var(--ink)", outline: "none",
            }}>
            {startModuleOptions.map(m => (
              <option key={m.id} value={m.id}>{m.label}</option>
            ))}
          </select>
        </div>
      </div>

      {/* === Réglage fin : couleur par catégorie === */}
      <div style={{
        fontSize: 11, fontWeight: 600, color: "var(--ink-4)",
        textTransform: "uppercase", letterSpacing: "0.06em", marginBottom: -6,
      }}>
        Réglage individuel par catégorie
      </div>
      <div style={{ fontSize: 13, color: "var(--ink-3)", lineHeight: 1.55, marginTop: -10 }}>
        Si vous voulez personnaliser une catégorie à part, choisissez directement sa couleur ici.
      </div>

      {APP_MODULE_GROUPS.map(group => {
        const current = choices[group.id] || group.defaultPalette;
        return (
          <div key={group.id} style={{
            padding: 14, background: "var(--bg-alt)",
            border: "1px solid var(--line)", borderRadius: 12,
          }}>
            <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 12 }}>
              <span style={{
                width: 32, height: 32, borderRadius: 9,
                background: CB_GROUP_PALETTES[current].bg,
                color: CB_GROUP_PALETTES[current].ink,
                display: "inline-flex", alignItems: "center", justifyContent: "center",
                flexShrink: 0,
                transition: "background .25s ease, color .25s ease",
              }}>
                <Icon name={group.icon} size={15} stroke={1.8}/>
              </span>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontSize: 14, fontWeight: 600, color: "var(--ink)", letterSpacing: "-0.005em" }}>
                  {group.label}
                </div>
                <div style={{ fontSize: 11.5, color: "var(--ink-4)" }}>
                  {group.ids.length} module{group.ids.length > 1 ? "s" : ""} · couleur actuelle : <strong style={{ color: "var(--ink-3)" }}>{CB_PALETTE_LABELS[current]}</strong>
                </div>
              </div>
            </div>
            <div style={{
              display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(64px, 1fr))",
              gap: 6,
            }}>
              {Object.entries(CB_GROUP_PALETTES).map(([pid, palette]) => {
                const active = current === pid;
                return (
                  <button key={pid} onClick={() => pickPalette(group.id, pid)}
                    title={CB_PALETTE_LABELS[pid]}
                    style={{
                      display: "flex", flexDirection: "column", alignItems: "center", gap: 4,
                      padding: "8px 4px 6px",
                      background: active ? "var(--surface)" : "transparent",
                      border: active ? `1px solid ${palette.solid}` : "1px solid transparent",
                      borderRadius: 9, cursor: "pointer", fontFamily: "inherit",
                      transition: "border-color .2s ease, background .2s ease",
                      boxShadow: active ? `0 4px 10px -4px ${palette.solid}` : "none",
                    }}>
                    <span style={{
                      width: 28, height: 28, borderRadius: 8,
                      background: palette.solid,
                      boxShadow: `0 4px 10px -4px ${palette.solid}`,
                    }}/>
                    <span style={{
                      fontSize: 10.5, fontWeight: active ? 600 : 540,
                      color: active ? palette.ink : "var(--ink-3)",
                    }}>
                      {CB_PALETTE_LABELS[pid]}
                    </span>
                  </button>
                );
              })}
            </div>
          </div>
        );
      })}

      <button onClick={resetAll} style={{
        alignSelf: "flex-start",
        padding: "8px 14px", background: "transparent", color: "var(--ink-3)",
        border: "1px solid var(--line)", borderRadius: 999,
        fontSize: 12.5, fontWeight: 540, cursor: "pointer", fontFamily: "inherit",
      }}>
        ↺ Réinitialiser aux couleurs par défaut
      </button>
    </div>
  );
};

// Éditeur complet politique de réservation : délais (annul/modif) + acomptes
// (% + min + texte) + règles générales (textarea libre) + bouton "Valider".
// Persistance :
//   - délais & règles : actions.updateBusiness (table businesses)
//   - acomptes : window.cbCloud.upsertAcompteSettings (table acompte_settings)
//   - validation : window.cbCloud.markPolicyConfigured (RPC, set policy_configured_at)
const BookingPolicyEditor = ({ business, onChange, onValidated }) => {
  const cancelHours    = business.policyCancelHours || 24;
  const modifyHours    = business.policyModifyHours || 24;
  const maxAdvanceDays = business.policyMaxAdvanceDays == null ? 60 : business.policyMaxAdvanceDays;
  const bufferMin      = business.policyBufferMin == null ? 0 : business.policyBufferMin;
  const acompteType    = business.policyAcompteType || "none"; // none | percent | fixed
  const acompteValue   = business.policyAcompteValue == null ? 30 : business.policyAcompteValue;
  const policyText     = business.policyText || "";
  const configured     = !!business.policyConfiguredAt;

  const HOUR_PRESETS = [
    { val: 0,   label: "Jamais",  hint: "Pas de self-service" },
    { val: 2,   label: "2 h",     hint: "Très souple" },
    { val: 12,  label: "12 h",    hint: "La veille" },
    { val: 24,  label: "24 h",    hint: "Recommandé" },
    { val: 48,  label: "48 h",    hint: "2 jours avant" },
    { val: 168, label: "7 j",     hint: "Très strict" },
  ];
  // Délai max d'avance pour PRENDRE un RDV : combien de jours dans le futur
  // les clients peuvent voir des créneaux. Évite que quelqu'un réserve
  // 6 mois à l'avance avant que le pro n'ait posé ses vacances.
  const DAY_PRESETS = [
    { val: 7,   label: "7 j",    hint: "1 semaine" },
    { val: 14,  label: "14 j",   hint: "2 semaines" },
    { val: 30,  label: "30 j",   hint: "1 mois" },
    { val: 60,  label: "60 j",   hint: "2 mois" },
    { val: 90,  label: "90 j",   hint: "3 mois" },
    { val: 180, label: "180 j",  hint: "6 mois" },
  ];
  // Délai (battement) entre deux RDV : temps de ménage/préparation bloqué
  // après chaque rendez-vous. Affiché grisé sur l'agenda.
  const MIN_PRESETS = [
    { val: 0,  label: "Aucun", hint: "Enchaîné" },
    { val: 5,  label: "5 min", hint: "Court" },
    { val: 10, label: "10 min", hint: "Souple" },
    { val: 15, label: "15 min", hint: "Confort" },
    { val: 20, label: "20 min", hint: "Large" },
    { val: 30, label: "30 min", hint: "Très large" },
  ];
  // Champ libre : si la valeur courante ne correspond à aucun preset on
  // affiche un état actif "Personnalisé" qui montre la valeur dans l'unité.
  const Row = ({ label, hint, value, field, presets = HOUR_PRESETS, unit = "heures", max = 8760 }) => {
    const matchesPreset = presets.some(p => p.val === value);
    const [draft, setDraft] = React.useState(matchesPreset ? "" : String(value));
    React.useEffect(() => { if (matchesPreset) setDraft(""); }, [value, matchesPreset]);
    const commit = () => {
      const n = parseInt(draft, 10);
      if (!Number.isNaN(n) && n >= 0 && n <= max) onChange({ [field]: n });
    };
    return (
      <div style={{ marginTop: 10 }}>
        <div style={{ fontSize: 13.5, fontWeight: 540, color: "var(--ink-2)", marginBottom: 4 }}>{label}</div>
        <div style={{ fontSize: 12, color: "var(--ink-4)", marginBottom: 8 }}>{hint}</div>
        <div style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 6 }}>
          {presets.map(p => {
            const active = p.val === value;
            return (
              <button key={p.val} onClick={() => onChange({ [field]: p.val })}
                style={{
                  padding: "10px 8px",
                  background: active ? "var(--accent-soft)" : "var(--surface)",
                  border: `1px solid ${active ? "var(--accent)" : "var(--line)"}`,
                  borderRadius: 10,
                  cursor: "pointer", fontFamily: "inherit", textAlign: "center",
                  color: active ? "var(--accent-ink)" : "var(--ink)",
                  transition: "background .15s, border-color .15s",
                }}>
                <div style={{ fontSize: 14, fontWeight: 580, letterSpacing: "-0.01em" }}>{p.label}</div>
                <div style={{ fontSize: 10.5, color: active ? "var(--accent-ink)" : "var(--ink-4)", marginTop: 2 }}>{p.hint}</div>
              </button>
            );
          })}
        </div>
        {/* Champ libre : valeur personnalisée, validée au blur / Enter.
            Pill "Personnalisé : N {unit}" s'affiche si la valeur ne matche aucun preset. */}
        <div style={{
          marginTop: 8, display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap",
        }}>
          <label style={{
            display: "inline-flex", alignItems: "center", gap: 6,
            fontSize: 12.5, color: "var(--ink-2)",
          }}>
            Valeur personnalisée
            <input
              type="number" min="0" max={max} step="1"
              value={draft}
              onChange={e => setDraft(e.target.value)}
              onBlur={commit}
              onKeyDown={e => { if (e.key === "Enter") { e.preventDefault(); commit(); e.currentTarget.blur(); } }}
              placeholder="ex : 36"
              style={{
                width: 86, padding: "7px 10px",
                background: "var(--surface)", border: "1px solid var(--line-strong)",
                borderRadius: 8, fontSize: 13, fontFamily: "inherit",
                color: "var(--ink)", boxSizing: "border-box",
              }}/>
            <span style={{ fontSize: 12, color: "var(--ink-3)" }}>{unit}</span>
          </label>
          {!matchesPreset && (
            <span style={{
              padding: "4px 10px",
              background: "var(--accent-soft)", color: "var(--accent-ink)",
              borderRadius: 999, fontSize: 11.5, fontWeight: 580,
              border: "1px solid var(--accent-soft-2)",
            }}>
              Personnalisé&nbsp;: {value}&nbsp;{unit === "heures" ? "h" : unit === "minutes" ? "min" : "j"}
            </span>
          )}
        </div>
      </div>
    );
  };

  // Acomptes retirés temporairement (trop complexe pour la bêta) — la table
  // acompte_settings reste en DB et les helpers cb-cloud sont conservés pour
  // qu'on puisse réactiver la feature plus tard sans migration.

  const [policyDraft, setPolicyDraft] = React.useState(policyText);
  React.useEffect(() => { setPolicyDraft(policyText); }, [policyText]);

  const [saving, setSaving] = React.useState(false);
  const handleValidate = async () => {
    setSaving(true);
    try {
      // Sauvegarde le texte libre (qui n'est pas auto-save)
      if (policyDraft !== policyText) onChange({ policyText: policyDraft });
      if (window.cbCloud && window.cbCloud.markPolicyConfigured) {
        const ts = await window.cbCloud.markPolicyConfigured();
        onValidated && onValidated(ts);
      } else {
        onValidated && onValidated(Date.now());
      }
    } finally { setSaving(false); }
  };

  return (
    <div>
      <Row
        label="Délai d'annulation"
        hint="Le client peut annuler son RDV jusqu'à ce délai avant l'heure prévue."
        value={cancelHours}
        field="policyCancelHours"
      />
      <Row
        label="Délai de modification"
        hint="Le client peut déplacer son RDV sur un autre créneau jusqu'à ce délai."
        value={modifyHours}
        field="policyModifyHours"
      />
      <Row
        label="Délai max d'avance pour réserver"
        hint="Combien de jours dans le futur vos clients peuvent voir des créneaux. Évite les réservations 6 mois à l'avance avant que vous ayez pu poser vos congés."
        value={maxAdvanceDays}
        field="policyMaxAdvanceDays"
        presets={DAY_PRESETS}
        unit="jours"
        max={365}
      />
      <Row
        label="Délai entre deux RDV"
        hint="Temps de battement (ménage, préparation) bloqué après chaque rendez-vous. Apparaît grisé sur votre agenda."
        value={bufferMin}
        field="policyBufferMin"
        presets={MIN_PRESETS}
        unit="minutes"
        max={120}
      />

      {/* ───────── Acompte à la réservation (prêt pour Stripe) ───────── */}
      <div style={{ marginTop: 18, paddingTop: 16, borderTop: "1px solid var(--line)" }}>
        <div style={{ fontSize: 13.5, fontWeight: 540, color: "var(--ink-2)", marginBottom: 4 }}>Acompte à la réservation</div>
        <div style={{ fontSize: 12, color: "var(--ink-4)", marginBottom: 10 }}>
          Demandez un acompte au moment de réserver pour sécuriser le RDV et réduire les lapins.
        </div>
        <div style={{
          display: "flex", alignItems: "center", gap: 8, marginBottom: 12,
          padding: "9px 12px", background: "var(--accent-soft)", border: "1px solid var(--accent-soft-2)",
          borderRadius: 9, fontSize: 12, color: "var(--accent-ink)", lineHeight: 1.5,
        }}>
          <Icon name="clock" size={13}/>
          <span>S'activera à l'arrivée du paiement par carte (<strong>mi-juillet</strong>). Vous pouvez le configurer dès maintenant.</span>
        </div>
        <div style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 6 }}>
          {[
            { v: "none",    label: "Aucun",        hint: "Pas d'acompte" },
            { v: "percent", label: "Pourcentage",  hint: "% du prix" },
            { v: "fixed",   label: "Montant fixe", hint: "Somme en €" },
          ].map(opt => {
            const active = acompteType === opt.v;
            return (
              <button key={opt.v} onClick={() => onChange({ policyAcompteType: opt.v })} style={{
                padding: "10px 8px",
                background: active ? "var(--accent-soft)" : "var(--surface)",
                border: `1px solid ${active ? "var(--accent)" : "var(--line)"}`,
                borderRadius: 10, cursor: "pointer", fontFamily: "inherit", textAlign: "center",
                color: active ? "var(--accent-ink)" : "var(--ink)",
              }}>
                <div style={{ fontSize: 13.5, fontWeight: 580 }}>{opt.label}</div>
                <div style={{ fontSize: 10.5, color: active ? "var(--accent-ink)" : "var(--ink-4)", marginTop: 2 }}>{opt.hint}</div>
              </button>
            );
          })}
        </div>
        {acompteType !== "none" && (
          <div style={{ marginTop: 10, display: "flex", alignItems: "center", gap: 8 }}>
            <label style={{ fontSize: 12.5, color: "var(--ink-2)" }}>Montant de l'acompte</label>
            <input type="number" min="1" value={acompteValue}
              onChange={e => onChange({ policyAcompteValue: Math.max(1, Number(e.target.value) || 1) })}
              style={{
                width: 90, padding: "7px 10px", background: "var(--surface)",
                border: "1px solid var(--line-strong)", borderRadius: 8, fontSize: 13,
                fontFamily: "inherit", color: "var(--ink)",
              }}/>
            <span style={{ fontSize: 14, color: "var(--ink-3)", fontWeight: 600 }}>
              {acompteType === "percent" ? "%" : "€"}
            </span>
          </div>
        )}
      </div>

      {/* ───────── Règles générales (texte libre) ───────── */}
      <div style={{ marginTop: 22 }}>
        <div style={{ fontSize: 13.5, fontWeight: 540, color: "var(--ink-2)", marginBottom: 4 }}>
          Règles générales
        </div>
        <div style={{ fontSize: 12, color: "var(--ink-4)", marginBottom: 8 }}>
          Code de conduite, retards, no-show, animaux acceptés, etc. Ce texte est visible par vos clients sur la page de réservation.
        </div>
        <textarea rows={5}
          value={policyDraft}
          onChange={e => setPolicyDraft(e.target.value)}
          onBlur={() => { if (policyDraft !== policyText) onChange({ policyText: policyDraft }); }}
          placeholder="Ex : Merci d'arriver 5 min avant votre RDV. Tout retard de plus de 15 min entraîne une annulation."
          style={{
            width: "100%", padding: "11px 13px",
            background: "var(--surface)", border: "1px solid var(--line-strong)",
            borderRadius: 10, fontSize: 13.5, fontFamily: "inherit", lineHeight: 1.5,
            boxSizing: "border-box", color: "var(--ink)", resize: "vertical",
          }}/>
      </div>

      {/* ───────── Bouton de validation finale ───────── */}
      <div style={{
        marginTop: 22, padding: 16,
        background: configured ? "var(--sage-soft)" : "var(--accent-soft)",
        border: `1px solid ${configured ? "oklch(82% 0.07 160)" : "var(--accent-soft-2)"}`,
        borderRadius: 12,
        display: "flex", alignItems: "center", gap: 14, flexWrap: "wrap",
      }}>
        <div style={{ flex: 1, minWidth: 200 }}>
          <div style={{ fontSize: 13.5, fontWeight: 600,
            color: configured ? "oklch(38% 0.08 160)" : "var(--accent-ink)" }}>
            {configured ? "✓ Politique validée" : "⚠ Politique non validée"}
          </div>
          <div style={{ fontSize: 12, color: "var(--ink-3)", marginTop: 4, lineHeight: 1.5 }}>
            {configured
              ? "Votre page de réservation publique est active. Vous pouvez modifier ces réglages à tout moment."
              : "Tant que vous n'avez pas validé vos politiques, votre lien public de réservation est désactivé pour vos clients."}
          </div>
        </div>
        <button onClick={handleValidate} disabled={saving}
          className={configured ? "btn btn-ghost btn-md" : "btn btn-accent btn-md"}>
          {saving ? "…" : (configured ? "Re-valider" : "Valider ma politique")}
        </button>
      </div>
    </div>
  );
};

const AppSettings = ({ data, actions, go }) => {
  const b = data.business || {};
  const [form, setForm] = React.useState({
    name: b.name || "",
    owner: b.owner || "",
    phone: b.phone || "",
    contactEmail: b.contactEmail || "",
    address: b.address || "",
    postalCode: b.postalCode || "",
    city: b.city || "",
    siret: b.siret || "",
    legalForm: b.legalForm || "Auto-entrepreneur",
    vatStatus: b.vatStatus || "franchise",
    vatNumber: b.vatNumber || "",
    vatRate: b.vatRate || 20,
    iban: b.iban || "",
    paymentTermsDays: b.paymentTermsDays || 0,
    paymentMethods: b.paymentMethods || "Virement, espèces, carte",
    rcsRm: b.rcsRm || "",
    mediatorName: b.mediatorName || "",
    mediatorUrl: b.mediatorUrl || "",
  });

  // Contact form (to ClientBase team)
  const [contact, setContact] = React.useState({ subject: "Question générale", message: "" });
  const [contactSent, setContactSent] = React.useState(false);

  // Modale de confirmation pour les actions dangereuses (reset / delete)
  const [dangerModal, setDangerModal] = React.useState(null); // null | "reset" | "delete"

  const user = cbAuth.getCurrentUser();
  const emailVerified = !!(user && user.emailVerified);

  // États locaux pour les 3 actions sécurité
  const [verifBusy, setVerifBusy]   = React.useState(false);
  const [verifSent, setVerifSent]   = React.useState(false);
  const [emailOpen, setEmailOpen]   = React.useState(false);
  const [newEmail, setNewEmail]     = React.useState("");
  const [emailBusy, setEmailBusy]   = React.useState(false);
  const [emailMsg,  setEmailMsg]    = React.useState(null); // {ok|err, text}
  const [pwBusy,    setPwBusy]      = React.useState(false);
  const [pwSent,    setPwSent]      = React.useState(false);

  const set = (patch) => setForm(f => ({ ...f, ...patch }));

  const saveSection = (keys) => {
    const patch = {};
    keys.forEach(k => { patch[k] = form[k]; });
    if (keys.includes("owner")) {
      patch.initials = (form.owner || "").split(" ").map(x => x[0]).slice(0, 2).join("").toUpperCase();
    }
    actions.updateBusiness(patch);
  };

  const [contactBusy, setContactBusy] = React.useState(false);
  const sendContact = async (e) => {
    e.preventDefault();
    if (!contact.message.trim() || contact.message.trim().length < 10) {
      showToast("Message trop court", "warn"); return;
    }
    const u = user || {};
    setContactBusy(true);
    const res = await window.sendContactMail({
      subject: `[ClientBase In-app, ${contact.subject}] ${u.email || "Utilisateur"}`,
      from: u.ownerName || u.email || "Utilisateur ClientBase",
      replyTo: u.email || "no-reply@clientbase.fr",
      message: contact.message,
      meta: {
        Activité: (data.business && data.business.name) || "—",
        Source: "App pro / Paramètres → Nous contacter",
      },
    });
    setContactBusy(false);
    if (res.error) { showToast(res.error, "warn"); return; }
    setContactSent(true);
    setTimeout(() => setContactSent(false), 4500);
    showToast("Message envoyé à l'équipe");
    setContact({ subject: "Question générale", message: "" });
  };

  const resendVerification = async () => {
    setVerifBusy(true);
    const res = await cbAuth.resendVerification();
    setVerifBusy(false);
    if (res.error) { showToast(res.error, "warn"); return; }
    setVerifSent(true);
    setTimeout(() => setVerifSent(false), 8000);
    showToast("Email de vérification envoyé ✉");
  };

  const submitChangeEmail = async () => {
    setEmailMsg(null);
    if (!cbAuth.isValidEmail(newEmail)) {
      setEmailMsg({ kind: "err", text: "Email invalide. Exemple : prenom@exemple.fr" });
      return;
    }
    setEmailBusy(true);
    const res = await cbAuth.changeEmail(newEmail);
    setEmailBusy(false);
    if (res.error) { setEmailMsg({ kind: "err", text: res.error }); return; }
    setEmailMsg({ kind: "ok", text: `Un email de confirmation a été envoyé à ${newEmail}. Cliquez sur le lien qu'il contient pour valider le changement.` });
    setNewEmail("");
  };

  const sendPasswordReset = async () => {
    if (!user || !user.email) { showToast("Aucun email associé", "warn"); return; }
    setPwBusy(true);
    const res = await cbAuth.resetPassword(user.email);
    setPwBusy(false);
    if (res.error) { showToast(res.error, "warn"); return; }
    setPwSent(true);
    setTimeout(() => setPwSent(false), 8000);
    showToast("Email de réinitialisation envoyé 🔒");
  };

  const doLogout = async () => {
    if (!window.confirm("Vous déconnecter de ClientBase ?")) return;
    try {
      // Préfixe window. obligatoire en Babel-standalone : cbAuth est
      // exposé via Object.assign(window) mais pas accessible comme
      // global bare dans les autres scripts.
      const auth = window.cbAuth || (typeof cbAuth !== "undefined" ? cbAuth : null);
      if (auth && auth.logout) {
        await auth.logout();
      }
    } catch (e) { window.cbDebug && window.cbDebug.warn("[logout]", e); }
    showToast("Déconnecté", "warn");
    // Redirect explicite vers la home — sans ça, l'UI peut rester
    // figée sur la page settings/app qui dépend de cbAuth.getCurrentUser.
    try { window.location.href = "/"; } catch { window.location.reload(); }
  };

  const isMobile = useIsMobile(720);
  // Mobile : la liste iOS en haut sert de navigation, aucune section ouverte au démarrage.
  // Desktop : onglet par défaut "business".
  // Initial tab forcé si window.cbInitialSettingsTab posé (ex: depuis
  // AppBookings qui redirige vers la politique de réservation).
  const initialTab = React.useMemo(() => {
    const forced = typeof window !== "undefined" && window.cbInitialSettingsTab;
    if (forced) { try { delete window.cbInitialSettingsTab; } catch {} return forced; }
    return isMobile ? null : "business";
  }, []);
  const [tab, setTab] = React.useState(initialTab);
  // Onglet « Apparence » retiré : les couleurs des modules sont désormais
  // figées sur la palette canonique (indigo + sage + rose), identiques pour
  // tous les pros, meilleur rappel visuel des modules entre site vitrine,
  // démo et espace pro.
  const tabs = [
    { id: "business",   label: "Votre activité",         icon: "sparkle",  tone: "business", sub: "Photo, nom, contact" },
    { id: "legal",      label: "Factures",               icon: "invoice",  tone: "legal",    sub: "SIRET, TVA, mentions" },
    { id: "booking",    label: "Politiques de réservation", icon: "calendar", tone: "business", sub: "Annulation, modification, règles" },
    { id: "account",    label: "Compte",                 icon: "users",    tone: "account",  sub: "Email, sécurité" },
    { id: "data",       label: "Vos données",            icon: "shield",   tone: "data",     sub: "RGPD, réinitialisation" },
    { id: "contact",    label: "Nous contacter",         icon: "mail",     tone: "contact",  sub: "Bug, question, idée" },
  ];

  return (
    <div style={{ maxWidth: isMobile ? 760 : "100%", paddingBottom: 48 }}>
      {/* Header settings, desktop ET mobile */}
      <div style={{ marginBottom: isMobile ? 16 : 22 }}>
        <h2 style={{
          margin: 0, fontFamily: "var(--ff-display)", fontSize: isMobile ? 22 : 28, fontWeight: 580,
          letterSpacing: "-0.025em",
        }}>
          Paramètres
        </h2>
        <p style={{ margin: "4px 0 0", fontSize: 13.5, color: "var(--ink-3)" }}>
          {isMobile
            ? (tab === null ? "Touchez une section pour l'ouvrir" : tabs.find(t => t.id === tab)?.sub || "")
            : "Personnalisez votre activité, votre apparence et votre compte"}
        </p>
      </div>

      {/* Mobile : liste verticale style iOS Settings.
          Tant qu'aucune section n'est active (tab === null), on affiche la liste.
          Dès qu'une section est ouverte, on remplace la liste par un bouton retour + la section seule.
          C'est ce flux navigation/section qui évite tout doublon de header. */}
      {isMobile && tab === null && (() => {
        // Mêmes catégories que desktop : business / legal+booking / account / data+contact.
        // "Factures" + "Politiques de réservation" groupées car même cycle RDV→encaissement.
        const groups = [
          { label: "Workspace", ids: ["business"] },
          { label: "Activité",  ids: ["legal", "booking"] },
          { label: "Compte",    ids: ["account"] },
          { label: "Système",   ids: ["data", "contact"] },
        ];
        return (
          <div style={{ display: "flex", flexDirection: "column", gap: 18, marginBottom: 20 }}>
            {groups.map(g => (
              <div key={g.label}>
                <div style={{
                  fontSize: 10.5, fontWeight: 700, color: "var(--ink-4)",
                  textTransform: "uppercase", letterSpacing: "0.09em",
                  padding: "0 14px 8px",
                }}>{g.label}</div>
                <div style={{
                  background: "var(--surface)", border: "1px solid var(--line)",
                  borderRadius: 14, overflow: "hidden",
                }}>
                  {g.ids.map((id, i) => {
                    const t = tabs.find(x => x.id === id);
                    if (!t) return null;
                    const tone = SETTINGS_TONES[t.tone] || SETTINGS_TONES.business;
                    const accent = tone.dot || tone.ink || "var(--accent)";
                    return (
                      <button key={t.id}
                        className="cb-press-feedback"
                        onClick={() => {
                          setTab(t.id);
                          setTimeout(() => { try { window.scrollTo({ top: 0, behavior: "smooth" }); } catch {} }, 30);
                        }}
                        style={{
                          display: "flex", alignItems: "center", gap: 14,
                          width: "100%", padding: "13px 14px",
                          background: "transparent",
                          border: "none",
                          borderBottom: i < g.ids.length - 1 ? "1px solid var(--line)" : "none",
                          cursor: "pointer", fontFamily: "inherit", textAlign: "left",
                          transition: "background .18s ease, transform .12s ease",
                        }}
                        onTouchStart={e => { e.currentTarget.style.background = "var(--bg-alt)"; }}
                        onTouchEnd={e => { e.currentTarget.style.background = "transparent"; }}>
                        <span style={{
                          width: 32, height: 32, borderRadius: 9, flexShrink: 0,
                          background: accent, color: "#fff",
                          display: "inline-flex", alignItems: "center", justifyContent: "center",
                          boxShadow: `0 4px 10px -4px ${accent}`,
                          transition: "box-shadow .2s ease, transform .12s ease",
                        }}>
                          <Icon name={t.icon} size={15} stroke={1.9}/>
                        </span>
                        <span style={{ flex: 1, minWidth: 0 }}>
                          <div style={{ fontSize: 14.5, fontWeight: 600, color: "var(--ink)", letterSpacing: "-0.01em" }}>
                            {t.label}
                          </div>
                          {t.sub && (
                            <div style={{ fontSize: 11.5, color: "var(--ink-4)", marginTop: 2, lineHeight: 1.3 }}>
                              {t.sub}
                            </div>
                          )}
                        </span>
                        <Icon name="arrow" size={13}
                          style={{ color: "var(--ink-4)", flexShrink: 0 }}/>
                      </button>
                    );
                  })}
                </div>
              </div>
            ))}
          </div>
        );
      })()}

      {/* Mobile : bouton retour vers la liste lorsqu'une section est ouverte */}
      {isMobile && tab !== null && (
        <button
          className="cb-press-feedback"
          onClick={() => { setTab(null); try { window.scrollTo({ top: 0, behavior: "smooth" }); } catch {} }}
          style={{
            display: "inline-flex", alignItems: "center", gap: 6,
            padding: "6px 12px 6px 6px", marginBottom: 14,
            background: "var(--bg-alt)", border: "1px solid var(--line)",
            borderRadius: 999, cursor: "pointer", fontFamily: "inherit",
            fontSize: 13, fontWeight: 540, color: "var(--ink-2)",
          }}>
          <Icon name="arrow" size={14} style={{ transform: "rotate(180deg)" }}/>
          Tous les paramètres
        </button>
      )}
      {/* Desktop : layout 2 colonnes (nav verticale sticky + contenu) */}
      <div className={isMobile ? "" : "cb-settings-2col"} style={isMobile ? {} : {
        display: "grid", gridTemplateColumns: "278px 1fr", gap: 36, alignItems: "start",
      }}>

      {/* Nav verticale, desktop only, riche : icône carrée tonale + label + sous-titre détaillé + badge d'état */}
      {!isMobile && (() => {
        // Desktop : mêmes groupes que mobile (parité), avec "booking" entre
        // Factures et Compte comme demandé. "Activité" groupe legal+booking
        // car même cycle de vie RDV → encaissement.
        const groups = [
          { label: "Workspace", ids: ["business"],          hint: "Identité de votre activité" },
          { label: "Activité",  ids: ["legal", "booking"],  hint: "Facturation et politiques de RDV" },
          { label: "Compte",    ids: ["account"],           hint: "Email, sécurité, session" },
          { label: "Système",   ids: ["data", "contact"],   hint: "Données et support" },
        ];
        // Indicateurs d'état par section (badge "À compléter" si infos manquantes, "OK" sinon).
        const status = {
          business: (form.name && form.owner && form.phone) ? "ok" : "todo",
          appearance: "ok",
          legal: (form.siret && form.address && form.postalCode && form.city) ? "ok" : "todo",
          booking: (data.business && data.business.policyConfiguredAt) ? "ok" : "todo",
          account: emailVerified ? "ok" : "todo",
          data: "ok",
          contact: "ok",
        };
        return (
          <nav style={{
            position: "sticky", top: 16,
            display: "flex", flexDirection: "column", gap: 24,
          }}>
            {groups.map(g => (
              <div key={g.label}>
                <div style={{ padding: "0 4px 10px" }}>
                  <div style={{
                    fontSize: 10.5, fontWeight: 700, color: "var(--ink-4)",
                    textTransform: "uppercase", letterSpacing: "0.09em",
                  }}>{g.label}</div>
                  <div style={{ fontSize: 11.5, color: "var(--ink-4)", marginTop: 3, lineHeight: 1.35 }}>
                    {g.hint}
                  </div>
                </div>
                <div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
                  {g.ids.map(id => {
                    const t = tabs.find(x => x.id === id);
                    if (!t) return null;
                    const isActive = tab === t.id;
                    const tone = SETTINGS_TONES[t.tone] || SETTINGS_TONES.business;
                    const accent = tone.dot || tone.ink || "var(--accent)";
                    const st = status[t.id];
                    return (
                      <button key={t.id} onClick={() => setTab(t.id)}
                        className="cb-press-feedback"
                        style={{
                          position: "relative",
                          display: "flex", alignItems: "center", gap: 11,
                          padding: "10px 12px 10px 14px", width: "100%",
                          background: isActive ? "var(--bg-alt)" : "transparent",
                          color: isActive ? "var(--ink)" : "var(--ink-2)",
                          border: "none", borderRadius: 10, cursor: "pointer", fontFamily: "inherit",
                          textAlign: "left",
                          letterSpacing: "-0.005em",
                          transition: "background .18s ease, color .18s ease, transform .12s ease",
                        }}
                        onMouseEnter={e => { if (!isActive) e.currentTarget.style.background = "var(--bg-alt)"; }}
                        onMouseLeave={e => { if (!isActive) { e.currentTarget.style.background = "transparent"; e.currentTarget.style.transform = "scale(1)"; } }}
                        onMouseDown={e => { e.currentTarget.style.transform = "scale(0.985)"; }}
                        onMouseUp={e => { e.currentTarget.style.transform = "scale(1)"; }}>
                        <span aria-hidden style={{
                          position: "absolute", left: 0, top: 9, bottom: 9,
                          width: 2.5, borderRadius: 99,
                          background: accent,
                          transform: isActive ? "scaleY(1)" : "scaleY(0)",
                          transformOrigin: "center",
                          transition: "transform .25s cubic-bezier(.22,1,.36,1)",
                        }}/>
                        {/* Pastille colorée toujours visible (parité mobile), l'accent solide passe
                            d'un fond légèrement adouci à pleine intensité quand l'onglet est actif. */}
                        <span style={{
                          width: 30, height: 30, borderRadius: 9, flexShrink: 0,
                          background: accent,
                          color: "#fff",
                          display: "inline-flex", alignItems: "center", justifyContent: "center",
                          transition: "box-shadow .2s ease, opacity .2s ease, transform .12s ease",
                          boxShadow: isActive ? `0 6px 14px -4px ${accent}` : `0 2px 6px -3px ${accent}`,
                          opacity: isActive ? 1 : 0.85,
                        }}>
                          <Icon name={t.icon} size={14.5} stroke={isActive ? 2.1 : 1.9}/>
                        </span>
                        <span style={{ flex: 1, minWidth: 0 }}>
                          <div style={{ fontSize: 13.5, fontWeight: isActive ? 600 : 540, lineHeight: 1.2 }}>
                            {t.label}
                          </div>
                          {t.sub && (
                            <div style={{ fontSize: 11.5, color: "var(--ink-4)", marginTop: 2, lineHeight: 1.3 }}>
                              {t.sub}
                            </div>
                          )}
                        </span>
                        {st === "todo" && (
                          <span aria-label="À compléter" title="À compléter" style={{
                            width: 7, height: 7, borderRadius: 99, flexShrink: 0,
                            background: "oklch(70% 0.16 60)",
                            boxShadow: "0 0 0 2px var(--surface)",
                          }}/>
                        )}
                      </button>
                    );
                  })}
                </div>
              </div>
            ))}
          </nav>
        );
      })()}

      {/* Colonne contenu, toutes les sections */}
      <div style={{ minWidth: 0 }}>

      {/* Identité */}
      {tab === "business" && (
      <SettingsSection title="Votre activité" subtitle="Photo, nom, contact, identité" tone="business">
        <AvatarUploader
          currentUrl={data.business.avatar}
          initials={data.business.initials || (data.business.owner || "?").split(" ").map(x => x[0]).slice(0,2).join("").toUpperCase()}
          hue={data.business.hue}
          onChange={(dataUrl) => actions.updateBusiness({ avatar: dataUrl || "" })}
        />
        <FormField label="Nom de l'activité" value={form.name} onChange={v => set({ name: v })}/>
        <FormField label="Votre nom" value={form.owner} onChange={v => set({ owner: v })}/>
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10 }} className="app-split">
          <FormField label="Téléphone" value={form.phone} onChange={v => set({ phone: v })} placeholder="06 12 34 56 78"/>
          <FormField label="Email de contact" type="email" value={form.contactEmail} onChange={v => set({ contactEmail: v })} placeholder="contact@votre-activité.fr"/>
        </div>
        <div>
          <button className="btn btn-primary" onClick={() => saveSection(["name", "owner", "phone", "contactEmail"])}>
            Enregistrer
          </button>
        </div>
      </SettingsSection>
      )}

      {/* Section Apparence retirée : couleurs des modules figées sur la
          palette canonique pour cohérence visuelle entre site et app. */}

      {/* Politiques de réservation : annulation, modification, acomptes, règles */}
      {tab === "booking" && (
      <SettingsSection title="Politiques de réservation" subtitle="Annulation, modification, règles générales" tone="business">
        <div style={{ fontSize: 12.5, color: "var(--ink-3)", marginTop: -4, marginBottom: 6, lineHeight: 1.5 }}>
          Vos clients avec un compte ClientBase peuvent annuler ou déplacer leurs RDV depuis leur espace personnel selon les délais que vous fixez. Au-delà, ils doivent vous contacter directement.
        </div>
        <BookingPolicyEditor
          business={data.business}
          onChange={(patch) => actions.updateBusiness(patch)}
          onValidated={(ts) => actions.updateBusiness({ policyConfiguredAt: ts })}
        />
      </SettingsSection>
      )}

      {/* Infos légales pour factures */}
      {tab === "legal" && (
      <SettingsSection title="Informations légales" subtitle="Obligatoires pour vos factures" tone="legal">
        <div style={{ fontSize: 12.5, color: "var(--ink-3)", marginTop: -4, marginBottom: 6, lineHeight: 1.5 }}>
          Ces informations apparaissent sur vos factures. Elles sont <strong>obligatoires</strong> en France (art. L441-9 du Code de commerce).
        </div>
        <FormSelect label="Forme juridique" value={form.legalForm} onChange={v => set({ legalForm: v })}
          options={["Auto-entrepreneur", "Entreprise individuelle (EI)", "EURL", "SASU", "SARL", "SAS", "Autre"]}/>
        <FormField label="SIRET" value={form.siret} onChange={v => set({ siret: v })} placeholder="123 456 789 00012"/>
        <FormField label="Adresse" value={form.address} onChange={v => set({ address: v })} placeholder="12 rue du Commerce"/>
        <div style={{ display: "grid", gridTemplateColumns: "120px 1fr", gap: 10 }}>
          <FormField label="Code postal" value={form.postalCode} onChange={v => set({ postalCode: v })} placeholder="75015"/>
          <FormField label="Ville" value={form.city} onChange={v => set({ city: v })} placeholder="Paris"/>
        </div>
        <FormSelect label="Statut TVA" value={form.vatStatus} onChange={v => set({ vatStatus: v })}
          options={[
            ["franchise",  "Franchise en base (auto-ent. / petite entreprise), sans TVA"],
            ["applicable", "Assujetti·e à la TVA"],
          ]}/>
        {form.vatStatus === "applicable" && (
          <div style={{ display: "grid", gridTemplateColumns: "1fr 120px", gap: 10 }}>
            <FormField label="N° TVA intracommunautaire" value={form.vatNumber} onChange={v => set({ vatNumber: v })} placeholder="FR12345678901"/>
            <FormField label="Taux (%)" value={String(form.vatRate)} onChange={v => set({ vatRate: parseFloat(v) || 0 })}/>
          </div>
        )}
        <FormField label="IBAN (facultatif)" value={form.iban} onChange={v => set({ iban: v })} placeholder="FR76 XXXX XXXX XXXX"/>
        <div style={{ display: "grid", gridTemplateColumns: "140px 1fr", gap: 10 }}>
          <div>
            <label style={fieldLabelStyle}>Délai paiement (jours)</label>
            <input type="number" min="0" max="60" value={form.paymentTermsDays}
              onChange={e => set({ paymentTermsDays: parseInt(e.target.value, 10) || 0 })}
              style={fieldInputStyle}/>
          </div>
          <FormField label="Moyens de paiement acceptés" value={form.paymentMethods} onChange={v => set({ paymentMethods: v })}/>
        </div>
        <FormField label="Immatriculation RCS / RM (facultatif)" value={form.rcsRm} onChange={v => set({ rcsRm: v })}
          placeholder="ex : RCS Paris 123 456 789, ou, RM 91 123 456 789"/>
        <div style={{ fontSize: 12, color: "var(--ink-4)", marginTop: -8, lineHeight: 1.5 }}>
          Commerçant&nbsp;: numéro RCS + ville du greffe. Artisan&nbsp;: numéro Répertoire des Métiers + département.
        </div>
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10 }}>
          <FormField label="Médiateur de la consommation" value={form.mediatorName} onChange={v => set({ mediatorName: v })}
            placeholder="ex : MEDICYS"/>
          <FormField label="Site / contact du médiateur" value={form.mediatorUrl} onChange={v => set({ mediatorUrl: v })}
            placeholder="ex : medicys.fr"/>
        </div>
        <div style={{ fontSize: 12, color: "var(--ink-4)", marginTop: -8, lineHeight: 1.5 }}>
          Obligatoire pour tout pro vendant à des particuliers (art. L612-1 Code de la consommation).
          Trouver un médiateur référencé&nbsp;: <a href="https://www.economie.gouv.fr/mediation-conso" target="_blank" rel="noopener noreferrer" style={{ color: "var(--accent-ink)", fontWeight: 520 }}>economie.gouv.fr/mediation-conso</a>.
        </div>
        <div>
          <button className="btn btn-primary" onClick={() => saveSection([
            "address", "postalCode", "city", "siret", "legalForm",
            "vatStatus", "vatNumber", "vatRate", "iban", "paymentTermsDays", "paymentMethods",
            "rcsRm", "mediatorName", "mediatorUrl",
          ])}>
            Enregistrer
          </button>
        </div>
      </SettingsSection>
      )}

      {/* Contact ClientBase */}
      {tab === "contact" && (
      <SettingsSection title="Nous contacter" subtitle="Un bug, une question, une idée" tone="contact">
        <div style={{ fontSize: 13, color: "var(--ink-3)", marginTop: -4, lineHeight: 1.5 }}>
          Un bug, une suggestion, une question&nbsp;? On répond en moins de 24&nbsp;h ouvrées.
        </div>
        {contactSent ? (
          <div style={{
            padding: "16px 18px", background: "var(--sage-soft)",
            border: "1px solid oklch(88% 0.03 160)", borderRadius: 10,
            display: "flex", gap: 10, alignItems: "center",
            color: "oklch(38% 0.08 160)",
          }}>
            <Icon name="check" size={18} stroke={2.6}/>
            <div>
              <div style={{ fontWeight: 520 }}>Message envoyé</div>
              <div style={{ fontSize: 12.5, marginTop: 2 }}>On vous répond à {user?.email || "votre email"}.</div>
            </div>
          </div>
        ) : (
          <form onSubmit={sendContact} style={{ display: "flex", flexDirection: "column", gap: 12 }}>
            <FormSelect label="Sujet" value={contact.subject} onChange={v => setContact({ ...contact, subject: v })}
              options={["Question générale", "Bug à signaler", "Suggestion de fonctionnalité", "Demande commerciale", "Autre"]}/>
            <FormTextarea label="Votre message" value={contact.message} onChange={v => setContact({ ...contact, message: v })} rows={5} placeholder="Décrivez votre demande…"/>
            <div>
              <button type="submit" disabled={contactBusy} className="btn btn-primary" style={{ opacity: contactBusy ? 0.7 : 1 }}>
                <Icon name="mail" size={13}/> {contactBusy ? "Envoi…" : "Envoyer à l'équipe"}
              </button>
            </div>
          </form>
        )}
      </SettingsSection>

      )}

      {/* RGPD / Reset / Delete account */}
      {tab === "data" && (
      <SettingsSection title="Vos données" subtitle="RGPD, export, suppression" tone="data">
        <div style={{ fontSize: 13, color: "var(--ink-2)", lineHeight: 1.6 }}>
          Vos données sont hébergées en France (Paris), dans l'Union européenne, et chiffrées en transit.{" "}
          {go && (
            <a href="#" onClick={(e) => { e.preventDefault(); go("legal"); }} style={{ color: "var(--accent-ink)", fontWeight: 520 }}>
              En savoir plus sur vos droits
            </a>
          )}.
        </div>

        {/* Export RGPD */}
        <div className="app-split-row" style={{
          display: "flex", justifyContent: "space-between", alignItems: "center",
          gap: 14, flexWrap: "wrap", paddingTop: 14, borderTop: "1px solid var(--line)",
        }}>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontWeight: 520, fontSize: 14 }}>Exporter mes données</div>
            <div style={{ fontSize: 13, color: "var(--ink-3)", marginTop: 4, lineHeight: 1.5 }}>
              Télécharge une copie complète de vos données au format JSON (compte, clients, RDV, factures, prestations, stock).
              Conforme au droit à la portabilité (RGPD&nbsp;art.&nbsp;20).
            </div>
          </div>
          <button className="btn btn-ghost" onClick={() => {
            try {
              const u = cbAuth.getCurrentUser() || {};
              const payload = {
                exportedAt: new Date().toISOString(),
                format: "ClientBase RGPD export v1",
                account: { email: u.email || "", name: u.name || "", emailVerified: !!u.emailVerified },
                data: data,
              };
              const blob = new Blob([JSON.stringify(payload, null, 2)], { type: "application/json" });
              const url = URL.createObjectURL(blob);
              const a = document.createElement("a");
              const stamp = new Date().toISOString().slice(0, 10);
              a.href = url;
              a.download = `clientbase-export-${stamp}.json`;
              document.body.appendChild(a);
              a.click();
              document.body.removeChild(a);
              setTimeout(() => URL.revokeObjectURL(url), 1000);
              showToast("Export téléchargé", "ok");
            } catch (e) {
              showToast("Échec de l'export, réessayez", "warn");
            }
          }}>
            <Icon name="arrow" size={12} style={{ transform: "rotate(90deg)" }}/> Télécharger (JSON)
          </button>
        </div>

        {/* Réinitialiser */}
        <div className="app-split-row" style={{
          display: "flex", justifyContent: "space-between", alignItems: "center",
          gap: 14, flexWrap: "wrap", paddingTop: 14, borderTop: "1px solid var(--line)",
        }}>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontWeight: 520, fontSize: 14 }}>Réinitialiser les données</div>
            <div style={{ fontSize: 13, color: "var(--ink-3)", marginTop: 4, lineHeight: 1.5 }}>
              Supprime <strong>définitivement</strong> toutes vos clients, RDV, factures, prestations et statistiques.
              Votre compte reste actif, vous pouvez recommencer de zéro.
            </div>
          </div>
          <button className="btn btn-ghost" onClick={() => setDangerModal("reset")}
            style={{ color: "oklch(55% 0.18 25)", borderColor: "oklch(85% 0.05 25)" }}>
            Réinitialiser
          </button>
        </div>

        {/* Supprimer le compte */}
        <div className="app-split-row" style={{
          display: "flex", justifyContent: "space-between", alignItems: "center",
          gap: 14, flexWrap: "wrap", paddingTop: 14, borderTop: "1px solid var(--line)",
        }}>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontWeight: 520, fontSize: 14, color: "oklch(45% 0.2 25)" }}>Supprimer mon compte</div>
            <div style={{ fontSize: 13, color: "var(--ink-3)", marginTop: 4, lineHeight: 1.5 }}>
              Supprime votre compte ClientBase <strong>et toutes vos données</strong>.
              Action <strong>irréversible</strong>, vous ne pourrez plus vous reconnecter avec cet email.
            </div>
          </div>
          <button onClick={() => setDangerModal("delete")} style={{
            padding: "8px 14px", fontSize: 13, fontWeight: 580,
            background: "oklch(55% 0.2 25)", color: "white",
            border: "none", borderRadius: 999, cursor: "pointer", fontFamily: "inherit",
          }}>
            Supprimer le compte
          </button>
        </div>
      </SettingsSection>

      )}

      {/* Compte, email + sécurité */}
      {(tab === "account" && user) && (
        <SettingsSection title="Compte" subtitle="Email, sécurité, session" tone="account">
          {/* Bloc identité avec badge vérifié/non vérifié */}
          <div style={{
            padding: "14px 16px",
            background: "var(--bg-alt)", border: "1px solid var(--line)",
            borderRadius: 12,
            display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap",
          }}>
            <div style={{
              width: 40, height: 40, borderRadius: 11, flexShrink: 0,
              background: emailVerified ? "var(--sage-soft)" : "var(--warn-soft)",
              color: emailVerified ? "var(--sage)" : "var(--warn-ink)",
              display: "flex", alignItems: "center", justifyContent: "center",
            }}>
              <Icon name={emailVerified ? "check" : "mail"} size={18} stroke={emailVerified ? 2.6 : 1.8}/>
            </div>
            <div style={{ flex: 1, minWidth: 180 }}>
              <div style={{ fontSize: 14, fontWeight: 560, color: "var(--ink)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
                {user.email}
              </div>
              <div style={{ marginTop: 4, display: "flex", alignItems: "center", gap: 6, flexWrap: "wrap" }}>
                {emailVerified ? (
                  <span style={{
                    display: "inline-flex", alignItems: "center", gap: 4,
                    padding: "2px 8px", fontSize: 11, fontWeight: 600,
                    background: "var(--sage-soft)", color: "oklch(38% 0.08 160)",
                    borderRadius: 999,
                  }}>
                    <Icon name="check" size={10} stroke={2.8}/> Vérifié
                  </span>
                ) : (
                  <span style={{
                    display: "inline-flex", alignItems: "center", gap: 4,
                    padding: "2px 8px", fontSize: 11, fontWeight: 600,
                    background: "var(--warn-soft)", color: "var(--warn-ink)",
                    borderRadius: 999,
                  }}>
                    <Icon name="clock" size={10} stroke={2.6}/> Non vérifié
                  </span>
                )}
                <span style={{ fontSize: 12, color: "var(--ink-4)" }}>
                  · Compte créé le {new Date(user.createdAt).toLocaleDateString("fr-FR")}
                </span>
              </div>
            </div>
          </div>

          {/* Action : re-envoyer l'email de vérification (si non vérifié) */}
          {!emailVerified && (
            <div style={{
              padding: "14px 16px",
              background: "var(--warn-soft)", border: "1px solid var(--warn-soft-2)",
              borderRadius: 12, color: "var(--warn-ink)",
              display: "flex", gap: 12, alignItems: "center", flexWrap: "wrap",
            }}>
              <div style={{ flex: 1, minWidth: 180 }}>
                <div style={{ fontSize: 13.5, fontWeight: 560 }}>Confirmez votre email</div>
                <div style={{ fontSize: 12.5, marginTop: 2, lineHeight: 1.5 }}>
                  Un email de confirmation a été envoyé à {user.email}. Cliquez sur le lien qu'il contient pour activer toutes les fonctionnalités.
                </div>
              </div>
              <button onClick={resendVerification} disabled={verifBusy || verifSent} className="btn btn-sm"
                style={{ background: "var(--warn-ink)", color: "white", opacity: (verifBusy || verifSent) ? 0.7 : 1 }}>
                {verifBusy ? "Envoi…" : verifSent ? "Envoyé ✓" : "Renvoyer l'email"}
              </button>
            </div>
          )}

          {/* Action : changer l'email */}
          <div style={{
            padding: "14px 16px",
            background: "var(--surface)", border: "1px solid var(--line)",
            borderRadius: 12,
          }}>
            <div style={{
              display: "flex", gap: 12, alignItems: "center", flexWrap: "wrap",
            }}>
              <div style={{
                width: 36, height: 36, borderRadius: 10, flexShrink: 0,
                background: "var(--accent-soft)", color: "var(--accent-ink)",
                display: "flex", alignItems: "center", justifyContent: "center",
              }}>
                <Icon name="mail" size={16}/>
              </div>
              <div style={{ flex: 1, minWidth: 160 }}>
                <div style={{ fontSize: 13.5, fontWeight: 560 }}>Changer l'adresse email</div>
                <div style={{ fontSize: 12.5, color: "var(--ink-3)", marginTop: 2 }}>
                  On enverra un lien de confirmation à la nouvelle adresse.
                </div>
              </div>
              {!emailOpen && (
                <button onClick={() => setEmailOpen(true)} className="btn btn-sm btn-ghost">
                  Modifier
                </button>
              )}
            </div>
            {emailOpen && (
              <div style={{ marginTop: 12, display: "flex", flexDirection: "column", gap: 10 }}>
                <input type="email" value={newEmail} onChange={e => setNewEmail(e.target.value)}
                  placeholder="nouvelle@adresse.fr"
                  style={fieldInputStyle}/>
                {emailMsg && (
                  <div style={{
                    padding: "9px 12px", borderRadius: 9, fontSize: 13,
                    background: emailMsg.kind === "ok" ? "var(--sage-soft)" : "oklch(96% 0.04 25)",
                    border: `1px solid ${emailMsg.kind === "ok" ? "oklch(88% 0.03 160)" : "oklch(88% 0.06 25)"}`,
                    color:      emailMsg.kind === "ok" ? "oklch(38% 0.08 160)" : "oklch(42% 0.15 25)",
                  }}>{emailMsg.text}</div>
                )}
                <div style={{ display: "flex", gap: 8, justifyContent: "flex-end", flexWrap: "wrap" }}>
                  <button onClick={() => { setEmailOpen(false); setNewEmail(""); setEmailMsg(null); }}
                    className="btn btn-sm btn-ghost">Annuler</button>
                  <button onClick={submitChangeEmail} disabled={emailBusy} className="btn btn-sm btn-accent"
                    style={{ opacity: emailBusy ? 0.7 : 1 }}>
                    {emailBusy ? "Envoi…" : "Envoyer le lien"}
                  </button>
                </div>
              </div>
            )}
          </div>

          {/* Action : reset password */}
          <div style={{
            padding: "14px 16px",
            background: "var(--surface)", border: "1px solid var(--line)",
            borderRadius: 12,
            display: "flex", gap: 12, alignItems: "center", flexWrap: "wrap",
          }}>
            <div style={{
              width: 36, height: 36, borderRadius: 10, flexShrink: 0,
              background: "var(--accent-soft)", color: "var(--accent-ink)",
              display: "flex", alignItems: "center", justifyContent: "center",
            }}>
              <Icon name="lock" size={16}/>
            </div>
            <div style={{ flex: 1, minWidth: 160 }}>
              <div style={{ fontSize: 13.5, fontWeight: 560 }}>Réinitialiser le mot de passe</div>
              <div style={{ fontSize: 12.5, color: "var(--ink-3)", marginTop: 2 }}>
                On envoie un lien sécurisé à {user.email}.
              </div>
            </div>
            <button onClick={sendPasswordReset} disabled={pwBusy || pwSent} className="btn btn-sm btn-ghost"
              style={{ opacity: (pwBusy || pwSent) ? 0.7 : 1 }}>
              {pwBusy ? "Envoi…" : pwSent ? "Envoyé ✓" : "Envoyer le lien"}
            </button>
          </div>

          {/* Logout, bouton rouge plein pour bien le distinguer */}
          <div style={{
            paddingTop: 12, borderTop: "1px solid var(--line)",
            display: "flex", justifyContent: "flex-end",
          }}>
            <button onClick={doLogout} style={{
              padding: "9px 16px", fontSize: 13.5, fontWeight: 580,
              background: "oklch(55% 0.2 25)", color: "white",
              border: "none", borderRadius: 999, cursor: "pointer", fontFamily: "inherit",
              display: "inline-flex", alignItems: "center", gap: 7,
            }}>
              <Icon name="arrow" size={13} style={{ transform: "rotate(180deg)" }}/>
              Se déconnecter
            </button>
          </div>
        </SettingsSection>
      )}

      </div>{/* fin colonne contenu */}
      </div>{/* fin layout 2 colonnes */}

      {/* Modale de confirmation pour reset / delete account */}
      <DangerConfirmModal
        kind={dangerModal}
        onClose={() => setDangerModal(null)}
        onConfirm={async () => {
          const k = dangerModal;
          setDangerModal(null);
          if (k === "reset") await actions.reset();
          if (k === "delete") await actions.deleteAccount();
        }}
      />
    </div>
  );
};

/* Modale de confirmation forte, exige de TAPER un mot pour valider une
   action irréversible (réinitialiser ou supprimer le compte). */
const DangerConfirmModal = ({ kind, onClose, onConfirm }) => {
  const [typed, setTyped] = React.useState("");
  React.useEffect(() => { setTyped(""); }, [kind]);

  if (!kind) return null;
  const isDelete = kind === "delete";
  const requiredWord = isDelete ? "SUPPRIMER" : "RESET";
  const valid = typed.trim().toUpperCase() === requiredWord;

  const title = isDelete ? "Supprimer définitivement votre compte ?" : "Réinitialiser toutes vos données ?";

  return (
    <Modal open={true} onClose={onClose}
      title={title}
      footer={
        <>
          <button className="btn btn-ghost" onClick={onClose}>Annuler</button>
          <button onClick={onConfirm} disabled={!valid} style={{
            padding: "9px 16px", fontSize: 13.5, fontWeight: 580,
            background: valid ? "oklch(55% 0.2 25)" : "var(--bg-alt)",
            color: valid ? "white" : "var(--ink-4)",
            border: "none", borderRadius: 999,
            cursor: valid ? "pointer" : "not-allowed", fontFamily: "inherit",
          }}>
            {isDelete ? "Supprimer mon compte" : "Tout réinitialiser"}
          </button>
        </>
      }>
      <div style={{ display: "grid", gap: 14 }}>
        <div style={{
          padding: "14px 16px",
          background: "oklch(96% 0.04 25)", border: "1px solid oklch(88% 0.06 25)",
          borderRadius: 10, color: "oklch(35% 0.15 25)", fontSize: 13.5, lineHeight: 1.55,
        }}>
          <strong>⚠️ Cette action est irréversible.</strong>
          <ul style={{ margin: "8px 0 0", paddingLeft: 18, lineHeight: 1.55 }}>
            <li>Toutes vos clients seront supprimées</li>
            <li>Tous vos rendez-vous (passés et à venir) seront supprimés</li>
            <li>Toutes vos factures, prestations et statistiques seront perdues</li>
            <li>Aucune sauvegarde ne sera conservée, <strong>impossible de récupérer</strong></li>
            {isDelete && (
              <li style={{ marginTop: 6, fontWeight: 580 }}>
                Votre compte ClientBase ({(window.cbAuth && cbAuth.getCurrentUser() && cbAuth.getCurrentUser().email) || "vous"}) sera supprimé
              </li>
            )}
          </ul>
        </div>
        <div>
          <label style={{ fontSize: 13, color: "var(--ink-2)", display: "block", marginBottom: 6 }}>
            Pour confirmer, tapez <strong style={{ color: "oklch(55% 0.2 25)" }}>{requiredWord}</strong> ci-dessous&nbsp;:
          </label>
          <input value={typed} onChange={e => setTyped(e.target.value)} autoFocus
            placeholder={requiredWord}
            style={{ ...fieldInputStyle, letterSpacing: "0.04em", fontWeight: 500 }}/>
        </div>
      </div>
    </Modal>
  );
};

/* ================================================================
   SUBSCRIPTION, trial status, plan comparison, upgrade CTAs
================================================================ */
// === Formules canoniques (mêmes prix/quotas que la page tarifs publique) ===
// Quotas pensés pour rester rentables : SMS ~0,05 € pièce, email quasi gratuit.
// La formule du milieu (Pro, 19,99) est mise en avant ; Premium = gros volumes.
const SUBSCRIPTION_PLANS = [
  {
    id: "free",
    name: "Découverte",
    price: 0,
    tagline: "Pour démarrer",
    cta: "Commencer gratuitement",
    features: [
      { kind: "check", t: "Agenda + fiches clients (jusqu'à 20)" },
      { kind: "check", t: "Page de réservation en ligne" },
      { kind: "check", t: "100 emails / mois" },
      { kind: "cross", t: "SMS de relance & campagnes" },
      { kind: "cross", t: "Cartes de fidélité" },
      { kind: "cross", t: "Statistiques détaillées" },
    ],
  },
  {
    id: "essentiel",
    name: "Essentiel",
    price: 9.99,
    tagline: "L'essentiel pour gérer",
    cta: "Choisir Essentiel",
    features: [
      { kind: "check", t: "Clients illimités" },
      { kind: "check", t: "Fidélité + facturation conforme" },
      { kind: "check", t: "50 SMS / mois inclus" },
      { kind: "check", t: "1 000 emails / mois" },
      { kind: "check", t: "Relances par email" },
      { kind: "cross", t: "Marketing SMS & collecte d'avis" },
    ],
  },
  {
    id: "pro",
    name: "Pro",
    price: 19.99,
    tagline: "Le meilleur rapport qualité-prix",
    cta: "Choisir Pro",
    highlight: true,
    features: [
      { kind: "check", t: "Tout Essentiel, et en plus :" },
      { kind: "check", t: "200 SMS / mois inclus" },
      { kind: "check", t: "5 000 emails / mois" },
      { kind: "check", t: "Marketing complet (campagnes, relances, avis)" },
      { kind: "check", t: "Statistiques détaillées" },
      { kind: "check", t: "Badge « Vérifié » + sans pub" },
    ],
  },
  {
    id: "premium",
    name: "Premium",
    price: 29.99,
    tagline: "Pour les gros volumes",
    cta: "Choisir Premium",
    features: [
      { kind: "check", t: "Tout Pro, et en plus :" },
      { kind: "check", t: "500 SMS / mois inclus" },
      { kind: "check", t: "Emails illimités" },
      { kind: "check", t: "Multi-postes / accès équipe" },
      { kind: "check", t: "Accompagnement dédié" },
    ],
  },
];

// Thème visuel de chaque bandeau de formule, nuances d'indigo alignées
// sur la couleur d'accent du site (hue ~278), du plus clair au plus profond.
const PLAN_THEME = {
  free:      { grad: "linear-gradient(135deg, oklch(70% 0.14 274) 0%, oklch(63% 0.17 280) 100%)", glow: "oklch(64% 0.16 277)" },
  essentiel: { grad: "linear-gradient(135deg, oklch(60% 0.20 278) 0%, oklch(53% 0.21 284) 100%)", glow: "oklch(57% 0.20 280)" },
  pro:       { grad: "linear-gradient(135deg, oklch(55% 0.22 278) 0%, oklch(47% 0.22 288) 100%)", glow: "var(--accent)" },
  premium:   { grad: "linear-gradient(135deg, oklch(40% 0.19 282) 0%, oklch(32% 0.17 292) 100%)", glow: "oklch(37% 0.18 286)" },
};

const AppSubscription = ({ data, go, setMod }) => {
  // Bêta ouverte pour 3 mois à partir d'aujourd'hui. Durée susceptible d'être allongée.
  const betaDurationDays = 90;
  const betaStart = React.useMemo(() => {
    const saved = localStorage.getItem("cb_beta_start_v1");
    if (saved) return +saved;
    const now = Date.now();
    localStorage.setItem("cb_beta_start_v1", String(now));
    return now;
  }, []);
  const betaEnd = betaStart + betaDurationDays * 24 * 60 * 60 * 1000;

  const [now, setNow] = React.useState(() => Date.now());
  React.useEffect(() => {
    const t = setInterval(() => setNow(Date.now()), 1000);
    return () => clearInterval(t);
  }, []);

  const remaining = Math.max(0, betaEnd - now);
  const days    = Math.floor(remaining / (1000 * 60 * 60 * 24));
  const hours   = Math.floor((remaining / (1000 * 60 * 60)) % 24);
  const minutes = Math.floor((remaining / (1000 * 60)) % 60);
  const seconds = Math.floor((remaining / 1000) % 60);
  const trialPct = Math.max(0, Math.min(100, ((betaDurationDays * 24 * 60 * 60 * 1000 - remaining) / (betaDurationDays * 24 * 60 * 60 * 1000)) * 100));

  // Mini-questionnaire, recommande une formule selon les réponses
  const [answers, setAnswers] = React.useState({});
  const setAns = (k, v) => setAnswers(a => ({ ...a, [k]: v }));
  const recommendId = (() => {
    const { size, billing, fid } = answers;
    if (!size || !billing || !fid) return null;
    if (size === "large" || fid === "yes") return "pro";
    if (billing === "yes") return "essentiel";
    return "free";
  })();
  const recommendName = recommendId
    ? (SUBSCRIPTION_PLANS.find(p => p.id === recommendId) || {}).name
    : null;

  return (
    <div>
      <style>{`
        @keyframes cbHeartbeat { 0%,100% { transform: scale(1); } 50% { transform: scale(1.12); } }
        @keyframes cbBlink     { 0%,100% { opacity: 1; } 50% { opacity: 0.35; } }
        @keyframes cbFeatPop   { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }
        @keyframes cbShineMove { 0% { transform: translateX(-120%) skewX(-18deg); } 100% { transform: translateX(220%) skewX(-18deg); } }
        @keyframes cbFloatY    { 0%,100% { transform: translateY(0); } 50% { transform: translateY(-5px); } }
        @keyframes cbReco { 0%,100% { box-shadow: 0 0 0 0 oklch(60% 0.18 250 / 0.5); } 50% { box-shadow: 0 0 0 14px oklch(60% 0.18 250 / 0); } }
        @keyframes cbBadgeIn { from { opacity: 0; transform: translateY(-6px) scale(0.95); } to { opacity: 1; transform: translateY(0) scale(1); } }
        @media (max-width: 720px) {
          .cb-sub-hero { padding: 22px 18px !important; }
        }
      `}</style>

      {/* ===== HERO, bêta en cours, tout offert ===== */}
      <div className="cb-sub-hero" style={{
        padding: "30px 34px", marginBottom: 20,
        background: "linear-gradient(135deg, var(--accent) 0%, oklch(48% 0.22 300) 55%, oklch(56% 0.20 330) 100%)",
        borderRadius: 24, color: "white",
        position: "relative", overflow: "hidden",
      }}>
        <div aria-hidden style={{ position: "absolute", top: -70, right: -40, width: 230, height: 230, borderRadius: "50%", background: "rgba(255,255,255,0.10)" }}/>
        <div aria-hidden style={{ position: "absolute", bottom: -90, right: 150, width: 170, height: 170, borderRadius: "50%", background: "rgba(255,255,255,0.07)" }}/>
        {/* Reflet animé qui balaye le hero */}
        <div aria-hidden style={{
          position: "absolute", top: 0, bottom: 0, left: 0, width: 80,
          background: "linear-gradient(90deg, transparent, rgba(255,255,255,0.16), transparent)",
          animation: "cbShineMove 5.5s ease-in-out infinite", pointerEvents: "none",
        }}/>

        <div style={{ position: "relative", maxWidth: 620 }}>
          <div style={{ display: "inline-flex", alignItems: "center", gap: 8, marginBottom: 12 }}>
            <span style={{ width: 9, height: 9, borderRadius: 50, background: "white", animation: "cbHeartbeat 1.4s ease-in-out infinite" }}/>
            <span style={{ fontSize: 11.5, opacity: 0.92, textTransform: "uppercase", letterSpacing: "0.1em", fontWeight: 600 }}>
              Vous êtes en bêta
            </span>
          </div>
          <h2 style={{
            margin: 0, color: "#fff", fontFamily: "var(--ff-display)", fontSize: "clamp(26px, 3.4vw, 38px)",
            fontWeight: 580, letterSpacing: "-0.035em", lineHeight: 1.08,
          }}>
            Tout ClientBase, <em style={{ fontStyle: "italic" }}>gratuitement</em>.
          </h2>
          <p style={{ margin: "10px 0 0", color: "#fff", fontSize: 14.5, opacity: 0.92, lineHeight: 1.55, maxWidth: 480 }}>
            Pendant la bêta, <strong>chaque fonctionnalité</strong> est débloquée, clientes,
            factures, page de réservation, fidélité, stats. Pas de carte bancaire, pas de limite.
          </p>

          {/* Countdown, tuiles */}
          <div style={{ display: "flex", gap: 9, marginTop: 20, flexWrap: "wrap" }}>
            {[
              { v: days, l: days > 1 ? "jours" : "jour" },
              { v: hours, l: "heures" },
              { v: minutes, l: "minutes" },
              { v: seconds, l: "sec", blink: true },
            ].map((x, i) => (
              <div key={i} style={{
                background: "rgba(255,255,255,0.16)",
                border: "1px solid rgba(255,255,255,0.25)",
                borderRadius: 14, padding: "11px 15px",
                minWidth: 70, textAlign: "center",
                backdropFilter: "blur(8px)", WebkitBackdropFilter: "blur(8px)",
              }}>
                <div style={{
                  fontFamily: "var(--ff-display)", fontSize: 27, fontWeight: 600,
                  letterSpacing: "-0.025em", lineHeight: 1, fontVariantNumeric: "tabular-nums",
                  animation: x.blink ? "cbBlink 1s ease-in-out infinite" : "none",
                }}>{String(x.v).padStart(2, "0")}</div>
                <div style={{ fontSize: 9.5, opacity: 0.85, marginTop: 5, letterSpacing: "0.06em", textTransform: "uppercase" }}>{x.l}</div>
              </div>
            ))}
          </div>
          <div style={{ marginTop: 14, maxWidth: 340, height: 6, background: "rgba(255,255,255,0.22)", borderRadius: 999, overflow: "hidden" }}>
            <div style={{
              width: `${trialPct}%`, height: "100%",
              background: "linear-gradient(90deg, #fff, oklch(92% 0.10 330))",
              borderRadius: 999, transition: "width 1s linear",
            }}/>
          </div>
          <div style={{ fontSize: 11.5, opacity: 0.78, marginTop: 7 }}>
            La durée peut être prolongée, on vous préviendra bien avant la fin.
          </div>
        </div>
      </div>

      {/* ===== Quelle formule est faite pour vous ?, mini-questionnaire interactif ===== */}
      {(() => {
        const QUESTIONS = [
          { k: "size",    icon: "users",   q: "Combien de clientes différentes par mois ?", opts: [
            { v: "small", label: "Moins de 20" },
            { v: "mid",   label: "20 à 50" },
            { v: "large", label: "Plus de 50" },
          ]},
          { k: "billing", icon: "invoice", q: "Vous éditez des factures conformes ?", opts: [
            { v: "yes", label: "Oui, c'est important" },
            { v: "no",  label: "Non, juste un reçu" },
          ]},
          { k: "fid",     icon: "heart",   q: "Vous gérez des cartes de fidélité ?", opts: [
            { v: "yes", label: "Oui, j'en ai besoin" },
            { v: "no",  label: "Pas pour l'instant" },
          ]},
        ];
        const answered = QUESTIONS.filter(qq => answers[qq.k]).length;
        const progressPct = (answered / QUESTIONS.length) * 100;
        return (
      <div style={{
        marginBottom: 18, padding: "22px 26px",
        background: "linear-gradient(135deg, var(--accent-soft) 0%, oklch(98% 0.015 280) 100%)",
        border: "1px solid var(--accent-soft-2)", borderRadius: 18,
      }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 12, flexWrap: "wrap", marginBottom: 4 }}>
          <div>
            <h3 style={{ margin: 0, fontFamily: "var(--ff-display)", fontSize: 19, fontWeight: 580, letterSpacing: "-0.02em" }}>
              Quelle formule est faite pour vous&nbsp;?
            </h3>
            <p style={{ margin: "4px 0 0", fontSize: 13, color: "var(--ink-3)" }}>
              Trois questions rapides, on vous suggère la formule la plus adaptée.
            </p>
          </div>
          <div style={{
            display: "inline-flex", alignItems: "center", gap: 8,
            padding: "6px 12px", background: "var(--surface)",
            border: "1px solid var(--accent-soft-2)", borderRadius: 999,
            fontSize: 12, fontWeight: 600, color: "var(--accent-ink)",
          }}>
            <span style={{
              width: 40, height: 5, background: "var(--accent-soft)",
              borderRadius: 999, overflow: "hidden", display: "inline-block",
            }}>
              <span style={{
                display: "block", width: `${progressPct}%`, height: "100%",
                background: "var(--accent)", borderRadius: 999,
                transition: "width .35s cubic-bezier(.22,1,.36,1)",
              }}/>
            </span>
            {answered}/{QUESTIONS.length}
          </div>
        </div>

        <div style={{ display: "flex", flexDirection: "column", gap: 14, marginTop: 16 }}>
          {QUESTIONS.map(({ k, q, opts, icon }, qi) => {
            const isAnswered = !!answers[k];
            return (
              <div key={k} style={{
                padding: "12px 14px",
                background: "var(--surface)",
                border: `1px solid ${isAnswered ? "var(--accent-soft-2)" : "var(--line)"}`,
                borderRadius: 12,
                transition: "border-color .2s",
              }}>
                <div style={{ display: "flex", alignItems: "center", gap: 9, marginBottom: 8 }}>
                  <span style={{
                    width: 26, height: 26, borderRadius: 8, flexShrink: 0,
                    background: isAnswered ? "var(--accent)" : "var(--bg-alt)",
                    color: isAnswered ? "#fff" : "var(--ink-4)",
                    display: "inline-flex", alignItems: "center", justifyContent: "center",
                    transition: "background .2s, color .2s",
                  }}>
                    {isAnswered
                      ? <Icon name="check" size={13} stroke={2.6}/>
                      : <span style={{ fontSize: 12, fontWeight: 700 }}>{qi + 1}</span>}
                  </span>
                  <div style={{ fontSize: 13, color: "var(--ink-2)", fontWeight: 540 }}>{q}</div>
                </div>
                <div style={{ display: "flex", gap: 6, flexWrap: "wrap", paddingLeft: 35 }}>
                  {opts.map(o => {
                    const active = answers[k] === o.v;
                    return (
                      <button key={o.v} onClick={() => setAns(k, o.v)}
                        style={{
                          padding: "7px 13px",
                          background: active ? "var(--accent)" : "var(--surface)",
                          color: active ? "#fff" : "var(--ink-2)",
                          border: `1px solid ${active ? "var(--accent)" : "var(--line)"}`,
                          borderRadius: 999, fontSize: 12.5, fontWeight: active ? 600 : 540,
                          cursor: "pointer", fontFamily: "inherit",
                          transition: "background .15s, color .15s, border-color .15s, transform .12s",
                          boxShadow: active ? "0 6px 14px -6px var(--accent)" : "none",
                        }}
                        onMouseDown={e => e.currentTarget.style.transform = "scale(0.96)"}
                        onMouseUp={e => e.currentTarget.style.transform = "scale(1)"}
                        onMouseLeave={e => e.currentTarget.style.transform = "scale(1)"}>
                        {o.label}
                      </button>
                    );
                  })}
                </div>
              </div>
            );
          })}
        </div>

        {recommendId && (
          <div style={{
            marginTop: 18, padding: "14px 18px",
            background: PLAN_THEME[recommendId].grad, color: "#fff",
            borderRadius: 14,
            boxShadow: `0 12px 28px -14px ${PLAN_THEME[recommendId].glow}`,
            display: "flex", alignItems: "center", gap: 14, flexWrap: "wrap",
            animation: "cbBadgeIn .35s cubic-bezier(.22,1,.36,1) both",
          }}>
            <span style={{ fontSize: 22 }}>🎯</span>
            <div style={{ flex: 1, minWidth: 180 }}>
              <div style={{ fontSize: 11, fontWeight: 700, letterSpacing: "0.06em", textTransform: "uppercase", opacity: 0.9 }}>
                Notre suggestion
              </div>
              <div style={{ fontFamily: "var(--ff-display)", fontSize: 19, fontWeight: 600, letterSpacing: "-0.02em" }}>
                La formule <em style={{ fontStyle: "italic" }}>{recommendName}</em> colle à votre profil.
              </div>
            </div>
            <button onClick={() => setAnswers({})} style={{
              padding: "7px 13px", background: "rgba(255,255,255,0.2)", color: "#fff",
              border: "1px solid rgba(255,255,255,0.28)", borderRadius: 999,
              cursor: "pointer", fontFamily: "inherit", fontSize: 12.5, fontWeight: 540,
            }}>
              Recommencer
            </button>
          </div>
        )}
      </div>
        );
      })()}

      {/* ===== Et après la bêta ? ===== */}
      <div style={{ marginBottom: 14 }}>
        <h3 style={{ margin: 0, fontFamily: "var(--ff-display)", fontSize: 19, fontWeight: 580, letterSpacing: "-0.02em" }}>
          Et après la bêta&nbsp;?
        </h3>
        <p style={{ margin: "3px 0 0", fontSize: 13, color: "var(--ink-3)" }}>
          Vous garderez Premium, ou choisirez une formule plus légère. Sans engagement, changement à tout moment.
        </p>
      </div>

      {/* 3 formules, un gros bandeau par formule, empilés */}
      <div style={{ display: "flex", flexDirection: "column", gap: 14, marginBottom: 8 }}>
        {SUBSCRIPTION_PLANS.map(p => {
          const th = PLAN_THEME[p.id];
          const isCurrent = p.id === "premium";
          const isReco = recommendId === p.id;
          return (
            <div key={p.id} style={{
              position: "relative", overflow: "hidden",
              background: th.grad, color: "#fff",
              borderRadius: 20, padding: "24px 26px",
              boxShadow: isReco
                ? `0 18px 44px -16px ${th.glow}, 0 0 0 2px ${th.glow}`
                : `0 18px 44px -22px ${th.glow}`,
              transition: "box-shadow .3s",
            }}>
              <div aria-hidden style={{ position: "absolute", top: -60, right: -30, width: 180, height: 180, borderRadius: "50%", background: "rgba(255,255,255,0.10)" }}/>
              <div aria-hidden style={{ position: "absolute", bottom: -90, right: 130, width: 150, height: 150, borderRadius: "50%", background: "rgba(255,255,255,0.06)" }}/>

              <div className="cb-plan-banner" style={{
                position: "relative", display: "flex", gap: 26,
                flexWrap: "wrap", alignItems: "flex-start",
              }}>
                {/* Identité + prix + CTA */}
                <div style={{ flex: "1 1 220px", minWidth: 200 }}>
                  {(isCurrent || p.highlight || isReco) && (
                    <span style={{
                      display: "inline-flex", alignItems: "center", gap: 5,
                      padding: "3px 10px", marginBottom: 9,
                      background: isReco ? "#fff" : "rgba(255,255,255,0.22)",
                      color: isReco ? "var(--ink)" : "#fff",
                      border: isReco ? "1px solid #fff" : "1px solid rgba(255,255,255,0.3)",
                      borderRadius: 999, fontSize: 10.5, fontWeight: 700,
                      textTransform: "uppercase", letterSpacing: "0.05em",
                      animation: isReco ? "cbBadgeIn .35s cubic-bezier(.22,1,.36,1) both" : "none",
                    }}>
                      {isReco
                        ? <>🎯 Recommandée pour vous</>
                        : isCurrent
                          ? <><Icon name="check" size={11} stroke={3}/> Votre formule actuelle</>
                          : <>★ Le plus choisi</>}
                    </span>
                  )}
                  <div style={{ fontSize: 11.5, opacity: 0.85, fontWeight: 540, textTransform: "uppercase", letterSpacing: "0.05em" }}>
                    {p.tagline}
                  </div>
                  <div style={{ fontFamily: "var(--ff-display)", fontSize: 30, fontWeight: 600, marginTop: 2, letterSpacing: "-0.025em" }}>
                    {p.name}
                  </div>
                  <div style={{ marginTop: 10 }}>
                    <span style={{ fontFamily: "var(--ff-display)", fontSize: 42, fontWeight: 600, letterSpacing: "-0.04em" }}>
                      {p.price === 0 ? "0" : p.price.toString().replace(".", ",")}
                    </span>
                    <span style={{ fontSize: 18, fontWeight: 600, marginLeft: 3 }}>€</span>
                    <span style={{ fontSize: 13, opacity: 0.82, marginLeft: 7 }}>
                      {p.price === 0 ? "toujours gratuit" : "/ mois"}
                    </span>
                  </div>
                  <button
                    onClick={() => isCurrent
                      ? showToast("C'est déjà votre formule pendant la bêta ✨")
                      : showToast("Bientôt disponible, on vous préviendra !")}
                    className="cb-press-feedback"
                    style={{
                      marginTop: 16, width: "100%", maxWidth: 280, padding: "12px",
                      background: "#fff", color: "var(--ink)",
                      border: "none", borderRadius: 12, cursor: "pointer", fontFamily: "inherit",
                      fontSize: 13.5, fontWeight: 600,
                    }}>
                    {isCurrent ? "✓ Votre formule actuelle" : p.cta}
                  </button>
                </div>

                {/* Features */}
                <div style={{
                  flex: "2 1 320px",
                  display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(215px, 1fr))", gap: 8,
                }}>
                  {p.features.map(f => {
                    const ok = f.kind === "check";
                    return (
                      <div key={f.t} style={{
                        display: "flex", alignItems: "center", gap: 9,
                        padding: "9px 12px",
                        background: ok ? "rgba(255,255,255,0.15)" : "rgba(255,255,255,0.05)",
                        border: "1px solid rgba(255,255,255,0.18)", borderRadius: 10,
                        opacity: ok ? 1 : 0.6,
                      }}>
                        <span style={{
                          width: 20, height: 20, borderRadius: 50, flexShrink: 0,
                          background: "rgba(255,255,255,0.22)",
                          display: "inline-flex", alignItems: "center", justifyContent: "center",
                        }}>
                          <Icon name={ok ? "check" : "close"} size={11} stroke={2.8}/>
                        </span>
                        <span style={{
                          fontSize: 12.5, fontWeight: 500, lineHeight: 1.35,
                          textDecoration: ok ? "none" : "line-through",
                        }}>{f.t}</span>
                      </div>
                    );
                  })}
                </div>
              </div>
            </div>
          );
        })}
      </div>
    </div>
  );
};

/* ================================================================
   MODAL FORMS
================================================================ */
const ClientFormModal = ({ modal, onClose, data, actions }) => {
  const open = modal && modal.type === "client";
  const editing = open && modal.ctx && modal.ctx.id ? findClient(data, modal.ctx.id) : null;
  const [form, setForm] = React.useState({ name: "", email: "", phone: "", birthday: "", social: "", notes: "" });

  React.useEffect(() => {
    if (!open) return;
    if (editing) {
      setForm({
        name: editing.name, email: editing.email || "",
        phone: editing.phone || "", birthday: editing.birthday || "",
        social: editing.social || "", notes: editing.notes || "",
      });
    } else {
      setForm({ name: "", email: "", phone: "", birthday: "", social: "", notes: "" });
    }
  }, [open, editing && editing.id]);

  const submit = () => {
    if (!form.name.trim()) { showToast("Le nom est requis", "warn"); return; }
    if (editing) actions.updateClient(editing.id, form);
    else actions.addClient(form);
    onClose();
  };

  return (
    <Modal open={!!open} onClose={onClose}
      title={editing ? `Modifier, ${editing.name}` : "Nouveau client"}
      footer={
        <>
          {editing && (
            <button className="btn btn-ghost cb-btn-destructive"
              onClick={() => { actions.deleteClient(editing.id); onClose(); }}>Supprimer</button>
          )}
          <button className="btn btn-ghost" onClick={onClose}>Annuler</button>
          <button className="btn btn-primary" onClick={submit}>{editing ? "Enregistrer" : "Créer la fiche"}</button>
        </>
      }>
      <div style={{ display: "grid", gap: 14 }}>
        <FormField label="Nom complet" value={form.name} onChange={v => setForm({ ...form, name: v })} placeholder="Léa Morel" required autoFocus/>
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 14 }}>
          <FormField label="Email" type="email" value={form.email} onChange={v => setForm({ ...form, email: v })} placeholder="lea@exemple.fr"/>
          <FormField label="Téléphone" value={form.phone} onChange={v => setForm({ ...form, phone: v })} placeholder="06 12 34 56 78"/>
        </div>
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 14 }}>
          <FormField label="Anniversaire (optionnel)" value={form.birthday} onChange={v => setForm({ ...form, birthday: v })} placeholder="JJ/MM, ex. 14/03"/>
          <FormField label={`${CB_SOCIAL_LABEL[(data.bookingSettings && data.bookingSettings.preferredSocial) || "instagram"] || "Réseau social"} (optionnel)`}
            value={form.social} onChange={v => setForm({ ...form, social: v })}
            placeholder={CB_SOCIAL_PLACEHOLDER[(data.bookingSettings && data.bookingSettings.preferredSocial) || "instagram"] || "@pseudo"}/>
        </div>
        <FormTextarea label="Notes (allergies, préférences…)" value={form.notes} onChange={v => setForm({ ...form, notes: v })} placeholder="Préfère les couleurs nude." rows={3}/>
      </div>
    </Modal>
  );
};

/* ============ Fiche client (lecture), modale détaillée ============ */
// Corps de la fiche client, partagé entre modale (mobile) et panneau lateral (desktop)
const ClientDetailBody = ({ client, data }) => {
  const totalVisits = data.fideliteRules.visits;
  const todayOff = cbTodayOffset();
  const allAppts = (data.appointments || [])
    .filter(a => a.clientId === client.id)
    .slice()
    .sort((a, b) => a.day - b.day || a.h - b.h);
  const upcoming = allAppts.filter(a => a.day >= todayOff && !a.done);
  const past = allAppts.filter(a => a.day < todayOff || a.done).slice(-5).reverse();
  const upPlanned = upcoming.reduce((s, a) => {
    const svc = (data.services || []).find(x => x.id === a.serviceId);
    return s + (svc ? Number(svc.price) || 0 : 0);
  }, 0);

  const callPhone = () => { if (client.phone) window.location.href = `tel:${client.phone.replace(/\s+/g, "")}`; };
  const mailTo = () => { if (client.email) window.location.href = `mailto:${client.email}`; };
  const whatsApp = () => {
    if (!client.phone) { showToast("Pas de téléphone enregistré", "warn"); return; }
    const num = client.phone.replace(/\D/g, "");
    window.open(`https://wa.me/${num.startsWith("0") ? "33" + num.slice(1) : num}`, "_blank");
  };

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
      {/* Header : avatar + nom + contacts rapides */}
      <div style={{ display: "flex", alignItems: "center", gap: 16 }}>
        <Avatar client={client} size={64}/>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontFamily: "var(--ff-display)", fontSize: 22, fontWeight: 600, letterSpacing: "-0.022em", color: "var(--ink)" }}>
            {client.name}
          </div>
          <div style={{ fontSize: 13, color: "var(--ink-3)", marginTop: 3, display: "flex", gap: 12, flexWrap: "wrap" }}>
            {client.email && <span>✉️ {client.email}</span>}
            {client.phone && <span>📞 {client.phone}</span>}
            {client.birthday && <span>🎂 {client.birthday}</span>}
          </div>
        </div>
      </div>

      {(client.phone || client.email) && (
        <div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>
          {client.phone && (
            <button onClick={callPhone} className="btn btn-sm btn-ghost">
              <Icon name="phone" size={13}/> Appeler
            </button>
          )}
          {client.phone && (
            <button onClick={whatsApp} className="btn btn-sm btn-ghost">
              💬 WhatsApp
            </button>
          )}
          {client.email && (
            <button onClick={mailTo} className="btn btn-sm btn-ghost">
              <Icon name="mail" size={13}/> Email
            </button>
          )}
        </div>
      )}

      <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(140px, 1fr))", gap: 10 }}>
        {[
          { label: "Visites", value: client.visits || 0, tone: "var(--accent)" },
          { label: "Total dépensé", value: fmtEUR(client.spent || 0), tone: "oklch(55% 0.12 160)" },
          { label: "RDV à venir", value: upcoming.length, tone: "oklch(60% 0.17 30)" },
          { label: "Fidélité", value: `${client.fid || 0}/${totalVisits}`, tone: "oklch(60% 0.16 340)" },
        ].map(s => (
          <div key={s.label} style={{
            padding: "12px 14px", background: "var(--surface)",
            border: "1px solid var(--line)", borderRadius: 11,
            borderLeft: `3px solid ${s.tone}`,
          }}>
            <div style={{ fontFamily: "var(--ff-display)", fontSize: 20, fontWeight: 600, letterSpacing: "-0.022em", color: "var(--ink)" }}>
              {s.value}
            </div>
            <div style={{ fontSize: 12.5, color: "var(--ink-3)", marginTop: 2 }}>{s.label}</div>
          </div>
        ))}
      </div>

      <div>
        <div style={{ display: "flex", justifyContent: "space-between", marginBottom: 5, fontSize: 12.5, color: "var(--ink-3)" }}>
          <span>Carte de fidélité</span>
          <span>{client.fid || 0} / {totalVisits} visites</span>
        </div>
        <div style={{ height: 6, background: "var(--bg-alt)", borderRadius: 999, overflow: "hidden" }}>
          <div style={{
            width: `${Math.min(100, ((client.fid || 0) / totalVisits) * 100)}%`,
            height: "100%",
            background: (client.fid || 0) >= totalVisits ? "var(--sage)" : "var(--accent)",
            borderRadius: 999, transition: "width .3s",
          }}/>
        </div>
      </div>

      {upcoming.length > 0 && (
        <div>
          <div style={{ fontSize: 12.5, fontWeight: 580, color: "var(--ink-2)", marginBottom: 6 }}>
            Prochains RDV ({upcoming.length}{upPlanned > 0 ? ` · ${fmtEUR(upPlanned)} prévus` : ""})
          </div>
          <div style={{ display: "flex", flexDirection: "column", gap: 5 }}>
            {upcoming.slice(0, 4).map(a => {
              const svc = (data.services || []).find(x => x.id === a.serviceId);
              const d = window.cbDateForOffset ? window.cbDateForOffset(a.day) : new Date();
              return (
                <div key={a.id} style={{
                  padding: "8px 12px", background: "var(--bg-alt)",
                  border: "1px solid var(--line)", borderRadius: 8,
                  display: "flex", justifyContent: "space-between", alignItems: "center", gap: 10,
                  fontSize: 12.5,
                }}>
                  <span style={{ color: "var(--ink-2)" }}>
                    {d.toLocaleDateString("fr-FR", { weekday: "short", day: "numeric", month: "short" })} · {fmtH(a.h)}
                  </span>
                  <span style={{ color: "var(--ink-3)" }}>{svc ? svc.name : "—"}</span>
                </div>
              );
            })}
          </div>
        </div>
      )}

      {past.length > 0 && (
        <div>
          <div style={{ fontSize: 12.5, fontWeight: 580, color: "var(--ink-2)", marginBottom: 6 }}>
            Dernières visites
          </div>
          <div style={{ display: "flex", flexDirection: "column", gap: 5 }}>
            {past.map(a => {
              const svc = (data.services || []).find(x => x.id === a.serviceId);
              const d = window.cbDateForOffset ? window.cbDateForOffset(a.day) : new Date();
              return (
                <div key={a.id} style={{
                  padding: "8px 12px", background: "var(--bg-alt)",
                  border: "1px solid var(--line)", borderRadius: 8,
                  display: "flex", justifyContent: "space-between", alignItems: "center", gap: 10,
                  fontSize: 12.5, opacity: a.done ? 1 : 0.7,
                }}>
                  <span style={{ color: "var(--ink-3)" }}>
                    {d.toLocaleDateString("fr-FR", { day: "numeric", month: "short", year: "numeric" })}
                  </span>
                  <span style={{ color: "var(--ink-2)" }}>{svc ? `${svc.name} · ${fmtEUR(svc.price)}` : "—"}</span>
                </div>
              );
            })}
          </div>
        </div>
      )}

      {client.notes && (
        <div>
          <div style={{ fontSize: 12.5, fontWeight: 580, color: "var(--ink-2)", marginBottom: 6 }}>Notes</div>
          <div style={{
            padding: "10px 12px",
            background: "linear-gradient(135deg, oklch(98% 0.025 95) 0%, oklch(97% 0.04 95) 100%)",
            border: "1px solid oklch(90% 0.06 95)", borderRadius: 9,
            fontSize: 13, color: "var(--ink-2)", lineHeight: 1.5,
            whiteSpace: "pre-wrap",
          }}>
            {client.notes}
          </div>
        </div>
      )}

      {client.tags && client.tags.length > 0 && (
        <div style={{ display: "flex", flexWrap: "wrap", gap: 5 }}>
          {client.tags.map(t => (
            <span key={t} style={{
              fontSize: 11.5, padding: "3px 9px",
              background: "var(--accent-soft)", color: "var(--accent-ink)",
              borderRadius: 999, fontWeight: 540,
            }}>{t}</span>
          ))}
        </div>
      )}
    </div>
  );
};

const ClientDetailModal = ({ modal, onClose, data, actions, openModal }) => {
  const open = modal && modal.type === "client-detail";
  const client = open && modal.ctx && modal.ctx.id ? findClient(data, modal.ctx.id) : null;
  if (!open || !client) return null;
  return (
    <Modal open={true} onClose={onClose} title={`Fiche de ${client.name}`}
      footer={
        <>
          <button className="btn btn-ghost cb-btn-destructive"
            onClick={() => { if (window.confirm(`Supprimer ${client.name} ?`)) { actions.deleteClient(client.id); onClose(); } }}>
            Supprimer
          </button>
          <button className="btn btn-ghost" onClick={onClose}>Fermer</button>
          <button className="btn btn-primary"
            onClick={() => { onClose(); setTimeout(() => openModal("client", { id: client.id }), 0); }}>
            <Icon name="settings" size={13}/> Modifier
          </button>
        </>
      }>
      <ClientDetailBody client={client} data={data}/>
    </Modal>
  );
};

const AppointmentFormModal = ({ modal, onClose, data, actions }) => {
  const open = modal && modal.type === "appointment";
  const ctx = (modal && modal.ctx) || {};
  const [form, setForm] = React.useState({
    clientId: "", serviceId: "", day: cbTodayOffset(), h: 9, d: 1, color: "accent",
  });

  React.useEffect(() => {
    if (!open) return;
    const initialSvc = ctx.serviceId
      ? data.services.find(s => s.id === ctx.serviceId)
      : data.services[0];
    setForm({
      clientId: ctx.clientId || (data.clients[0] ? data.clients[0].id : ""),
      serviceId: ctx.serviceId || (data.services[0] ? data.services[0].id : ""),
      day: ctx.day != null ? ctx.day : cbTodayOffset(),
      h:   ctx.h   != null ? ctx.h   : 9,
      d: initialSvc ? Number(initialSvc.duration) || 1 : 1,
      color: "accent",
    });
  }, [open, ctx.day, ctx.h, ctx.clientId, ctx.serviceId]);

  // Quand on change la prestation, on récupère sa durée par défaut.
  // L'utilisateur peut ensuite modifier librement via le picker minutes.
  const onServiceChange = (newId) => {
    const svc = data.services.find(s => s.id === newId);
    setForm(f => ({
      ...f,
      serviceId: newId,
      d: svc ? Number(svc.duration) || 1 : f.d,
    }));
  };

  const submit = () => {
    if (!form.clientId || !form.serviceId) { showToast("Cliente et prestation sont requises", "warn"); return; }
    actions.addAppointment({ ...form, d: Number(form.d) || 1 });
    onClose();
  };

  const hourOptions = [];
  for (let h = 8; h < 19; h += 0.25) {
    hourOptions.push([h.toString(), fmtH(h)]);
  }

  // Raccourcis de durée (heures). Le champ libre h+min en-dessous accepte
  // n'importe quelle durée (utile pour les prestations longues : 4 h, 5 h…).
  const durationPresets = [0.25, 0.5, 0.75, 1, 1.5, 2, 3, 4];
  const durHours = Math.floor(form.d || 0);
  const durMins = Math.round(((form.d || 0) - durHours) * 60);
  const fmtPreset = (h) => h < 1
    ? `${Math.round(h * 60)} min`
    : (h % 1 === 0 ? `${h} h` : `${Math.floor(h)} h ${Math.round((h % 1) * 60)}`);

  return (
    <Modal open={!!open} onClose={onClose}
      title="Nouveau rendez-vous"
      footer={
        <>
          <button className="btn btn-ghost" onClick={onClose}>Annuler</button>
          <button className="btn btn-primary" onClick={submit}>Ajouter au planning</button>
        </>
      }>
      <div style={{ display: "grid", gap: 14 }}>
        <FormSelect label="Cliente" value={form.clientId} onChange={v => setForm({ ...form, clientId: v })} required
          options={data.clients.length === 0 ? [["", ", Aucun client. Ajoutez-en d'abord —"]] : data.clients.map(c => [c.id, c.name])}/>
        <FormSelect label="Prestation" value={form.serviceId} onChange={onServiceChange} required
          options={data.services.map(s => [s.id, `${s.name}, ${fmtEUR(s.price)}`])}/>
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 14 }}>
          <FormSelect label="Jour" value={String(form.day)} onChange={v => setForm({ ...form, day: parseInt(v, 10) })}
            options={DAY_LABELS.map((d, i) => [String(_AGENDA_MONDAY_OFFSET + i), d])}/>
          <FormSelect label="Heure" value={String(form.h)} onChange={v => setForm({ ...form, h: parseFloat(v) })}
            options={hourOptions}/>
        </div>
        <div>
          <label style={fieldLabelStyle}>Durée</label>
          <div style={{ display: "flex", gap: 6, flexWrap: "wrap", marginBottom: 8 }}>
            {durationPresets.map(h => {
              const on = Math.abs((form.d || 0) - h) < 0.005;
              return (
                <button key={h} type="button" onClick={() => setForm({ ...form, d: h })} style={{
                  padding: "6px 11px", borderRadius: 999, cursor: "pointer", fontFamily: "inherit",
                  fontSize: 12.5, fontWeight: on ? 640 : 520,
                  background: on ? "var(--accent)" : "var(--surface)",
                  color: on ? "#fff" : "var(--ink-2)",
                  border: on ? "1px solid var(--accent)" : "1px solid var(--line)",
                }}>{fmtPreset(h)}</button>
              );
            })}
          </div>
          <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
            <span style={{ fontSize: 12.5, color: "var(--ink-3)" }}>ou exact :</span>
            <input type="number" min="0" max="23" value={durHours}
              onChange={e => {
                const h = Math.max(0, Math.min(23, parseInt(e.target.value, 10) || 0));
                setForm({ ...form, d: h + durMins / 60 });
              }}
              style={{ ...fieldInputStyle, width: 64 }}/>
            <span style={{ fontSize: 13, color: "var(--ink-2)" }}>h</span>
            <input type="number" min="0" max="59" step="5" value={durMins}
              onChange={e => {
                const m = Math.max(0, Math.min(59, parseInt(e.target.value, 10) || 0));
                setForm({ ...form, d: durHours + m / 60 });
              }}
              style={{ ...fieldInputStyle, width: 64 }}/>
            <span style={{ fontSize: 13, color: "var(--ink-2)" }}>min</span>
          </div>
          {/* Indicateur du délai (battement) automatique selon la prestation
              choisie : explicite que le créneau suivant sera bloqué. */}
          {(() => {
            const svc = data.services.find(s => s.id === form.serviceId);
            const svcBuf = svc && svc.bufferMin != null ? Math.max(0, svc.bufferMin) : null;
            const globalBuf = (data.business && data.business.policyBufferMin) || 0;
            const buf = svcBuf != null ? svcBuf : globalBuf;
            if (!buf) return null;
            return (
              <div style={{
                marginTop: 8, display: "flex", alignItems: "center", gap: 8,
                padding: "7px 12px", background: "var(--accent-soft)",
                border: "1px solid var(--accent-soft-2)", borderRadius: 8,
                fontSize: 12, color: "var(--accent-ink)",
              }}>
                <Icon name="clock" size={13}/>
                <span>+ <strong>{buf} min</strong> de battement après ce RDV
                  {svcBuf != null ? " (réglé sur la prestation)" : " (délai global)"} —
                  le créneau suivant sera automatiquement bloqué.</span>
              </div>
            );
          })()}
        </div>
        <FormSelect label="Couleur" value={form.color} onChange={v => setForm({ ...form, color: v })} options={COLOR_CHOICES}/>
      </div>
    </Modal>
  );
};

const InvoiceFormModal = ({ modal, onClose, data, actions }) => {
  const open = modal && modal.type === "invoice";
  const [form, setForm] = React.useState({ clientId: "", designation: "", amount: "", paid: false });

  React.useEffect(() => {
    if (!open) return;
    setForm({
      clientId: data.clients[0] ? data.clients[0].id : "",
      designation: "", amount: "", paid: false,
    });
  }, [open]);

  const submit = () => {
    const amt = parseFloat((form.amount || "").replace(",", "."));
    if (!form.clientId) { showToast("Choisissez un client", "warn"); return; }
    if (!form.designation.trim()) { showToast("Désignation requise (description de la prestation)", "warn"); return; }
    if (!amt || amt <= 0) { showToast("Montant invalide", "warn"); return; }
    actions.addInvoice({
      clientId: form.clientId,
      designation: form.designation.trim(),
      amount: amt,
      paid: form.paid,
    });
    onClose();
  };

  return (
    <Modal open={!!open} onClose={onClose} title="Nouvelle facture"
      footer={
        <>
          <button className="btn btn-ghost" onClick={onClose}>Annuler</button>
          <button className="btn btn-primary" onClick={submit}>Créer</button>
        </>
      }>
      <div style={{ display: "grid", gap: 14 }}>
        <FormSelect label="Cliente" value={form.clientId} onChange={v => setForm({ ...form, clientId: v })} required
          options={data.clients.map(c => [c.id, c.name])}/>
        <FormField label="Désignation de la prestation" value={form.designation} onChange={v => setForm({ ...form, designation: v })}
          placeholder="ex : Coupe + brushing femme" required/>
        <FormField label="Montant TTC (€)" value={form.amount} onChange={v => setForm({ ...form, amount: v })} placeholder="45,00" required/>
        <label style={{ display: "flex", alignItems: "center", gap: 10, fontSize: 13.5, cursor: "pointer" }}>
          <input type="checkbox" checked={form.paid} onChange={e => setForm({ ...form, paid: e.target.checked })}
            style={{ accentColor: "var(--accent)", width: 16, height: 16 }}/>
          Marquer comme payée
        </label>
      </div>
    </Modal>
  );
};

const StockFormModal = ({ modal, onClose, actions }) => {
  const open = modal && modal.type === "stock";
  const editItem = open && modal.ctx && modal.ctx.edit;
  const [form, setForm] = React.useState({ name: "", qty: "", min: "", price: "", category: "", supplier: "" });

  React.useEffect(() => {
    if (!open) return;
    if (editItem) {
      setForm({
        name: editItem.name || "",
        qty: String(editItem.qty ?? ""),
        min: String(editItem.min ?? ""),
        price: editItem.price != null ? String(editItem.price).replace(".", ",") : "",
        category: editItem.category || "",
        supplier: editItem.supplier || "",
      });
    } else {
      setForm({ name: "", qty: "", min: "", price: "", category: "", supplier: "" });
    }
  }, [open, editItem]);

  const submit = () => {
    if (!form.name.trim()) { showToast("Nom du produit requis", "warn"); return; }
    const qty = parseInt(form.qty, 10) || 0;
    const min = parseInt(form.min, 10) || 0;
    const price = parseFloat((form.price || "").replace(",", ".")) || 0;
    const payload = {
      name: form.name.trim(), qty, min, price,
      category: form.category || "", supplier: form.supplier.trim(),
    };
    if (editItem) actions.updateStockItem(editItem.id, payload);
    else actions.addStockItem(payload);
    onClose();
  };

  return (
    <Modal open={!!open} onClose={onClose} title={editItem ? "Modifier le produit" : "Nouveau produit"}
      footer={
        <>
          <button className="btn btn-ghost" onClick={onClose}>Annuler</button>
          <button className="btn btn-primary" onClick={submit}>{editItem ? "Enregistrer" : "Ajouter"}</button>
        </>
      }>
      <div style={{ display: "grid", gap: 14 }}>
        <FormField label="Nom du produit" value={form.name} onChange={v => setForm({ ...form, name: v })} placeholder="Base coat OPI" required autoFocus/>
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 10 }}>
          <FormField label="Stock actuel" value={form.qty} onChange={v => setForm({ ...form, qty: v })} placeholder="10"/>
          <FormField label="Seuil mini" value={form.min} onChange={v => setForm({ ...form, min: v })} placeholder="3"/>
          <FormField label="Prix (€)" value={form.price} onChange={v => setForm({ ...form, price: v })} placeholder="12,90"/>
        </div>
        <FormSelect label="Catégorie" value={form.category} onChange={v => setForm({ ...form, category: v })}
          options={[["", ", Sans catégorie —"], ...STOCK_CATEGORIES.map(c => [c, c])]}/>
        <FormField label="Fournisseur (optionnel)" value={form.supplier} onChange={v => setForm({ ...form, supplier: v })} placeholder="ex. Nailish, fournisseur pro…"/>
      </div>
    </Modal>
  );
};

const PromoFormModal = ({ modal, onClose, actions }) => {
  const open = modal && modal.type === "promo";
  const [form, setForm] = React.useState({ title: "", segment: "", detail: "", status: "Brouillon" });

  React.useEffect(() => {
    if (!open) return;
    setForm({ title: "", segment: "", detail: "", status: "Brouillon" });
  }, [open]);

  const submit = () => {
    if (!form.title.trim()) { showToast("Titre requis", "warn"); return; }
    actions.addPromo({
      title: form.title.trim(),
      segment: form.segment.trim() || "Toutes les clients",
      detail: form.detail.trim() || "À configurer",
      status: form.status,
    });
    onClose();
  };

  return (
    <Modal open={!!open} onClose={onClose} title="Nouvelle promotion"
      footer={
        <>
          <button className="btn btn-ghost" onClick={onClose}>Annuler</button>
          <button className="btn btn-primary" onClick={submit}>Créer la campagne</button>
        </>
      }>
      <div style={{ display: "grid", gap: 14 }}>
        <FormField label="Titre" value={form.title} onChange={v => setForm({ ...form, title: v })} placeholder="Anniversaires avril" required autoFocus/>
        <FormField label="Cible / segment" value={form.segment} onChange={v => setForm({ ...form, segment: v })} placeholder="Clientes fidèles, VIP…"/>
        <FormField label="Détail (ex : -15%, SMS samedi 10h…)" value={form.detail} onChange={v => setForm({ ...form, detail: v })}/>
        <FormSelect label="Statut" value={form.status} onChange={v => setForm({ ...form, status: v })}
          options={[["Brouillon", "Brouillon"], ["Programmé", "Programmé"], ["Actif", "Actif"]]}/>
      </div>
    </Modal>
  );
};

const FideliteRulesModal = ({ modal, onClose, data, actions }) => {
  const open = modal && modal.type === "fidelite-rules";
  const [visits, setVisits] = React.useState(data.fideliteRules.visits);
  const [rewardLabel, setRewardLabel] = React.useState(data.fideliteRules.rewardLabel);

  React.useEffect(() => {
    if (!open) return;
    setVisits(data.fideliteRules.visits);
    setRewardLabel(data.fideliteRules.rewardLabel);
  }, [open]);

  const submit = () => {
    const v = parseInt(visits, 10);
    if (!v || v < 2) { showToast("Nombre de visites invalide", "warn"); return; }
    actions.updateFideliteRules({ visits: v, rewardLabel });
    onClose();
  };

  return (
    <Modal open={!!open} onClose={onClose} title="Règles de fidélité"
      footer={
        <>
          <button className="btn btn-ghost" onClick={onClose}>Annuler</button>
          <button className="btn btn-primary" onClick={submit}>Enregistrer</button>
        </>
      }>
      <div style={{ display: "grid", gap: 14 }}>
        <FormField label="Nombre de visites pour obtenir la récompense" value={String(visits)} onChange={v => setVisits(v)}/>
        <FormField label="Récompense" value={rewardLabel} onChange={setRewardLabel} placeholder="1 prestation offerte"/>
      </div>
    </Modal>
  );
};

Object.assign(window, { AppShell });
