/* ClientBase, shared UI: Logo, Nav, Footer, Icons + shared hooks */

// Mets à jour cette constante à chaque release + ajoute une entrée dans CB_CHANGELOG.
const CB_VERSION = "2.0";

/* =============================================================
   EASTER EGG — petit clin d'œil dans la console pour les curieux
   et les devs (et les recruteurs qui ouvrent les DevTools).
   Affiché une seule fois par session. Cohérent avec l'univers
   ClientBase (la marque a un côté outil-soigné pour artisans).
   ============================================================= */
(() => {
  try {
    if (window.__cbHelloShown) return;
    window.__cbHelloShown = true;
    // Style "git log" : ressemble à un commit, cohabite naturellement
    // avec la console de dev. Aucun logo, juste du texte tracé propre,
    // façon terminal. Hash = couleur de marque (5a50e6), date = today,
    // message = pitch + contact.
    const v = (typeof CB_VERSION !== "undefined" ? CB_VERSION : "2.x");
    const monoBase = "font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12.5px;line-height:1.55";
    const hashStyle = "color:#D4A017;font-weight:600;" + monoBase;     // jaune git
    const headStyle = "color:#5A50E6;font-weight:700;" + monoBase;     // indigo HEAD
    const labelStyle = "color:#6b6f80;" + monoBase;                    // gris label
    const valueStyle = "color:#0F1020;font-weight:600;" + monoBase;    // noir valeur
    const bodyStyle  = "color:#0F1020;" + monoBase;
    const dimStyle   = "color:#6b6f80;" + monoBase;
    const linkStyle  = "color:#5A50E6;font-weight:600;" + monoBase;

    console.log("%ccommit 5a50e6c1b4a5e6f7d8a9b0c1d2e3f4a5b6c7d8e9 %c(HEAD -> main)", hashStyle, headStyle);
    console.log("%cAuthor: %cclientbase.fr <clientbase.fr@gmail.com>", labelStyle, valueStyle);
    console.log("%cDate:   %caujourd'hui", labelStyle, valueStyle);
    console.log(" ");
    console.log("%c    v" + v + " — juste bosser, pas configurer.", bodyStyle);
    console.log(" ");
    console.log("%c    Bug ?  %cclientbase.fr@gmail.com", dimStyle, linkStyle);
    console.log("%c    Dev ?  %con cherche des copains, on lit tout 💌", dimStyle, dimStyle);
  } catch {}
})();

/* =============================================================
   AUDIENCE, pro vs client (vitrine duale)
   Persisté en localStorage pour retenir la préférence entre visites.
   Source de vérité : la page rendue (proHome vs clientHome). Le helper
   garde la valeur en cache pour le boot avant que React démarre.
   ============================================================= */
const CB_AUDIENCE_KEY = "cb_audience";
const cbAudience = {
  get() {
    try { return localStorage.getItem(CB_AUDIENCE_KEY) || "pro"; }
    catch { return "pro"; }
  },
  set(v) {
    if (v !== "pro" && v !== "client") return;
    try { localStorage.setItem(CB_AUDIENCE_KEY, v); } catch {}
  },
};
// Exposé pour le bootstrap (index.html), sert à choisir la page par défaut
// quand l'utilisateur arrive sur "/" sans path explicite.
try { window.cbAudience = cbAudience; } catch {}

const CB_CHANGELOG = [
  {
    version: "2.0",
    date: "2026-05-16",
    tag: "Refonte majeure",
    title: "Espace pro entièrement repensé + comptes client",
    items: [
      "Inscription / connexion : choix Pro ou Client, écran de confirmation email partagé",
      "Page « Ma page » : éditeur visuel avec aperçu temps réel = vraie page publique",
      "Personnalisation étendue : couleurs principales, couleur de fond, bannière, galerie, présentation longue, sections visibles/masquables",
      "Frame téléphone d'aperçu redessinée : status bar live, contenu jamais coupé par l'encoche",
      "Routing persistant : `/app#agenda`, `/app#ma-page`… les modules survivent au refresh",
      "Site vitrine recoloré : chaque feature reçoit une teinte (rose, teal, ambre, lavande, orange) en plus de l'indigo",
      "Indicateur « Enregistré ✓ » sous l'aperçu Ma page pour rassurer sur la sync",
      "Template email premium pour la confirmation d'inscription (à coller dans Supabase)",
    ],
  },
  {
    version: "1.1",
    date: "2026-04-28",
    tag: "Stabilisation",
    title: "Cycle de bug fixes, pas de nouvelles features",
    items: [
      "V1 clôturée. Ce cycle n'ajoute plus de fonctionnalités : on stabilise.",
      "Corrections de bugs remontés par les bêta-testeurs et polish UI.",
      "Tout retour, signalement de bug ou incohérence : clientbase.fr@gmail.com.",
    ],
  },
  {
    version: "1.0",
    date: "2026-04-28",
    tag: "Bêta, clôturée",
    title: "Première version complète",
    items: [
      "Comptes Supabase + synchro temps réel multi-appareils",
      "Page publique de réservation (?book=) via lien partageable + slug personnalisable",
      "Pseudo réseau social du client à la réservation (Instagram, Facebook, TikTok, Snapchat, WhatsApp)",
      "Anniversaires clients épinglés sur la home + champ JJ/MM dans la fiche",
      "Greeting dynamique (matinée, déjeuner, après-midi, soirée) + fête / saint du jour",
      "Mode pause / vacances, bandeau doux sur la home, blocage des réservations en ligne",
      "Onboarding 3 cartes à la première connexion",
      "Toast Annuler 5s sur les actions importantes (RDV fait & facturé, paiement encaissé)",
      "Sparkline CA animée + compteur RDV en ease-out",
      "Pages légales (RGPD, CGU, mentions, cookies), facture conforme avec mentions légales",
      "Mot de passe oublié, validation stricte, RGPD export et suppression compte",
      "Simulateur de gains, page Guide interactif, page d'invitation personnalisée",
      "Agenda mobile (strip 60 jours), marquer fait & facturer en 1 clic",
    ],
  },
  {
    version: "2.1",
    date: "À venir",
    tag: "Bientôt",
    title: "Backend compte client + rappels SMS",
    items: [
      "Vraie auth Supabase pour les comptes client (table customers + RLS)",
      "Dashboard client centralisé : RDV chez tous vos pros, historique, fidélité",
      "Rappels SMS automatiques 24 h avant chaque RDV (plan Business)",
      "Avis Google synchronisés sur votre page publique",
    ],
    upcoming: true,
  },
  {
    version: "3.0",
    date: "À venir",
    tag: "Objectif 2026",
    title: "App native iOS & Android",
    items: [
      "Application mobile publiée sur l'App Store (iOS)",
      "Application mobile publiée sur le Play Store (Android)",
      "Notifications push natives (nouvelle réservation, RDV du jour, paiement reçu)",
      "Mode hors-ligne complet : consultez votre agenda même sans réseau",
      "Lancement commercial officiel, fin de la bêta, structure juridique déclarée",
    ],
    upcoming: true,
  },
];

const useIsMobile = (bp = 720) => {
  const [isMobile, setIsMobile] = React.useState(() =>
    typeof window !== "undefined" && window.matchMedia(`(max-width: ${bp}px)`).matches
  );
  React.useEffect(() => {
    const mq = window.matchMedia(`(max-width: ${bp}px)`);
    const h = (e) => setIsMobile(e.matches);
    if (mq.addEventListener) mq.addEventListener("change", h);
    else mq.addListener(h);
    return () => {
      if (mq.removeEventListener) mq.removeEventListener("change", h);
      else mq.removeListener(h);
    };
  }, [bp]);
  return isMobile;
};

const Icon = ({ name, size = 18, stroke = 1.6, style }) => {
  const s = size;
  const props = {
    width: s, height: s, viewBox: "0 0 24 24", fill: "none",
    stroke: "currentColor", strokeWidth: stroke,
    strokeLinecap: "round", strokeLinejoin: "round",
    style,
  };
  const paths = {
    calendar: <><rect x="3" y="5" width="18" height="16" rx="2"/><path d="M3 9h18M8 3v4M16 3v4"/></>,
    users: <><circle cx="9" cy="8" r="3.5"/><path d="M2.5 20c0-3.6 2.9-6 6.5-6s6.5 2.4 6.5 6"/><circle cx="17" cy="9" r="2.5"/><path d="M21.5 18.5c0-2.5-2-4.5-4.5-4.5"/></>,
    chat: <><path d="M21 12a8 8 0 0 1-11.6 7.1L4 20l1-4.3A8 8 0 1 1 21 12z"/></>,
    heart: <><path d="M12 20s-7-4.5-9-9.2C1.8 7.1 4.1 4 7.3 4c2 0 3.5 1.1 4.7 2.6C13.2 5.1 14.7 4 16.7 4 19.9 4 22.2 7.1 21 10.8 19 15.5 12 20 12 20z"/></>,
    sparkle: <><path d="M12 3v4M12 17v4M3 12h4M17 12h4M5.6 5.6l2.8 2.8M15.6 15.6l2.8 2.8M5.6 18.4l2.8-2.8M15.6 8.4l2.8-2.8"/></>,
    chart: <><path d="M3 20V4M3 20h18"/><path d="M7 16l4-5 3 3 5-7"/></>,
    box: <><path d="M3 7l9-4 9 4v10l-9 4-9-4V7z"/><path d="M3 7l9 4 9-4M12 11v10"/></>,
    check: <path d="M5 12l4 4 10-10"/>,
    arrow: <path d="M5 12h14M13 5l7 7-7 7"/>,
    arrowUp: <path d="M7 17L17 7M7 7h10v10"/>,
    shield: <path d="M12 3l8 3v6c0 4.5-3.3 8.4-8 9-4.7-.6-8-4.5-8-9V6l8-3z"/>,
    zap: <path d="M13 2L4 14h7l-1 8 9-12h-7l1-8z"/>,
    menu: <><path d="M4 7h16M4 12h16M4 17h16"/></>,
    close: <><path d="M6 6l12 12M18 6L6 18"/></>,
    plus: <><path d="M12 5v14M5 12h14"/></>,
    search: <><circle cx="11" cy="11" r="7"/><path d="M20 20l-3.5-3.5"/></>,
    bell: <><path d="M18.2 15.5a3 3 0 0 1-.7-2V10a5.5 5.5 0 1 0-11 0v3.5a3 3 0 0 1-.7 2l-.6.7a.5.5 0 0 0 .4.8h12.8a.5.5 0 0 0 .4-.8l-.6-.7z"/><path d="M10 19.5a2 2 0 0 0 4 0"/></>,
    settings: <><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1A2 2 0 1 1 4.3 17l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.5-1.1 1.7 1.7 0 0 0-.3-1.8l-.1-.1A2 2 0 1 1 7 4.3l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z"/></>,
    euro: <><path d="M18 7a7 7 0 1 0 0 10M4 10h9M4 14h9"/></>,
    clock: <><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></>,
    lock: <><rect x="4" y="10" width="16" height="11" rx="2"/><path d="M8 10V7a4 4 0 0 1 8 0v3"/></>,
    eye: <><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12z"/><circle cx="12" cy="12" r="3"/></>,
    eyeOff: <><path d="M3 3l18 18"/><path d="M10.6 6.2A10.9 10.9 0 0 1 12 6c6.5 0 10 6 10 6a14 14 0 0 1-2.4 3M6.6 6.6C3.8 8.3 2 12 2 12s3.5 7 10 7a10.9 10.9 0 0 0 4.3-.9"/><path d="M9.5 9.5a3 3 0 0 0 4.2 4.2"/></>,
    mail: <><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M3 7l9 6 9-6"/></>,
    phone: <><path d="M5 4h4l2 5-2.5 1.5a11 11 0 0 0 5 5L15 13l5 2v4a2 2 0 0 1-2 2A16 16 0 0 1 3 6a2 2 0 0 1 2-2z"/></>,
    star: <path d="M12 3l2.9 5.9 6.5.9-4.7 4.6 1.1 6.5L12 17.8 6.2 20.9l1.1-6.5L2.6 9.8l6.5-.9L12 3z"/>,
    invoice: <><rect x="5" y="3" width="14" height="18" rx="2"/><path d="M8 8h8M8 12h8M8 16h5"/></>,
    tag: <><path d="M20 12L12 4H4v8l8 8 8-8z"/><circle cx="8" cy="8" r="1.2"/></>,
    gift: <><rect x="3" y="8" width="18" height="12" rx="1.5"/><path d="M12 8v12M3 12h18M7 8a2.5 2.5 0 1 1 5-2.5M17 8a2.5 2.5 0 1 0-5-2.5"/></>,
    link: <><path d="M10 14a5 5 0 0 0 7 0l3-3a5 5 0 0 0-7-7l-1 1"/><path d="M14 10a5 5 0 0 0-7 0l-3 3a5 5 0 0 0 7 7l1-1"/></>,
    copy: <><rect x="9" y="9" width="12" height="12" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></>,
    external: <><path d="M14 4h6v6M20 4l-9 9M19 13v6a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1h6"/></>,
    trash: <><path d="M4 7h16M10 11v6M14 11v6M5 7l1 13a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1l1-13M9 7V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v3"/></>,
    briefcase: <><rect x="3" y="7" width="18" height="13" rx="2"/><path d="M9 7V5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v2M3 13h18"/></>,
    user: <><circle cx="12" cy="8" r="4"/><path d="M4 21c0-4.4 3.6-8 8-8s8 3.6 8 8"/></>,
  };
  return <svg {...props}>{paths[name] || null}</svg>;
};

// Couleur du logo : indigo (--accent) pour l'audience pro, violet (--cli-accent)
// pour l'audience client. On résout la couleur via une CSS var dérivée du token
// audience (--v-pro-accent / --v-cli-accent) pour transitionner en douceur quand
// on bascule le switch. Fallback indigo si la page rend le logo hors audience.
// Salutation contextuelle selon l'heure. Utilisée dans le hero (sans nom)
// et dans l'espace pro logué (avec prénom). Renvoie aussi un emoji
// adapté pour donner un côté chaleureux à la page.
const cbGreeting = () => {
  const h = (new Date()).getHours();
  if (h < 6)  return { text: "Vous êtes encore là",      emoji: "🌙" };
  if (h < 12) return { text: "Bonjour",                  emoji: "👋" };
  if (h < 14) return { text: "Bon appétit",              emoji: "🥗" };
  if (h < 18) return { text: "Bonne journée",            emoji: "☀️" };
  if (h < 22) return { text: "Bonsoir",                  emoji: "🌆" };
  return       { text: "Bonne soirée",                   emoji: "🌃" };
};
if (typeof window !== "undefined") {
  window.cbGreeting = cbGreeting;
}

// Logo ClientBase — version interactive « 3D » :
// - Taille bumpée (28 par défaut au lieu de 24) pour plus de présence.
// - Au hover, on tilt en parallax 3D selon la position de la souris
//   (perspective-translate similaire aux PricingCard) + grossissement
//   léger + drop-shadow audience-color.
// - Une petite étincelle ✦ apparaît en haut à droite au hover pour
//   donner un signe de vie (élément 3D bonus).
// - Animations purement CSS dans v2/index.html (.cb-logo-interactive),
//   tracking 3D géré ici en state pour suivre le curseur précisément.
const Logo = ({ size = 28, withWordmark = true, audience, interactive = true }) => {
  const fill = audience === "client" ? "var(--cli-accent)" : "var(--accent)";
  const markRef = React.useRef(null);
  const [tilt, setTilt] = React.useState({ rx: 0, ry: 0 });
  const [hover, setHover] = React.useState(false);

  const onMouseMove = (e) => {
    if (!interactive) return;
    const el = markRef.current;
    if (!el) return;
    const r = el.getBoundingClientRect();
    const px = (e.clientX - r.left) / r.width;
    const py = (e.clientY - r.top)  / r.height;
    // Tilt plus doux qu'avant (±6° au lieu de ±9°) pour éviter l'effet
    // « flou de mouvement » : trop d'angle = sub-pixel rendering qui floute
    // le SVG. Sweet spot trouvé : 6° donne un parallax visible sans aliasing.
    const ry = (px - 0.5) *  12;
    const rx = (py - 0.5) * -12;
    setTilt({ rx, ry });
  };
  const onMouseLeave = () => {
    setTilt({ rx: 0, ry: 0 });
    setHover(false);
  };

  return (
    <div
      className={interactive ? "cb-logo cb-logo-interactive" : "cb-logo"}
      onMouseEnter={() => interactive && setHover(true)}
      onMouseLeave={onMouseLeave}
      onMouseMove={onMouseMove}
      style={{
        display: "inline-flex", alignItems: "center", gap: 9,
        // Perspective plus grande = parallax plus subtil et moins distordu.
        // 800 → le SVG reste net sans déformation excessive.
        perspective: 800,
      }}>
      <span
        ref={markRef}
        className="cb-logo-mark"
        style={{
          position: "relative",
          display: "inline-flex",
          transform: interactive
            ? `rotateX(${tilt.rx}deg) rotateY(${tilt.ry}deg) scale(${hover ? 1.08 : 1})`
            : "none",
          transition: (tilt.rx === 0 && tilt.ry === 0)
            ? "transform .35s cubic-bezier(.22,1.4,.36,1)"
            : "transform .12s ease-out",
          // box-shadow propre sur la card derrière (pas drop-shadow filter
          // qui rastérise le SVG et le rend flou). Le drop-shadow CSS filter
          // sur SVG = forcing GPU = sub-pixel antialiasing perdu.
          transformStyle: "preserve-3d",
          willChange: "transform",
          backfaceVisibility: "hidden",
          WebkitFontSmoothing: "antialiased",
        }}>
        {/* Ombre douce en pseudo-élément via div absolute (pas filter)
            pour ne PAS rendre le SVG flou. Apparaît au hover, suit le tilt. */}
        <span aria-hidden style={{
          position: "absolute", inset: 0,
          borderRadius: 8.5,
          background: fill,
          opacity: hover ? 0.35 : 0,
          filter: "blur(12px)",
          transform: `translate(${tilt.ry * 0.6}px, ${4 + Math.abs(tilt.rx) * 0.3}px) scale(0.95)`,
          transition: "opacity .25s ease, transform .15s ease",
          zIndex: -1,
          pointerEvents: "none",
        }}/>
        <svg width={size} height={size} viewBox="0 0 32 32"
          style={{ display: "block", shapeRendering: "geometricPrecision" }}>
          <rect x="0" y="0" width="32" height="32" rx="8.5" fill={fill}
            style={{ transition: "fill .35s ease" }}/>
          <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>
        {/* Étincelle ✦ qui pop en haut à droite au hover. Pas de drop-shadow
            filter sur celle-ci non plus pour la garder nette. */}
        <span aria-hidden style={{
          position: "absolute",
          top: -6, right: -6,
          width: 14, height: 14,
          color: fill,
          opacity: hover ? 1 : 0,
          transform: `scale(${hover ? 1 : 0.3}) rotate(${hover ? 12 : 0}deg)`,
          transition: "opacity .25s ease, transform .35s cubic-bezier(.5,1.6,.4,1)",
          pointerEvents: "none",
        }}>
          <svg viewBox="0 0 24 24" fill="currentColor" width="14" height="14"
            style={{ shapeRendering: "geometricPrecision" }}>
            <path d="M12 0 L13.5 9.5 L23 12 L13.5 14.5 L12 24 L10.5 14.5 L1 12 L10.5 9.5 Z"/>
          </svg>
        </span>
      </span>
      {withWordmark && (
        <span className="cb-logo-word" style={{
          fontFamily: "var(--ff-display)", fontWeight: 600, fontSize: 20,
          letterSpacing: "-0.025em", color: hover ? (audience === "client" ? "var(--cli-ink)" : "var(--accent-ink)") : "var(--ink)",
          transition: "color .25s ease",
        }}>ClientBase</span>
      )}
    </div>
  );
};

// Nav links dépendent de l'audience : la vitrine client n'a pas de tarifs
// (compte gratuit pour les particuliers).
const NAV_LINKS_PRO = [
  { id: "features", label: "Guide" },
  { id: "pricing",  label: "Tarif" },
  { id: "blog",     label: "Blog" },
  { id: "contact",  label: "Contact" },
];
const NAV_LINKS_CLIENT = [
  { id: "clientGuide",     label: "Guide" },
  { id: "clientAnnuaire",  label: "Annuaire" },
  { id: "clientBlog",      label: "Blog" },
  { id: "clientContact",   label: "Contact" },
];

// Tones par module, palette vitrine canonique (indigo / rose / sauge) cyclée
// pour que chaque module garde une couleur d'identité sans dépasser les 3 teintes.
const NAV_MODULE_TONES = {
  agenda:    { bg: "var(--accent-soft)", ink: "var(--accent-ink)" },
  clients:   { bg: "var(--rose-soft)",   ink: "var(--rose-ink)"   },
  factures:  { bg: "var(--sage-soft)",   ink: "var(--sage-ink)"   },
  acomptes:  { bg: "var(--accent-soft)", ink: "var(--accent-ink)" },
  page:      { bg: "var(--rose-soft)",   ink: "var(--rose-ink)"   },
  stats:     { bg: "var(--sage-soft)",   ink: "var(--sage-ink)"   },
};

// Sous-menu Produit : chaque entrée lance la démo positionnée sur le bon module
const NAV_PRODUCT_MENU = [
  { icon: "calendar", title: "Agenda",       desc: "Tous tes RDV en un coup d'œil",     module: "agenda"   },
  { icon: "users",    title: "Clients",      desc: "Fiches enrichies + historique",      module: "clients"  },
  { icon: "invoice",  title: "Facturation",  desc: "Factures conformes, RGPD",          module: "factures" },
  { icon: "lock",     title: "Acomptes",     desc: "Sécurise tes RDV",                  module: "acomptes" },
  { icon: "sparkle",  title: "Ma page",      desc: "Page de RDV à ton image",           module: "page"     },
  { icon: "chart",    title: "Statistiques", desc: "Pilote ton activité",               module: "stats"    },
];

/* === Lance une session démo (mode local, pas de Supabase) ===
   - sauvegarde la session courante dans sessionStorage (récupérable à la sortie)
   - signOut Supabase pour passer en mode local (SEED_DATA)
   - écrit un user démo dans cb_user_v1
   - reload vers /v2/app
*/
const cbStartDemo = async (targetModule = "accueil", role = "pro") => {
  try {
    // Sauvegarde éventuelle de la session existante
    const prev = {
      user: localStorage.getItem("cb_user_v1"),
      data: localStorage.getItem("cb_app_data_v3"),
      dataCloud: localStorage.getItem("cb_app_data_v3_cloud"),
      ts: Date.now(),
    };
    sessionStorage.setItem("cb_demo_backup", JSON.stringify(prev));

    // SignOut Supabase pour forcer le mode local (SEED_DATA s'affiche)
    if (window.cbSupabase) {
      try { await window.cbSupabase.auth.signOut(); } catch {}
    }

    // Reset des données d'app pour avoir le SEED tout neuf
    try { localStorage.removeItem("cb_app_data_v3"); } catch {}
    try { localStorage.removeItem("cb_app_data_v3_cloud"); } catch {}

    // User démo local — aligné sur SEED_DATA (Ongles by Léa, prothésiste
    // ongulaire à Lyon) pour cohérence visuelle entre le compte et les
    // données affichées (clientes, factures, RDV).
    // Nom adapté selon le profil démo (pour s'y retrouver visuellement).
    const _roleOwner = {
      pro:          "Léa Bernard",
      ambassadrice: "Léa Bernard",
      cofondatrice: "Sarah (co-fondatrice)",
      admin:        "Vous (Admin)",
    };
    const safeRole = ["pro", "ambassadrice", "cofondatrice", "admin"].indexOf(role) >= 0 ? role : "pro";
    const demoUser = {
      id: "demo-" + Date.now().toString(36),
      email: "demo@clientbase.fr",
      emailVerified: true,
      businessName: "Ongles by Léa · démo",
      ownerName: _roleOwner[safeRole] || "Léa Bernard",
      bookingSlug: "demo-lea",
      demo: true,
      role: safeRole,
      createdAt: Date.now(),
    };
    localStorage.setItem("cb_user_v1", JSON.stringify(demoUser));
    // Flag visible pour bandeau démo dans le dashboard
    localStorage.setItem("cb_demo_mode", "1");
    // Module cible : l'AppShell s'y positionne au mount
    localStorage.setItem("cb_demo_target", targetModule || "accueil");

    // Force l'affichage du splash de démo après le reload (sinon le flag
    // "déjà montré" de la session en cours le sauterait).
    try { sessionStorage.removeItem("cb_splash_shown"); } catch {}

    // Reload sur l'app, on cible /v2/ (toujours résolu, dossier avec index.html)
    // L'app détecte la session démo et bascule automatiquement sur "app".
    window.location.href = "/v2/";
  } catch (e) {
    console.error("[cbStartDemo]", e);
    alert("Impossible de lancer la démo. Réessayez.");
  }
};

const cbExitDemo = () => {
  try {
    const raw = sessionStorage.getItem("cb_demo_backup");
    sessionStorage.removeItem("cb_demo_backup");
    localStorage.removeItem("cb_demo_mode");
    if (raw) {
      const prev = JSON.parse(raw);
      if (prev.user) localStorage.setItem("cb_user_v1", prev.user);
      else localStorage.removeItem("cb_user_v1");
      if (prev.data) localStorage.setItem("cb_app_data_v3", prev.data);
      else localStorage.removeItem("cb_app_data_v3");
      if (prev.dataCloud) localStorage.setItem("cb_app_data_v3_cloud", prev.dataCloud);
      else localStorage.removeItem("cb_app_data_v3_cloud");
    } else {
      localStorage.removeItem("cb_user_v1");
      localStorage.removeItem("cb_app_data_v3");
    }
    window.location.href = "/v2/";
  } catch (e) {
    console.error("[cbExitDemo]", e);
  }
};
window.cbStartDemo = cbStartDemo;
window.cbExitDemo = cbExitDemo;

// Rôle du compte courant (par défaut "pro"). Lu par le dashboard pour
// afficher l'onglet Ambassadrice / Co-fondatrice / Admin selon le profil.
const cbCurrentRole = () => {
  try {
    const u = window.cbAuth && window.cbAuth.getCurrentUser && window.cbAuth.getCurrentUser();
    return (u && u.role) || "pro";
  } catch { return "pro"; }
};
window.cbCurrentRole = cbCurrentRole;

// Lanceur de démo par rôle : ouvrir le site avec ?demo=pro|ambassadrice|
// cofondatrice|admin lance directement la démo du profil correspondant.
try {
  const _dr = new URLSearchParams(window.location.search).get("demo");
  if (_dr && ["pro", "ambassadrice", "cofondatrice", "admin"].indexOf(_dr) >= 0) {
    const target = _dr === "pro" ? "accueil" : _dr;
    const inDemo = localStorage.getItem("cb_demo_mode") === "1";
    let curUser = null;
    try { curUser = JSON.parse(localStorage.getItem("cb_user_v1") || "null"); } catch (e) {}
    const isAdminDemo = inDemo && curUser && curUser.role === "admin";
    // pro/admin : entrées directes autorisées. ambassadrice/cofondatrice :
    // uniquement en prévisualisation depuis une démo ADMIN (outil interne).
    const isEntry = _dr === "pro" || _dr === "admin";
    if (!inDemo && isEntry) {
      cbStartDemo(target, _dr);
    } else if (inDemo && (isEntry || isAdminDemo) && curUser && curUser.role !== _dr) {
      curUser.role = _dr;
      localStorage.setItem("cb_user_v1", JSON.stringify(curUser));
      localStorage.setItem("cb_demo_target", target);
      sessionStorage.removeItem("cb_splash_shown");
      window.location.href = "/v2/";
    }
  }
} catch (e) {}

/* Démo CLIENT, bascule l'utilisateur dans l'espace cliente démo
   (token magique « demo » que pages-espace.jsx reconnaît déjà avec un
   compte rempli : 2 pros, RDV à venir, historique, cartes fidélité).
   Pas de signOut ni d'écrasement de session : c'est juste une navigation
   vers /?espace=demo, ce qui hijacke l'app pour rendre EspacePage. */
const cbStartClientDemo = () => {
  try { window.location.href = "/?espace=demo"; }
  catch (e) {
    console.error("[cbStartClientDemo]", e);
    alert("Impossible de lancer la démo. Réessayez.");
  }
};
window.cbStartClientDemo = cbStartClientDemo;

/* ============================================================
   cbMarketingSend — envoi marketing (SMS / email).
   SIMULÉ pour l'instant : renvoie un succès après un court délai.

   ⚠️ POINT DE BRANCHEMENT (envoi réel) :
   Remplacer le corps par un appel à window.cbCloud (edge function /
   serveur) qui lui-même contacte le fournisseur (Twilio pour le SMS,
   Brevo/Postmark pour l'email). NE JAMAIS appeler un fournisseur
   directement depuis le navigateur : les clés API seraient exposées,
   et l'envoi en masse doit être contrôlé côté serveur (quotas, coûts,
   consentement RGPD). Garder la même signature pour ne rien changer
   dans l'UI le jour du branchement.
   ============================================================ */
/* ============================================================
   cbPay — HUB DE PAIEMENT CENTRAL (carte bancaire via Stripe).
   Un seul endroit qui décide si le paiement est actif.

   ⚙️ AUJOURD'HUI (bêta, avant mi-juillet) : enabled = false → tous les
   boutons « payer » du site affichent « disponible mi-juillet ».

   🚀 À L'ARRIVÉE DE STRIPE (mi-juillet) : passer `enabled` à true et
   implémenter checkout() (créer une session Stripe Checkout côté serveur
   puis rediriger). Rien d'autre à changer dans l'UI : cartes cadeaux,
   forfaits, acomptes et recharges de crédits passent TOUS par ici.

   Rappel modèle (cf. plan Stripe) :
     - client → pro (acompte, carte cadeau, forfait) : Stripe **Connect**.
     - pro → ClientBase (abonnement, crédits SMS) : Stripe standard.
   ============================================================ */
window.cbPay = {
  enabled: false,
  eta: "mi-juillet",
  // kind: "giftcard" | "forfait" | "acompte" | "sms-credits" | "subscription"
  checkout: async function (opts) {
    if (!this.enabled) {
      window.cbComingSoon && window.cbComingSoon({
        title: "Paiement par carte",
        message: "Le paiement en ligne (acomptes, cartes cadeaux, forfaits…) arrive très vite, 100 % sécurisé par Stripe.",
        eta: this.eta,
      });
      return { soon: true, eta: this.eta };
    }
    // TODO(Stripe mi-juillet) : créer la session côté serveur et rediriger.
    // const url = await window.cbCloud.createCheckout(opts); window.location.href = url;
    return { soon: true, eta: this.eta };
  },
  // Petit libellé réutilisable pour les boutons « bientôt ».
  soonLabel: function () { return "Dispo " + this.eta; },
};

/* ============================================================
   CB_ROLES — profils (rôles) de ClientBase.
   Hiérarchie par `level` (plus haut = plus de droits). L'admin/co-fondatrice
   attribuera ces profils à de vrais comptes une fois le backend des rôles
   branché (table `profiles.role` côté Supabase + RLS). Pour l'instant c'est
   la source de vérité côté front : libellés, badge, features par profil.
   ============================================================ */
const CB_ROLES = {
  pro: {
    id: "pro", level: 1, label: "Professionnel",
    badge: { text: "Pro", color: "var(--accent)" },
    desc: "Le compte standard d'un salon : gère son activité au quotidien.",
    features: [
      "Agenda, clients, prestations, Ma page",
      "Marketing : relances + avis",
      "Facturation, fidélité, cartes cadeaux, forfaits",
      "Statistiques de son activité",
    ],
  },
  // Ambassadrice & Co-fondatrice = AVANT TOUT un compte pro normal
  // (elles utilisent l'app comme une pro) + UN ONGLET en plus pour leur rôle.
  ambassadrice: {
    id: "ambassadrice", level: 2, label: "Pro Ambassadrice",
    badge: { text: "Ambassadrice", color: "oklch(60% 0.17 30)" },
    desc: "Un compte pro normal + un onglet pour recommander ClientBase et gagner des récompenses.",
    base: "Compte Professionnel complet (elle utilise l'app comme une pro).",
    features: [
      "Onglet « Ambassadrice » en plus de son compte pro",
      "Lien d'invitation perso + suivi des pros parrainés",
      "Commissions / récompenses sur les abonnements apportés",
      "Badge « Ambassadrice » + accès anticipé aux nouveautés",
      "Kit de partage (visuels + messages prêts à poster)",
    ],
  },
  cofondatrice: {
    id: "cofondatrice", level: 3, label: "Co-fondatrice",
    badge: { text: "Co-fondatrice", color: "var(--accent-deep, #3C2FBD)" },
    desc: "Un compte pro normal + un onglet pour piloter le marketing de ClientBase.",
    base: "Compte Professionnel complet (elle utilise l'app comme une pro).",
    features: [
      "Onglet « Co-fondatrice » en plus de son compte pro",
      "Mini tableau de bord : nb d'inscrits, nouveaux cette semaine, pros actifs",
      "Annonces diffusées à tous les pros",
      "Suivi des ambassadrices (pros apportés par chacune)",
      "Bibliothèque marketing (visuels, posts à partager)",
      "Avis & témoignages collectés (pour le marketing)",
      // PAS de gestion des admins/ambassadrices, PAS de réglages plateforme.
    ],
  },
  admin: {
    id: "admin", level: 4, label: "Administrateur",
    badge: { text: "Admin", color: "var(--ink)" },
    desc: "Toi, la fondatrice : seul compte qui gère la plateforme et attribue les profils.",
    features: [
      "Attribuer les profils (pro / ambassadrice / co-fondatrice)",
      "Voir et gérer tous les comptes",
      "Réglages de la plateforme (formules, quotas, prix)",
      "Tableau de bord global complet + export comptable",
      "Support : se connecter à un compte pour aider",
    ],
  },
};
try { window.CB_ROLES = CB_ROLES; } catch {}

/* ============================================================
   cbComingSoon — écran UNIQUE « cette fonctionnalité arrive bientôt ».
   Appelé partout où une action n'est pas encore fonctionnelle (paiement
   carte, envoi SMS réel, etc.). L'utilisateur peut tout configurer/cliquer,
   mais au moment de FINALISER, ce voile s'affiche pour expliquer que ça
   arrive — avec un bouton de contact. Vanilla DOM → utilisable depuis
   n'importe quel composant (React ou non).
   opts: { title, message, eta }
   ============================================================ */
window.cbComingSoon = function (opts) {
  opts = opts || {};
  try {
    if (document.getElementById("cb-soon-overlay")) return;
    var eta = opts.eta || "";
    var title = opts.title || "Bientôt disponible";
    var message = opts.message || "Cette fonctionnalité arrive très vite. On peaufine les derniers détails !";

    var ov = document.createElement("div");
    ov.id = "cb-soon-overlay";
    ov.style.cssText =
      "position:fixed;inset:0;z-index:100000;display:flex;align-items:center;justify-content:center;padding:20px;" +
      "background:rgba(15,16,32,0.5);backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);opacity:0;transition:opacity .25s ease;" +
      "font-family:'Geist',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;";

    var card = document.createElement("div");
    card.style.cssText =
      "background:#fff;border-radius:20px;max-width:380px;width:100%;padding:28px 26px;text-align:center;" +
      "box-shadow:0 30px 70px -20px rgba(15,18,30,0.4);transform:translateY(12px) scale(.98);transition:transform .3s cubic-bezier(.22,1,.36,1);";

    card.innerHTML =
      '<div style="width:62px;height:62px;border-radius:16px;margin:0 auto 16px;background:linear-gradient(135deg,#5A50E6,#3C2FBD);' +
            'display:flex;align-items:center;justify-content:center;font-size:30px;box-shadow:0 10px 26px -8px rgba(90,80,230,.6);">🚀</div>' +
      (eta ? '<div style="display:inline-block;margin-bottom:10px;padding:3px 11px;background:#F1F0FF;color:#3C2FBD;' +
             'border-radius:999px;font-size:11px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;">Arrive ' + eta + '</div>' : '') +
      '<div style="font-size:19px;font-weight:680;letter-spacing:-0.02em;color:#0F1020;margin-bottom:8px;">' + title + '</div>' +
      '<div style="font-size:13.5px;color:#6B7280;line-height:1.55;margin-bottom:20px;">' + message + '</div>' +
      '<div style="display:flex;flex-direction:column;gap:8px;">' +
        '<a href="mailto:hello@clientbase.fr?subject=Je veux être prévenu(e) — ' + encodeURIComponent(title) + '" ' +
          'style="display:block;padding:12px;background:#5A50E6;color:#fff;border-radius:11px;font-size:14px;font-weight:620;text-decoration:none;">Être prévenu·e / nous contacter</a>' +
        '<button id="cb-soon-close" style="padding:11px;background:#fff;border:1px solid #E5E7EB;border-radius:11px;font-size:13.5px;' +
          'font-weight:560;color:#374151;cursor:pointer;font-family:inherit;">Compris</button>' +
      '</div>';

    ov.appendChild(card);
    document.body.appendChild(ov);
    requestAnimationFrame(function () { ov.style.opacity = "1"; card.style.transform = "translateY(0) scale(1)"; });

    var close = function () {
      ov.style.opacity = "0"; card.style.transform = "translateY(12px) scale(.98)";
      setTimeout(function () { ov.remove(); }, 250);
    };
    ov.addEventListener("click", function (e) { if (e.target === ov) close(); });
    card.querySelector("#cb-soon-close").onclick = close;
  } catch (e) { /* silencieux */ }
};

const cbMarketingSend = async ({ channel = "sms", recipients = [], message = "" } = {}) => {
  // TODO(provider): return window.cbCloud.sendCampaign({ channel, recipients, message });
  await new Promise(r => setTimeout(r, 700));
  return { ok: true, sent: recipients.length, channel, at: Date.now() };
};
try { window.cbMarketingSend = cbMarketingSend; } catch {}

/* ============================================================
   CbDemoSplash — écran de chargement dédié aux DÉMOS.
   Différent du splash habituel : badge « DÉMO », libellé adapté à
   l'audience, et couleur thématique :
     - variant "pro"    → indigo de marque (--accent)
     - variant "client" → violet client (--cli-accent)
   Exporté sur window pour être utilisable depuis app-dashboard.jsx
   (démo pro) ET pages-espace.jsx (démo client), qui sont des scripts
   séparés sans portée partagée.
   ============================================================ */
const CbDemoSplash = ({ variant = "pro" }) => {
  const isClient = variant === "client";
  const accent     = isClient ? "var(--cli-accent)" : "var(--accent)";
  const accentDeep = isClient ? "oklch(38% 0.16 295)" : "#3C2FBD";
  const soft       = isClient ? "var(--cli-soft)" : "var(--accent-soft)";
  const title      = isClient ? "Espace client" : "Espace professionnel";
  const sub        = isClient
    ? "On prépare vos RDV, vos cartes de fidélité et votre historique…"
    : "On prépare votre tableau de bord et vos données d'exemple…";

  return (
    <div style={{
      position: "fixed", inset: 0, zIndex: 9999,
      background: "var(--bg, #FAFAFA)",
      display: "flex", alignItems: "center", justifyContent: "center",
      flexDirection: "column", gap: 16, padding: 24, textAlign: "center",
    }}>
      <style>{`
        @keyframes cbDemoSplashIn { 0% { opacity: 0; transform: scale(0.86); } 100% { opacity: 1; transform: scale(1); } }
        @keyframes cbDemoSplashUp { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
        @keyframes cbDemoSplashBar { 0% { transform: translateX(-100%); } 100% { transform: translateX(250%); } }
        @keyframes cbDemoSplashPulse { 0%,100% { box-shadow: 0 0 0 4px color-mix(in oklab, ${accent} 22%, transparent); } 50% { box-shadow: 0 0 0 9px color-mix(in oklab, ${accent} 8%, transparent); } }
      `}</style>

      {/* Logo + badge DÉMO */}
      <div style={{ position: "relative", animation: "cbDemoSplashIn .45s cubic-bezier(.22,1,.36,1)" }}>
        <svg width="56" height="56" viewBox="0 0 32 32" style={{ display: "block" }}>
          <rect x="0" y="0" width="32" height="32" rx="8.5" fill={accentDeep}/>
          <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>
        <span style={{
          position: "absolute", top: -8, right: -16,
          background: accent, color: "#fff",
          fontSize: 9.5, fontWeight: 700, letterSpacing: "0.08em",
          padding: "3px 7px", borderRadius: 999,
          fontFamily: "var(--ff-mono, monospace)",
          boxShadow: "0 2px 8px -2px rgba(0,0,0,0.25)",
        }}>DÉMO</span>
      </div>

      <div style={{ animation: "cbDemoSplashUp .45s ease .08s both", maxWidth: 320 }}>
        <div style={{
          fontFamily: "var(--ff-display)", fontSize: 19, fontWeight: 640,
          letterSpacing: "-0.02em", color: "var(--ink)", marginBottom: 6,
        }}>
          {title}
        </div>
        <div style={{ fontSize: 13.5, color: "var(--ink-4)", lineHeight: 1.5 }}>
          {sub}
        </div>
      </div>

      {/* Barre de progression indéterminée */}
      <div style={{
        width: 160, height: 3, borderRadius: 999, overflow: "hidden",
        background: soft, position: "relative",
        animation: "cbDemoSplashUp .45s ease .16s both",
      }}>
        <div style={{
          position: "absolute", top: 0, left: 0, height: "100%", width: "40%",
          background: accent, borderRadius: 999,
          animation: "cbDemoSplashBar 1.1s ease-in-out infinite",
        }}/>
      </div>
    </div>
  );
};
try { window.CbDemoSplash = CbDemoSplash; } catch {}

/* Bouton « Mon espace », audience-aware.
   Libellé unifié pro/client (« Mon espace » dans les deux cas) : place le
   bouton dans le même registre lexical quelle que soit l'audience. La
   distinction se fait par la couleur du dégradé (indigo pro / violet client)
   et par la navigation côté caller (pro → /app, client → /espace).
   Forme « pill » pour s'intégrer à droite du switch audience. */
const OpenAppButton = ({ onClick, large, audience = "pro", label = "Mon espace", icon = "arrow" }) => {
  const [hover, setHover] = React.useState(false);
  const isClient = audience === "client";
  const accent     = isClient ? "var(--cli-accent)" : "var(--accent)";
  const accentDeep = isClient ? "oklch(48% 0.18 295)" : "oklch(45% 0.22 288)";
  return (
    <button onClick={onClick}
      onMouseEnter={() => setHover(true)}
      onMouseLeave={() => setHover(false)}
      style={{
        display: "inline-flex", alignItems: "center", gap: 8,
        padding: large ? "12px 22px" : "8px 17px 8px 15px",
        height: large ? 48 : 36,
        background: `linear-gradient(135deg, ${accent} 0%, ${accentDeep} 100%)`,
        color: "white",
        border: "none", borderRadius: 999,
        cursor: "pointer", fontFamily: "inherit",
        fontSize: large ? 15 : 13.5, fontWeight: 580,
        letterSpacing: "-0.005em",
        boxShadow: hover
          ? `0 10px 28px -8px color-mix(in oklab, ${accent} 65%, transparent), 0 2px 4px color-mix(in oklab, ${accent} 30%, transparent), inset 0 1px 0 rgba(255,255,255,0.2)`
          : `0 4px 14px -4px color-mix(in oklab, ${accent} 45%, transparent), 0 1px 2px color-mix(in oklab, ${accent} 25%, transparent), inset 0 1px 0 rgba(255,255,255,0.2)`,
        transform: hover ? "translateY(-1px)" : "translateY(0)",
        transition: "transform 0.18s ease, box-shadow 0.22s ease, background 0.3s ease",
        width: large ? "100%" : "auto",
        justifyContent: "center",
      }}>
      <span>{label}</span>
      <span style={{
        display: "inline-flex",
        transform: hover ? "translateX(3px)" : "translateX(0)",
        transition: "transform 0.2s ease",
      }}>
        <Icon name={icon} size={large ? 16 : 13}/>
      </span>
    </button>
  );
};

/* Bascule visuelle entre vitrine pro et vitrine client.
   Le segmented control toggle prend très peu de place et signale clairement
   l'audience active. Au clic : persiste dans localStorage + navigue vers
   l'accueil de l'audience choisie. */
const AudienceSwitch = ({ audience, go, compact = false, fullWidth = false }) => {
  const items = [
    { id: "pro",    label: "Pro",    color: "var(--accent)" },
    { id: "client", label: "Client", color: "var(--cli-accent)" },
  ];
  // Hauteur 36 px = même que la pill Compte. Padding/font ajustés pour
  // que chaque demi-pill ressemble visuellement au bouton Compte.
  return (
    <div role="group" aria-label="Audience" style={{
      position: "relative",
      display: fullWidth ? "flex" : "inline-flex",
      width: fullWidth ? "100%" : "auto",
      height: fullWidth ? "auto" : 36,
      padding: 3,
      background: "var(--bg-alt)",
      border: "1px solid var(--line)",
      borderRadius: 999,
      gap: 0,
      boxSizing: "border-box",
    }}>
      {items.map(it => {
        const active = audience === it.id;
        return (
          <button key={it.id}
            onClick={() => {
              if (audience === it.id) return;
              cbAudience.set(it.id);
              go(it.id === "pro" ? "home" : "clientHome");
            }}
            aria-pressed={active}
            style={{
              position: "relative",
              flex: fullWidth ? 1 : "0 0 auto",
              padding: fullWidth ? "10px 14px" : "0 16px",
              minWidth: fullWidth ? 0 : 64,
              background: active ? it.color : "transparent",
              color: active ? "#fff" : "var(--ink-3)",
              border: "none", borderRadius: 999,
              cursor: "pointer", fontFamily: "inherit",
              fontSize: fullWidth ? 14.5 : 13.5, fontWeight: 580,
              letterSpacing: "-0.005em",
              transition: "background .22s ease, color .22s ease, transform .12s ease",
              boxShadow: active ? `0 4px 14px -6px ${it.color}` : "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)"; }}
            >
            {it.label}
          </button>
        );
      })}
    </div>
  );
};

// Variante mobile de l'AudienceSwitch : indicateur actif animé qui
// SLIDE de Pro vers Client (ou inverse) avec une transition CSS visible
// (260ms cubic-bezier). Pas d'overlay plein écran — juste la petite
// encoche qui glisse, puis la page change. Donne un feedback clair que
// l'audience change sans interrompre le contexte.
const AudienceSwitchMobile = ({ audience, onSwitch }) => {
  const items = [
    { id: "pro",    label: "Pro",    color: "var(--accent)" },
    { id: "client", label: "Client", color: "var(--cli-accent)" },
  ];
  // visualActive : état purement visuel pour faire glisser l'indicateur
  // avant que la nav effective se déclenche. Sync avec la prop audience.
  const [visualActive, setVisualActive] = React.useState(audience);
  React.useEffect(() => { setVisualActive(audience); }, [audience]);

  const handleClick = (target) => {
    if (visualActive === target) return;
    setVisualActive(target);
    // Délai = durée de l'anim slide → on voit la petite encoche bouger
    // avant que la page change.
    setTimeout(() => onSwitch(target), 280);
  };

  const activeIdx = items.findIndex(i => i.id === visualActive);
  const activeColor = items[activeIdx >= 0 ? activeIdx : 0].color;

  return (
    <div role="group" aria-label="Audience" style={{
      position: "relative", display: "flex",
      width: "100%", padding: 3,
      background: "var(--bg-alt)", border: "1px solid var(--line)",
      borderRadius: 999, boxSizing: "border-box",
    }}>
      {/* Encoche colorée qui slide d'un côté à l'autre. C'est ce qui
          donne le feedback « ça change » sans avoir besoin d'overlay. */}
      <div aria-hidden style={{
        position: "absolute",
        top: 3, bottom: 3, left: 3,
        width: "calc(50% - 3px)",
        background: activeColor,
        borderRadius: 999,
        transform: `translateX(${activeIdx * 100}%)`,
        transition: "transform .28s cubic-bezier(.4, 1.4, .4, 1), background .25s ease",
        boxShadow: `0 4px 14px -6px ${activeColor}`,
        pointerEvents: "none",
      }}/>
      {items.map((it) => {
        const active = visualActive === it.id;
        return (
          <button key={it.id}
            onClick={() => handleClick(it.id)}
            aria-pressed={active}
            style={{
              position: "relative", zIndex: 1, flex: 1,
              padding: "10px 14px",
              background: "transparent",
              color: active ? "#fff" : "var(--ink-3)",
              border: "none", borderRadius: 999,
              cursor: "pointer", fontFamily: "inherit",
              fontSize: 14.5, fontWeight: 580,
              letterSpacing: "-0.005em",
              transition: "color .25s ease",
            }}>
            {it.label}
          </button>
        );
      })}
    </div>
  );
};

// 2 boutons côte à côte, même hauteur (36px = AudienceSwitch), design
// sobre : « Connexion » en ghost (outline subtil), « Créer un compte »
// en pill plein audience-color. Pas d'icône — texte propre suffit.
const CompteMenu = ({ audience, go }) => {
  const isClient = audience === "client";
  const accent      = isClient ? "var(--cli-accent)" : "var(--accent)";
  const accentInk   = isClient ? "var(--cli-ink)"    : "var(--accent-ink)";
  const signupPage  = isClient ? "clientSignup" : "signup";
  const loginPage   = isClient ? "clientLogin"  : "login";
  const [hovPrim, setHovPrim] = React.useState(false);
  const [hovSec,  setHovSec]  = React.useState(false);

  return (
    <div style={{ display: "inline-flex", alignItems: "center", gap: 8 }}>
      {/* Connexion : pill ghost, même hauteur que le primary pour parité
          visuelle. Au hover prend la couleur d'audience pour signaler. */}
      <button onClick={() => go(loginPage)}
        onMouseEnter={() => setHovSec(true)}
        onMouseLeave={() => setHovSec(false)}
        style={{
          height: 36, padding: "0 14px",
          display: "inline-flex", alignItems: "center",
          background: hovSec
            ? `color-mix(in oklab, ${accent} 10%, var(--surface))`
            : "transparent",
          color: hovSec ? accentInk : "var(--ink-2)",
          border: `1px solid ${hovSec
            ? `color-mix(in oklab, ${accent} 35%, var(--line))`
            : "var(--line-strong)"}`,
          borderRadius: 999,
          fontWeight: 540, fontSize: 13.5, fontFamily: "inherit",
          letterSpacing: "-0.005em", cursor: "pointer",
          transition: "background .18s ease, color .18s ease, border-color .18s ease, transform .12s ease",
        }}
        onMouseDown={e => { e.currentTarget.style.transform = "scale(0.97)"; }}
        onMouseUp={e => { e.currentTarget.style.transform = "scale(1)"; }}>
        Connexion
      </button>

      {/* Créer un compte : pill plein audience-color, pas d'icône (le
          texte « Créer un compte » est explicite, l'icône ✨ faisait trop). */}
      <button onClick={() => go(signupPage)}
        onMouseEnter={() => setHovPrim(true)}
        onMouseLeave={() => setHovPrim(false)}
        style={{
          height: 36, padding: "0 16px",
          display: "inline-flex", alignItems: "center",
          background: accent, color: "#fff", border: "none",
          borderRadius: 999,
          fontWeight: 600, fontSize: 13.5, fontFamily: "inherit",
          letterSpacing: "-0.005em", cursor: "pointer",
          boxShadow: hovPrim
            ? `0 8px 22px -8px color-mix(in oklab, ${accent} 60%, transparent)`
            : `0 3px 10px -4px color-mix(in oklab, ${accent} 45%, transparent)`,
          transform: hovPrim ? "translateY(-1px)" : "translateY(0)",
          transition: "box-shadow .22s ease, transform .15s ease, filter .15s ease",
          filter: hovPrim ? "brightness(1.06)" : "brightness(1)",
        }}
        onMouseDown={e => { e.currentTarget.style.transform = "translateY(0) scale(0.97)"; }}
        onMouseUp={e => { e.currentTarget.style.transform = "translateY(-1px) scale(1)"; }}>
        Créer un compte
      </button>
    </div>
  );
};
const Nav = ({ page, go, user, audience }) => {
  const [open, setOpen] = React.useState(false);
  const loggedIn = !!user;
  const navLinks = audience === "client" ? NAV_LINKS_CLIENT : NAV_LINKS_PRO;

  // Détermine le rôle effectif :
  // - si logué : on lit user_metadata.role (les comptes pro legacy sans role
  //   sont traités comme "pro")
  // - si pas logué : on lit cb_last_audience pour proposer un raccourci vers
  //   le bon login. Ça évite à la cliente qui revient de devoir cliquer
  //   "Compte → Espace client" pour retomber sur la bonne page.
  const userRole = user && user.user_metadata && user.user_metadata.role;
  const effectiveRole = loggedIn
    ? (userRole === "client" ? "client" : "pro")
    : null;
  const lastAudience = React.useMemo(() => {
    try {
      const v = localStorage.getItem("cb_last_audience");
      return v === "pro" || v === "client" ? v : null;
    } catch { return null; }
  }, []);

  // CTA cible pour "Mon espace" — routing strict : un client se retrouve
  // toujours dans /?espace=me, un pro dans /?page=app, jamais l'inverse.
  const openMyEspace = () => {
    if (effectiveRole === "client") {
      try { window.location.href = "/?espace=me"; } catch {}
    } else {
      go("app");
    }
  };
  // CTA cible pour un retour après logout (mémoire du dernier rôle).
  const openLastLogin = () => {
    if (lastAudience === "client") {
      try { window.location.href = "/?espace=login"; } catch {}
    } else {
      go("login");
    }
  };

  // Navigation depuis le menu mobile : ferme d'abord le menu proprement,
  // puis navigue après la fin de la transition pour éviter le "flash" où
  // la nouvelle page apparaît sous le menu encore ouvert.
  const goFromMenu = (p, opts) => {
    setOpen(false);
    setTimeout(() => go(p, opts), 220);
  };

  // Transition Pro ↔ Client depuis le menu mobile : la petite encoche
  // de l'AudienceSwitch glisse (CSS transform 280ms) puis on navigue.
  // L'AudienceSwitchMobile gère lui-même la temporisation visuelle, on
  // n'a plus qu'à fermer le menu et à exécuter la nav.
  const switchAudienceFromMenu = (targetAudience) => {
    if (audience === targetAudience) return;
    setOpen(false);
    cbAudience.set(targetAudience);
    go(targetAudience === "pro" ? "home" : "clientHome");
  };

  return (
    <header style={{
      position: "sticky", top: 0, zIndex: 40,
      background: "color-mix(in oklab, var(--bg) 92%, transparent)",
      backdropFilter: "saturate(140%) blur(14px)",
      WebkitBackdropFilter: "saturate(140%) blur(14px)",
      // Démarcation menu/page plus marquée : bordure pleine + ombre douce
      // qui fait clairement décoller le menu de la page (au lieu du simple
      // 1px line invisible sur fond blanc).
      borderBottom: "1px solid var(--line-strong)",
      boxShadow: "0 6px 20px -10px rgba(15, 18, 30, 0.08), 0 2px 6px -2px rgba(15, 18, 30, 0.04)",
    }}>
      <div className="container" style={{
        display: "flex", alignItems: "center", justifyContent: "space-between",
        height: 64,
      }}>
        <a href="#home" onClick={(e) => { e.preventDefault(); go(audience === "client" ? "clientHome" : "home"); }} style={{ display: "inline-flex", alignItems: "center", gap: 8 }}>
          <Logo audience={audience}/>
          <span style={{
            padding: "2px 7px",
            background: audience === "client" ? "var(--cli-soft)" : "var(--accent-soft)",
            color: audience === "client" ? "var(--cli-ink)" : "var(--accent-ink)",
            fontSize: 9.5, fontWeight: 700,
            borderRadius: 4, letterSpacing: "0.08em",
            fontFamily: "var(--ff-text)",
            textTransform: "uppercase",
          }}>bêta</span>
        </a>
        <nav style={{ display: "flex", gap: 4, alignItems: "center" }} className="nav-links">
          {navLinks.map(l => {
            const active = page === l.id;
            // Pill active : indigo en mode pro, violet en mode client.
            const activeBg  = audience === "client" ? "var(--cli-soft)" : "var(--accent-soft)";
            const activeInk = audience === "client" ? "var(--cli-ink)"  : "var(--accent-ink)";
            return (
              <a key={l.id} href={`#${l.id}`} onClick={(e) => { e.preventDefault(); go(l.id); }}
                style={{
                  padding: "8px 14px",
                  fontSize: 14, fontWeight: active ? 580 : 460,
                  color: active ? activeInk : "var(--ink-3)",
                  background: active ? activeBg : "transparent",
                  borderRadius: 999,
                  transition: "color .15s, background .15s",
                }}
                onMouseEnter={(e) => { if (!active) e.currentTarget.style.color = "var(--ink)"; }}
                onMouseLeave={(e) => { if (!active) e.currentTarget.style.color = "var(--ink-3)"; }}
              >
                {l.label}
              </a>
            );
          })}
        </nav>
        <div style={{ display: "flex", alignItems: "center", gap: 10 }} className="nav-cta">
          {/* Switch audience à gauche, CTA principal à droite (placement plus
              propre : le switch sert d'index visuel, le bouton coloré attire
              l'œil tout à droite, dans la zone d'action naturelle). */}
          <AudienceSwitch audience={audience} go={go} compact/>
          {loggedIn ? (
            <OpenAppButton
              audience={effectiveRole || audience}
              label="Mon espace"
              onClick={openMyEspace}
            />
          ) : lastAudience ? (
            // Mémoire post-logout : on propose un retour direct dans le bon
            // espace, sans risquer de basculer vers l'autre rôle.
            <OpenAppButton
              audience={lastAudience}
              label={lastAudience === "client" ? "Espace client" : "Espace pro"}
              onClick={openLastLogin}
            />
          ) : (
            <CompteMenu audience={audience} go={go}/>
          )}
        </div>
        <button className="nav-menu-btn" onClick={() => setOpen(!open)} aria-label="Menu" aria-expanded={open}
          style={{
            // Audience-aware : la croix de fermeture (et le fond/border quand
            // le menu est ouvert) prend la couleur de l'audience active,
            // indigo en pro, violet en client.
            display: "none",
            background: open
              ? (audience === "client" ? "var(--cli-soft)" : "var(--accent-soft)")
              : "transparent",
            border: "1px solid " + (open
              ? (audience === "client" ? "var(--cli-soft-2)" : "var(--accent-soft-2)")
              : "var(--line)"),
            borderRadius: 12,
            width: 42, height: 42, alignItems: "center", justifyContent: "center",
            cursor: "pointer",
            color: open
              ? (audience === "client" ? "var(--cli-ink)" : "var(--accent-ink)")
              : "var(--ink)",
            transition: "background 0.2s ease, border-color 0.2s ease, color 0.2s ease",
          }}>
          <div style={{ width: 18, height: 12, position: "relative" }}>
            <span style={{
              position: "absolute", top: open ? 5 : 0, left: 0,
              width: 18, height: 2, background: "currentColor", borderRadius: 2,
              transform: open ? "rotate(45deg)" : "rotate(0)",
              transition: "transform 0.28s cubic-bezier(0.22, 1, 0.36, 1), top 0.28s cubic-bezier(0.22, 1, 0.36, 1)",
            }}/>
            <span style={{
              position: "absolute", top: 5, left: 0,
              width: 18, height: 2, background: "currentColor", borderRadius: 2,
              opacity: open ? 0 : 1,
              transform: open ? "translateX(8px)" : "translateX(0)",
              transition: "opacity 0.18s ease, transform 0.2s ease",
            }}/>
            <span style={{
              position: "absolute", bottom: open ? 5 : 0, left: 0,
              width: 18, height: 2, background: "currentColor", borderRadius: 2,
              transform: open ? "rotate(-45deg)" : "rotate(0)",
              transition: "transform 0.28s cubic-bezier(0.22, 1, 0.36, 1), bottom 0.28s cubic-bezier(0.22, 1, 0.36, 1)",
            }}/>
          </div>
        </button>
      </div>
      {/* Backdrop */}
      <div
        onClick={() => setOpen(false)}
        aria-hidden="true"
        style={{
          position: "fixed", inset: "64px 0 0 0", zIndex: 39,
          background: "color-mix(in oklab, var(--ink) 18%, transparent)",
          backdropFilter: "blur(2px)", WebkitBackdropFilter: "blur(2px)",
          opacity: open ? 1 : 0,
          pointerEvents: open ? "auto" : "none",
          transition: "opacity 0.28s ease",
        }}
      />
      {/* Mobile menu, animated slide-down */}
      <div style={{
        position: "absolute", top: 64, left: 0, right: 0, zIndex: 40,
        borderTop: "1px solid var(--line)",
        background: "var(--bg)",
        boxShadow: open ? "0 18px 40px -18px rgba(0,0,0,0.18)" : "none",
        maxHeight: open ? 520 : 0,
        opacity: open ? 1 : 0,
        overflow: "hidden",
        transition: "max-height 0.34s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.22s ease, box-shadow 0.3s ease",
      }}>
        <div style={{ padding: "14px 20px 22px" }}>
          {navLinks.map((l, i) => {
            const active = page === l.id;
            const activeBg  = audience === "client" ? "var(--cli-soft)" : "var(--accent-soft)";
            const activeInk = audience === "client" ? "var(--cli-ink)"  : "var(--accent-ink)";
            return (
              <a key={l.id} href={`#${l.id}`}
                onClick={(e) => { e.preventDefault(); goFromMenu(l.id); }}
                style={{
                  display: "flex", alignItems: "center", justifyContent: "space-between",
                  padding: "14px 14px",
                  margin: "2px 0",
                  color: active ? activeInk : "var(--ink)",
                  background: active ? activeBg : "transparent",
                  fontWeight: active ? 560 : 500, fontSize: 15,
                  borderRadius: 10,
                  opacity: open ? 1 : 0,
                  transform: open ? "translateX(0)" : "translateX(-12px)",
                  transition: `opacity 0.32s ease ${0.06 + i * 0.05}s, transform 0.32s cubic-bezier(0.22, 1, 0.36, 1) ${0.06 + i * 0.05}s`,
                }}>
                <span>{l.label}</span>
                <span aria-hidden style={{
                  color: active ? activeInk : "var(--ink-4)",
                  fontSize: 18, lineHeight: 1,
                }}>›</span>
              </a>
            );
          })}
          <div style={{
            display: "flex", flexDirection: "column", gap: 10, marginTop: 18,
            paddingTop: 16, borderTop: "1px solid var(--line)",
            opacity: open ? 1 : 0,
            transform: open ? "translateY(0)" : "translateY(10px)",
            transition: `opacity 0.34s ease ${navLinks.length * 0.05 + 0.08}s, transform 0.34s cubic-bezier(0.22, 1, 0.36, 1) ${navLinks.length * 0.05 + 0.08}s`,
          }}>
            {/* Switch audience mobile : utilise le handler avec overlay
                de transition pour donner un effet « slide d'univers » au
                lieu d'un swap instantané. */}
            <AudienceSwitchMobile
              audience={audience}
              onSwitch={switchAudienceFromMenu}
            />
            {loggedIn ? (
              <OpenAppButton
                large
                audience={effectiveRole || audience}
                label="Mon espace"
                onClick={() => {
                  setOpen(false);
                  setTimeout(openMyEspace, 220);
                }}
              />
            ) : lastAudience ? (
              <OpenAppButton
                large
                audience={lastAudience}
                label={lastAudience === "client" ? "Espace client" : "Espace pro"}
                onClick={() => {
                  setOpen(false);
                  setTimeout(openLastLogin, 220);
                }}
              />
            ) : (
              <>
                <a onClick={() => { goFromMenu(audience === "client" ? "clientSignup" : "signup"); }}
                  className="btn btn-lg"
                  style={{
                    cursor: "pointer", width: "100%",
                    background: audience === "client" ? "var(--cli-accent)" : "var(--accent)",
                    color: "#fff", border: "none",
                  }}>
                  Créer un compte <Icon name="arrow" size={15}/>
                </a>
                <a onClick={() => { goFromMenu(audience === "client" ? "clientLogin" : "login"); }}
                  className="btn btn-ghost btn-lg"
                  style={{ cursor: "pointer", width: "100%" }}>
                  Se connecter
                </a>
              </>
            )}
          </div>
        </div>
      </div>
    </header>
  );
};

const Footer = ({ go, audience }) => {
  const isMobile = useIsMobile(720);
  return (
    <footer style={{ borderTop: "1px solid var(--line)", background: "var(--bg-section)", marginTop: 0 }}>
      <div className="container" style={{ padding: isMobile ? "40px 24px 24px" : "64px 24px 32px" }}>
        {isMobile ? (
          /* Mobile: équilibré, tagline, email, 2 colonnes de liens, badges, signature */
          <div style={{ display: "flex", flexDirection: "column", gap: 20 }}>
            <div style={{ textAlign: "center" }}>
              <Logo audience={audience}/>
              <p style={{ fontSize: 13.5, color: "var(--ink-3)", lineHeight: 1.5, margin: "10px auto 0", maxWidth: 300 }}>
                L'outil tout-en-un pour les indépendants de service.
              </p>
            </div>

            <a href="mailto:clientbase.fr@gmail.com" style={{
              alignSelf: "center",
              padding: "8px 14px 8px 12px",
              background: "var(--surface)", border: "1px solid var(--line)",
              borderRadius: 999,
              fontSize: 13, color: "var(--accent-ink)", fontWeight: 520,
              display: "inline-flex", alignItems: "center", gap: 7,
              textDecoration: "none",
            }}>
              <Icon name="mail" size={13}/> clientbase.fr@gmail.com
            </a>

            {/* 2 colonnes de petits liens (Produit / ClientBase),
                puis 1 rangée "Alternatives" en dessous (liens externes SEO).
                Les alternatives ne doivent PAS apparaître dans le menu
                principal — uniquement ici en footer (volonté du client). */}
            <nav style={{
              display: "grid", gridTemplateColumns: "1fr 1fr",
              columnGap: 24, rowGap: 2,
              padding: "16px 20px",
              borderTop: "1px solid var(--line)", borderBottom: "1px solid var(--line)",
            }}>
              {[
                { col: "Produit", items: [
                  ["Tarif",      "pricing"],
                  ["Guide",      "features"],
                  ["Blog",       "blog"],
                  ["FAQ",        "faq"],
                  ["Nouveautés", "changelog"],
                ]},
                { col: "ClientBase", items: [
                  ["Contact",  "contact"],
                  ["À propos", "about"],
                  ["Légal",    "legal"],
                ]},
              ].map(group => (
                <div key={group.col} style={{ display: "flex", flexDirection: "column", gap: 4 }}>
                  <div style={{
                    fontSize: 11, fontWeight: 600, color: "var(--ink-4)",
                    textTransform: "uppercase", letterSpacing: "0.04em", marginBottom: 6,
                  }}>{group.col}</div>
                  {group.items.map(([label, page]) => (
                    <a key={page} href={`#${page}`} onClick={(e) => { e.preventDefault(); go(page); }}
                      style={{ fontSize: 13.5, color: "var(--ink-2)", padding: "4px 0", textDecoration: "none" }}>
                      {label}
                    </a>
                  ))}
                </div>
              ))}
            </nav>

            {/* Section Alternatives compacte mobile : liens directs vers
                les landings statiques (SEO interne, signal vers Google). */}
            <div style={{ padding: "4px 20px 0" }}>
              <div style={{
                fontSize: 11, fontWeight: 600, color: "var(--ink-4)",
                textTransform: "uppercase", letterSpacing: "0.04em", marginBottom: 8,
              }}>Alternatives</div>
              <div style={{
                display: "flex", gap: 6, flexWrap: "wrap",
              }}>
                {[
                  ["Planity",   "/alternative-planity"],
                  ["Treatwell", "/alternative-treatwell"],
                  ["Kiute Pro", "/alternative-kiute-pro"],
                  ["Fresha",    "/alternative-fresha"],
                  ["iAra",      "/alternative-iara"],
                  ["Métiers",   "/metiers"],
                  ["Guides",    "/guides/quitter-planity-sans-perdre-clients"],
                  ["Tous →",    "/alternatives"],
                ].map(([label, href]) => (
                  <a key={href} href={href}
                    style={{
                      fontSize: 12.5, color: "var(--ink-2)",
                      padding: "5px 10px",
                      background: "var(--surface)", border: "1px solid var(--line)",
                      borderRadius: 999, textDecoration: "none",
                    }}>{label}</a>
                ))}
              </div>
            </div>

            {/* Badges de rassurance */}
            <div style={{ display: "flex", gap: 6, justifyContent: "center", flexWrap: "wrap" }}>
              <span className="badge" style={{ background: "var(--sage-soft)", color: "oklch(38% 0.08 160)", borderColor: "oklch(88% 0.03 160)" }}>
                <Icon name="shield" size={12}/> RGPD · données chez vous
              </span>
              <a href="#changelog" onClick={(e) => { e.preventDefault(); go("changelog"); }}
                className="badge"
                style={{ background: "var(--accent-soft)", color: "var(--accent-ink)", borderColor: "var(--accent-soft-2)", textDecoration: "none" }}>
                <Icon name="sparkle" size={12}/> v{CB_VERSION}, nouveautés
              </a>
            </div>

            {/* Signature */}
            <div style={{
              textAlign: "center", fontSize: 12, color: "var(--ink-4)",
            }}>
              © 2026 ClientBase · clientbase.fr
            </div>
          </div>
        ) : (
          /* Desktop: 4-column layout (Logo / Produit / Légal / Comparatifs) */
          <>
            <div style={{
              display: "grid",
              gridTemplateColumns: "1.4fr 1fr 1fr 1fr",
              gap: 40,
            }}>
              <div>
                <Logo audience={audience}/>
                <p style={{ marginTop: 14, fontSize: 14, maxWidth: 300, color: "var(--ink-3)" }}>
                  L'outil tout-en-un pour les indépendants et petites entreprises de service.
                </p>
                <a href="mailto:clientbase.fr@gmail.com" style={{
                  marginTop: 12, fontSize: 13.5, color: "var(--accent-ink)", fontWeight: 520,
                  display: "inline-flex", alignItems: "center", gap: 6,
                }}>
                  <Icon name="mail" size={13}/> clientbase.fr@gmail.com
                </a>
                <div style={{ marginTop: 18, display: "flex", gap: 8 }}>
                  <span className="badge" style={{ background: "var(--sage-soft)", color: "oklch(38% 0.08 160)", borderColor: "oklch(88% 0.03 160)" }}>
                    <Icon name="shield" size={12}/> RGPD · données chez vous
                  </span>
                </div>
              </div>
              <FooterCol title="Produit" links={[
                ["Tarif", "pricing"],
                ["Guide", "features"],
                ["Blog", "blog"],
                ["FAQ", "faq"],
                ["Contact", "contact"],
                ["À propos", "about"],
                ["Nouveautés", "changelog"],
              ]} go={go}/>
              <FooterCol title="Légal" links={[
                ["Confidentialité (RGPD)", "legal", "privacy"],
                ["CGU", "legal", "cgu"],
                ["Cookies", "legal", "cookies"],
                ["Mentions légales", "legal", "mentions"],
              ]} go={go}/>
              {/* Comparatifs : liens externes vers landings statiques (SEO).
                  Ne PAS mettre dans le menu principal (volonté du client),
                  uniquement présents ici dans le footer. */}
              <FooterColExt title="Alternatives" links={[
                ["Alternative à Planity",   "/alternative-planity"],
                ["Alternative à Treatwell", "/alternative-treatwell"],
                ["Alternative à Kiute Pro", "/alternative-kiute-pro"],
                ["Alternative à Fresha",    "/alternative-fresha"],
                ["Alternative à iAra",      "/alternative-iara"],
                ["Tous les comparatifs",    "/alternatives"],
                ["—",                       "#"],
                ["Logiciel par métier",     "/metiers"],
                ["Calculateur commission",  "/calculateur-commission"],
                ["Guide : quitter Planity", "/guides/quitter-planity-sans-perdre-clients"],
                ["Guide : checklist auto-entrepreneur", "/guides/auto-entrepreneur-beaute-checklist"],
                ["—",                       "#"],
                ["Espace presse",           "/presse"],
                ["Laisser un témoignage",   "/temoigner"],
              ].filter(([l]) => l !== "—")}/>
            </div>
            <div style={{
              marginTop: 40, paddingTop: 24, borderTop: "1px solid var(--line)",
              display: "flex", justifyContent: "space-between", alignItems: "center",
              fontSize: 13, color: "var(--ink-4)", flexWrap: "wrap", gap: 12,
            }}>
              <div>
                © 2026 ClientBase. Tous droits réservés. ·{" "}
                <a href="#changelog" onClick={(e) => { e.preventDefault(); go("changelog"); }}
                  style={{ color: "var(--accent-ink)", fontWeight: 520 }}>
                  v{CB_VERSION}, voir les nouveautés
                </a>
              </div>
              <div style={{ color: "var(--ink-3)", fontWeight: 500 }}>clientbase.fr</div>
            </div>
          </>
        )}
      </div>
    </footer>
  );
};

const FooterCol = ({ title, links, go }) => (
  <div>
    <div style={{
      fontSize: 12, textTransform: "uppercase", letterSpacing: "0.08em",
      color: "var(--ink-4)", fontWeight: 500, marginBottom: 14,
    }}>{title}</div>
    <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
      {links.map(([label, page, section]) => (
        <a key={label} href={`#${page}${section ? "-" + section : ""}`}
          onClick={(e) => {
            e.preventDefault();
            if (section) { try { window.__cbLegalSection = section; } catch {} }
            go(page);
          }}
          style={{ fontSize: 14, color: "var(--ink-2)", width: "fit-content" }}
          onMouseEnter={(e) => e.currentTarget.style.color = "var(--ink)"}
          onMouseLeave={(e) => e.currentTarget.style.color = "var(--ink-2)"}>
          {label}
        </a>
      ))}
    </div>
  </div>
);

/* Variante FooterCol pour liens EXTERNES (vers pages statiques HTML,
   par ex. /alternative-planity). Pas de routing SPA : navigation réelle
   pour que Googlebot voie le lien sortant et passe du link juice. */
const FooterColExt = ({ title, links }) => (
  <div>
    <div style={{
      fontSize: 12, textTransform: "uppercase", letterSpacing: "0.08em",
      color: "var(--ink-4)", fontWeight: 500, marginBottom: 14,
    }}>{title}</div>
    <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
      {links.map(([label, href]) => (
        <a key={label} href={href}
          style={{ fontSize: 14, color: "var(--ink-2)", width: "fit-content", textDecoration: "none" }}
          onMouseEnter={(e) => e.currentTarget.style.color = "var(--ink)"}
          onMouseLeave={(e) => e.currentTarget.style.color = "var(--ink-2)"}>
          {label}
        </a>
      ))}
    </div>
  </div>
);

/* ============ CHANGELOG PAGE ============ */
const ChangelogPage = ({ go }) => (
  <>
    <section style={{ padding: "80px 0 24px", textAlign: "center" }}>
      <div className="container">
        <div className="eyebrow">Nouveautés</div>
        <h1 style={{ marginTop: 14, fontSize: "clamp(36px, 4.2vw, 54px)" }}>
          Ce qui vient d'arriver sur <span style={{ color: "var(--accent)" }}>ClientBase</span>.
        </h1>
        <p style={{ marginTop: 14, fontSize: 16, maxWidth: 560, marginLeft: "auto", marginRight: "auto", color: "var(--ink-3)" }}>
          On avance chaque semaine. Revenez ici pour voir ce qui change et ce qui arrive bientôt.
        </p>
      </div>
    </section>
    <section style={{ padding: "16px 0 80px" }}>
      <div className="container" style={{ maxWidth: 720 }}>
        <div style={{ position: "relative" }}>
          <div aria-hidden style={{
            position: "absolute", left: 13, top: 8, bottom: 8, width: 2,
            background: "linear-gradient(180deg, var(--accent-soft-2), var(--line))",
            borderRadius: 2,
          }}/>
          {CB_CHANGELOG.map((r, i) => (
            <div key={r.version} style={{ position: "relative", paddingLeft: 42, paddingBottom: i < CB_CHANGELOG.length - 1 ? 28 : 0 }}>
              <div style={{
                position: "absolute", left: 4, top: 4,
                width: 20, height: 20, borderRadius: 50,
                background: r.upcoming ? "var(--bg)" : "var(--accent)",
                border: `2px solid ${r.upcoming ? "var(--accent-soft-2)" : "var(--accent)"}`,
                display: "flex", alignItems: "center", justifyContent: "center",
                color: "white",
              }}>
                {!r.upcoming && <Icon name="check" size={11} stroke={3}/>}
              </div>
              <div style={{
                background: "var(--surface)",
                border: "1px solid var(--line)", borderRadius: 14, padding: "18px 22px",
              }}>
                <div style={{ display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap" }}>
                  <span style={{
                    fontFamily: "inherit", fontSize: 13,
                    padding: "2px 8px", background: r.upcoming ? "var(--bg-alt)" : "var(--accent-soft)",
                    color: r.upcoming ? "var(--ink-3)" : "var(--accent-ink)",
                    borderRadius: 6, fontWeight: 600,
                  }}>v{r.version}</span>
                  <span style={{ fontSize: 12.5, color: "var(--ink-4)" }}>{r.date}</span>
                  <span style={{
                    padding: "2px 8px", fontSize: 11, fontWeight: 600,
                    background: r.upcoming ? "oklch(95% 0.05 300)" : "var(--sage-soft)",
                    color:      r.upcoming ? "oklch(42% 0.18 300)" : "oklch(38% 0.08 160)",
                    borderRadius: 999,
                  }}>{r.tag}</span>
                </div>
                <h3 style={{ marginTop: 10, fontSize: 18, letterSpacing: "-0.015em" }}>{r.title}</h3>
                <ul style={{ listStyle: "none", padding: 0, margin: "12px 0 0", display: "flex", flexDirection: "column", gap: 8 }}>
                  {r.items.map(it => (
                    <li key={it} style={{ display: "flex", gap: 10, alignItems: "flex-start", fontSize: 14, color: "var(--ink-2)", lineHeight: 1.5 }}>
                      <span style={{
                        width: 18, height: 18, borderRadius: 50, flexShrink: 0, marginTop: 1,
                        background: r.upcoming ? "var(--bg-alt)" : "var(--sage-soft)",
                        color: r.upcoming ? "var(--ink-4)" : "var(--sage)",
                        display: "inline-flex", alignItems: "center", justifyContent: "center",
                      }}>
                        <Icon name={r.upcoming ? "clock" : "check"} size={10} stroke={2.6}/>
                      </span>
                      {it}
                    </li>
                  ))}
                </ul>
              </div>
            </div>
          ))}
        </div>

        <div style={{
          marginTop: 32, padding: "16px 20px",
          background: "var(--bg-alt)", border: "1px dashed var(--line-strong)",
          borderRadius: 12, fontSize: 13.5, color: "var(--ink-2)", lineHeight: 1.6,
          display: "flex", gap: 12, alignItems: "center", flexWrap: "wrap",
        }}>
          <Icon name="mail" size={16} style={{ color: "var(--accent-ink)", flexShrink: 0 }}/>
          <div style={{ flex: 1, minWidth: 220 }}>
            Une idée ? Un retour ? Écrivez-nous, chaque message est lu par l'équipe.
          </div>
          <button className="btn btn-ghost btn-sm" onClick={() => go("contact")}>
            Nous écrire
          </button>
        </div>
      </div>
    </section>
  </>
);

/* ===========================================================
   Date helpers, sémantique unifiée pour `appointments.day`.
   La valeur stockée est un OFFSET ABSOLU en jours depuis l'ancre
   (1er janvier 2026, locale). C'est stable dans le temps et
   permet aux agendas pro et aux liens publics de se comprendre.
=========================================================== */
const CB_DAY_ANCHOR = new Date(2026, 0, 1); // 1er janv. 2026, minuit local
const cbDayOffsetFrom = (date) => {
  const d = date || new Date();
  const start = new Date(d.getFullYear(), d.getMonth(), d.getDate());
  return Math.round((start.getTime() - CB_DAY_ANCHOR.getTime()) / 86400000);
};
const cbTodayOffset = () => cbDayOffsetFrom(new Date());
const cbDateForOffset = (off) => {
  const d = new Date(CB_DAY_ANCHOR);
  d.setDate(d.getDate() + off);
  return d;
};
const cbMondayOffset = (refDate) => {
  // Offset absolu du lundi de la semaine de refDate (ou aujourd'hui)
  const d = refDate ? new Date(refDate) : new Date();
  d.setHours(0, 0, 0, 0);
  const dow = d.getDay() || 7; // 1..7
  d.setDate(d.getDate() - (dow - 1));
  return cbDayOffsetFrom(d);
};
const CB_FR_DAYS_SHORT = ["Dim", "Lun", "Mar", "Mer", "Jeu", "Ven", "Sam"];
const CB_FR_MONTHS_SHORT = ["janv.", "févr.", "mars", "avril", "mai", "juin", "juil.", "août", "sept.", "oct.", "nov.", "déc."];
const CB_FR_MONTHS_LONG = ["janvier", "février", "mars", "avril", "mai", "juin", "juillet", "août", "septembre", "octobre", "novembre", "décembre"];
const cbYmd = (d) => `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`;

// ============================================================================
// CALENDAR EXPORT — ICS + deeplinks Google / Outlook / Apple
// ============================================================================
// Helpers partagés pour exporter un RDV (ou une série de RDV) vers le
// calendrier de l'utilisateur. Format ICS (RFC 5545) = standard universel
// compatible avec Apple Calendar, Thunderbird, Outlook desktop, et
// importable dans Google Calendar et Outlook.com.
//
// Pour Google et Outlook online, on génère des URLs deeplink qui ouvrent
// directement le formulaire de création d'événement avec les champs
// pré-remplis — pas besoin de télécharger l'ICS, c'est instantané.
//
// Pour Apple Calendar / Thunderbird, on télécharge un fichier .ics que
// l'OS associe à l'app calendrier par défaut (double-clic l'ajoute).
// ----------------------------------------------------------------------------

// Format date pour ICS : YYYYMMDDTHHMMSS (sans Z = local time floating).
// On utilise local time pour éviter les conversions UTC qui décalent
// le RDV d'une heure quand la TZ pro ≠ TZ client.
const cbIcsDate = (d) => {
  const Y = d.getFullYear();
  const M = String(d.getMonth() + 1).padStart(2, "0");
  const D = String(d.getDate()).padStart(2, "0");
  const h = String(d.getHours()).padStart(2, "0");
  const m = String(d.getMinutes()).padStart(2, "0");
  return `${Y}${M}${D}T${h}${m}00`;
};

// Échappement des caractères spéciaux ICS (RFC 5545 section 3.3.11).
// Surtout : \\ , ; et \n doivent être escapés.
const cbIcsEscape = (s) => String(s || "")
  .replace(/\\/g, "\\\\")
  .replace(/;/g, "\\;")
  .replace(/,/g, "\\,")
  .replace(/\n/g, "\\n");

// Génère le contenu ICS pour un ou plusieurs événements. Chaque event
// doit avoir : { uid, start (Date), end (Date), title, description, location }.
const cbBuildICS = (events) => {
  const list = Array.isArray(events) ? events : [events];
  const now = cbIcsDate(new Date());
  const lines = [
    "BEGIN:VCALENDAR",
    "VERSION:2.0",
    "PRODID:-//ClientBase//FR//EN",
    "CALSCALE:GREGORIAN",
    "METHOD:PUBLISH",
  ];
  for (const e of list) {
    if (!e || !e.start) continue;
    const endDate = e.end || new Date(e.start.getTime() + 60 * 60000);
    lines.push(
      "BEGIN:VEVENT",
      `UID:${e.uid || (Math.random().toString(36).slice(2) + "@clientbase.fr")}`,
      `DTSTAMP:${now}`,
      `DTSTART:${cbIcsDate(e.start)}`,
      `DTEND:${cbIcsDate(endDate)}`,
      `SUMMARY:${cbIcsEscape(e.title || "Rendez-vous")}`,
    );
    if (e.description) lines.push(`DESCRIPTION:${cbIcsEscape(e.description)}`);
    if (e.location)    lines.push(`LOCATION:${cbIcsEscape(e.location)}`);
    lines.push("END:VEVENT");
  }
  lines.push("END:VCALENDAR");
  return lines.join("\r\n");
};

// Déclenche le téléchargement d'un .ics dans le navigateur. Marche sur
// tous les navigateurs modernes (Blob + temporary <a download>).
const cbDownloadICS = (filename, icsContent) => {
  try {
    const blob = new Blob([icsContent], { type: "text/calendar;charset=utf-8" });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    a.download = filename.endsWith(".ics") ? filename : `${filename}.ics`;
    document.body.appendChild(a);
    a.click();
    a.remove();
    setTimeout(() => URL.revokeObjectURL(url), 100);
    return true;
  } catch {
    return false;
  }
};

// Construit les deeplinks Google / Outlook online pour 1 événement.
// Google attend des dates ISO compactes UTC (YYYYMMDDTHHMMSSZ).
const cbCalendarLinks = (e) => {
  if (!e || !e.start) return {};
  const endDate = e.end || new Date(e.start.getTime() + 60 * 60000);
  // Pour Google et Outlook online, on convertit en UTC car ils
  // interprètent les dates sans suffixe comme local au browser de l'user.
  const toUtcCompact = (d) => {
    const Y = d.getUTCFullYear();
    const M = String(d.getUTCMonth() + 1).padStart(2, "0");
    const D = String(d.getUTCDate()).padStart(2, "0");
    const h = String(d.getUTCHours()).padStart(2, "0");
    const m = String(d.getUTCMinutes()).padStart(2, "0");
    return `${Y}${M}${D}T${h}${m}00Z`;
  };
  const isoLocal = (d) => {
    const Y = d.getFullYear();
    const M = String(d.getMonth() + 1).padStart(2, "0");
    const D = String(d.getDate()).padStart(2, "0");
    const h = String(d.getHours()).padStart(2, "0");
    const m = String(d.getMinutes()).padStart(2, "0");
    return `${Y}-${M}-${D}T${h}:${m}:00`;
  };
  const title = encodeURIComponent(e.title || "Rendez-vous");
  const desc  = encodeURIComponent(e.description || "");
  const loc   = encodeURIComponent(e.location || "");
  const googleDates = `${toUtcCompact(e.start)}/${toUtcCompact(endDate)}`;
  return {
    google: `https://calendar.google.com/calendar/render?action=TEMPLATE&text=${title}&dates=${googleDates}&details=${desc}&location=${loc}`,
    outlook: `https://outlook.live.com/calendar/0/deeplink/compose?path=/calendar/action/compose&rru=addevent&subject=${title}&body=${desc}&startdt=${encodeURIComponent(isoLocal(e.start))}&enddt=${encodeURIComponent(isoLocal(endDate))}&location=${loc}`,
    office365: `https://outlook.office.com/calendar/0/deeplink/compose?path=/calendar/action/compose&rru=addevent&subject=${title}&body=${desc}&startdt=${encodeURIComponent(isoLocal(e.start))}&enddt=${encodeURIComponent(isoLocal(endDate))}&location=${loc}`,
  };
};

// Composant UI réutilisable : 3 boutons « Ajouter à mon calendrier »
// avec logos Google / Apple / Outlook. Le clic Google/Outlook ouvre une
// nouvelle fenêtre vers le deeplink, le clic Apple télécharge l'ICS
// (Apple Calendar s'ouvre automatiquement avec le .ics).
//
// Props :
//   - event : { uid, start, end, title, description, location }
//     OU events : [event, ...] pour exporter plusieurs RDV (Apple .ics
//     seulement, pas de bulk deeplink Google/Outlook).
//   - filename : nom du fichier .ics (sans l'extension), défaut "rdv".
//   - compact : version sur 1 ligne (3 boutons icon-only) au lieu de
//     boutons avec label complet.
// Brand icons : logos officiels d'Apple, Google et Outlook en SVG inline.
// Utilisés à la fois dans CalendarExportButtons (shared) et dans la
// dropdown CalendarExportDropdown du dashboard pro.
const CbCalIconApple = ({ size = 14 }) => (
  // Silhouette pomme croquée Apple (path simplifié officiel)
  <svg width={size} height={size} viewBox="0 0 24 24" fill="currentColor" aria-hidden>
    <path d="M17.04 12.34c-.02-2.04 1.67-3.02 1.75-3.07-.95-1.4-2.45-1.59-2.97-1.61-1.26-.13-2.46.74-3.1.74-.65 0-1.63-.72-2.69-.7-1.38.02-2.66.81-3.37 2.05-1.44 2.5-.37 6.2 1.04 8.23.69 1 1.5 2.12 2.57 2.08 1.04-.04 1.43-.67 2.68-.67s1.6.67 2.69.65c1.12-.02 1.82-1.01 2.5-2.02.79-1.16 1.11-2.29 1.13-2.35-.02-.01-2.17-.83-2.19-3.3zM15.06 6.4c.57-.69.96-1.65.85-2.6-.82.03-1.81.55-2.4 1.24-.53.61-.99 1.59-.87 2.52.92.07 1.85-.47 2.42-1.16z"/>
  </svg>
);
const CbCalIconGoogle = ({ size = 14 }) => (
  // Google "G" multicolore — 4 paths (bleu, vert, jaune, rouge)
  <svg width={size} height={size} viewBox="0 0 24 24" aria-hidden>
    <path fill="#4285F4" d="M21.6 12.227c0-.708-.064-1.39-.182-2.045H12v3.868h5.382a4.6 4.6 0 0 1-1.996 3.018v2.51h3.232c1.891-1.741 2.982-4.305 2.982-7.351z"/>
    <path fill="#34A853" d="M12 22c2.7 0 4.964-.895 6.618-2.422l-3.232-2.51c-.895.6-2.04.955-3.386.955-2.605 0-4.81-1.76-5.595-4.124H3.064v2.59A9.996 9.996 0 0 0 12 22z"/>
    <path fill="#FBBC05" d="M6.405 13.9a6.003 6.003 0 0 1 0-3.8V7.51H3.064a9.996 9.996 0 0 0 0 8.98z"/>
    <path fill="#EA4335" d="M12 5.977c1.468 0 2.786.504 3.823 1.495l2.868-2.868C16.96 2.99 14.696 2 12 2A9.996 9.996 0 0 0 3.064 7.51l3.341 2.59C7.19 7.737 9.395 5.977 12 5.977z"/>
  </svg>
);
const CbCalIconOutlook = ({ size = 14 }) => (
  // Outlook : carré bleu Microsoft + "O" blanc + flap mail stylisé
  <svg width={size} height={size} viewBox="0 0 32 32" aria-hidden>
    <rect width="32" height="32" rx="5" fill="#0078D4"/>
    <ellipse cx="13" cy="16" rx="4.4" ry="5" fill="none" stroke="#fff" strokeWidth="2.2"/>
    <path d="M19.5 11.5 H27 V20.5 H19.5 Z" fill="#fff" opacity="0.92"/>
    <path d="M19.5 11.5 L23.25 14.8 L27 11.5" stroke="#0078D4" strokeWidth="1.5" fill="none"/>
  </svg>
);
if (typeof window !== "undefined") {
  Object.assign(window, { CbCalIconApple, CbCalIconGoogle, CbCalIconOutlook });
}

const CalendarExportButtons = ({ event, events, filename = "rdv", compact = false }) => {
  const arr = events || (event ? [event] : []);
  if (arr.length === 0) return null;
  const single = arr.length === 1 ? arr[0] : null;
  const links = single ? cbCalendarLinks(single) : null;

  const handleApple = () => {
    const ics = cbBuildICS(arr);
    cbDownloadICS(filename, ics);
  };
  const handleGoogle = () => {
    if (!links) return handleApple();
    window.open(links.google, "_blank", "noopener");
  };
  const handleOutlook = () => {
    if (!links) return handleApple();
    window.open(links.outlook, "_blank", "noopener");
  };

  const btnBase = {
    display: "inline-flex", alignItems: "center", gap: compact ? 0 : 8,
    padding: compact ? "8px 10px" : "9px 14px",
    background: "var(--surface)",
    border: "1px solid var(--line-strong)",
    borderRadius: 10,
    cursor: "pointer", fontFamily: "inherit",
    fontSize: 13, fontWeight: 540,
    color: "var(--ink)",
    transition: "background .15s ease, border-color .15s ease, transform .12s ease",
  };

  const Btn = ({ onClick, logo, label, bg, fg }) => {
    const [hover, setHover] = React.useState(false);
    return (
      <button onClick={onClick}
        onMouseEnter={() => setHover(true)}
        onMouseLeave={() => setHover(false)}
        title={label}
        style={{
          ...btnBase,
          background: hover ? "var(--bg-alt)" : "var(--surface)",
          borderColor: hover ? bg : "var(--line-strong)",
        }}
        onMouseDown={e => { e.currentTarget.style.transform = "scale(0.97)"; }}
        onMouseUp={e => { e.currentTarget.style.transform = "scale(1)"; }}>
        <span aria-hidden style={{
          width: 22, height: 22, borderRadius: 6, flexShrink: 0,
          background: bg, color: fg,
          display: "inline-flex", alignItems: "center", justifyContent: "center",
          fontWeight: 700, fontSize: 13,
          fontFamily: "var(--ff-display)",
        }}>{logo}</span>
        {!compact && <span>{label}</span>}
      </button>
    );
  };

  return (
    <div style={{ display: "inline-flex", flexWrap: "wrap", gap: 8 }}>
      <Btn onClick={handleGoogle}
        logo={<CbCalIconGoogle size={14}/>} label="Google"
        bg="#fff" fg="#4285F4"/>
      <Btn onClick={handleApple}
        logo={<CbCalIconApple size={14}/>} label="Apple"
        bg="#000" fg="#fff"/>
      <Btn onClick={handleOutlook}
        logo={<CbCalIconOutlook size={14}/>} label="Outlook"
        bg="transparent" fg="#0078D4"/>
    </div>
  );
};

// ============================================================================
// SIGNUP BOTTOM SLOT — bouton démo + promesses ✓ harmonisés pro / client
// ============================================================================
// 3 éléments empilés, identiques sur les deux flows (pro et client) :
//   1. Bouton « Voir le compte démo » full-width pill blanc avec point
//      vert pulsant (signal « live demo dispo »).
//   2. 3 promises avec ✓ checkmark : sans CB, 30 sec, France.
//   3. Couleur du ✓ adaptée à l'audience.
// Le bouton démo accepte soit `onClick` (pour appeler cbStartDemo côté
// pro), soit `href` (lien direct pour côté client → /?espace=demo).
const CbSignupBottomSlot = ({ tone = "accent", demoLabel, demoHref, demoOnClick }) => {
  const isClient = tone === "client";
  const checkColor = isClient ? "var(--cli-accent)" : "var(--accent)";
  const items = [
    "Sans carte bancaire",
    "Compte créé en 30 sec",
    "Données en France",
  ];
  const showDemo = !!(demoHref || demoOnClick);

  const demoBtnStyle = {
    display: "flex", alignItems: "center", justifyContent: "center", gap: 8,
    width: "100%",
    padding: "12px 16px",
    background: "var(--surface)",
    border: "1px solid var(--line-strong)",
    borderRadius: 12,
    fontSize: 14, fontWeight: 540,
    color: "var(--ink)", fontFamily: "inherit",
    textDecoration: "none",
    cursor: "pointer",
    transition: "background .15s ease, border-color .15s ease, transform .12s ease",
    boxSizing: "border-box",
  };

  return (
    <div style={{
      marginTop: 24,
      display: "flex", flexDirection: "column", gap: 18,
    }}>
      {showDemo && (
        demoHref ? (
          <a href={demoHref} style={demoBtnStyle}
            onMouseEnter={e => { e.currentTarget.style.background = "var(--bg-alt)"; e.currentTarget.style.borderColor = checkColor; }}
            onMouseLeave={e => { e.currentTarget.style.background = "var(--surface)"; e.currentTarget.style.borderColor = "var(--line-strong)"; }}>
            <CbDemoPulseDot color={checkColor}/>
            {demoLabel || "Voir le compte démo"}
          </a>
        ) : (
          <button onClick={demoOnClick} style={demoBtnStyle}
            onMouseEnter={e => { e.currentTarget.style.background = "var(--bg-alt)"; e.currentTarget.style.borderColor = checkColor; }}
            onMouseLeave={e => { e.currentTarget.style.background = "var(--surface)"; e.currentTarget.style.borderColor = "var(--line-strong)"; }}
            onMouseDown={e => { e.currentTarget.style.transform = "scale(0.98)"; }}
            onMouseUp={e => { e.currentTarget.style.transform = "scale(1)"; }}>
            <CbDemoPulseDot color={checkColor}/>
            {demoLabel || "Voir le compte démo"}
          </button>
        )
      )}
      <div style={{
        display: "flex", flexWrap: "wrap", justifyContent: "center",
        gap: "8px 14px",
        fontSize: 13, color: "var(--ink-2)", fontWeight: 500,
      }}>
        {items.map((t, i) => (
          <React.Fragment key={t}>
            <span style={{
              display: "inline-flex", alignItems: "center", gap: 6,
              whiteSpace: "nowrap",
            }}>
              <span aria-hidden style={{
                color: checkColor, fontWeight: 700, fontSize: 14, lineHeight: 1,
              }}>✓</span>
              {t}
            </span>
            {i < items.length - 1 && (
              <span aria-hidden style={{
                color: "var(--ink-4)", opacity: 0.5,
                alignSelf: "center",
              }}>·</span>
            )}
          </React.Fragment>
        ))}
      </div>
    </div>
  );
};
const CbDemoPulseDot = ({ color }) => (
  <span aria-hidden style={{
    width: 8, height: 8, borderRadius: 50,
    background: color,
    boxShadow: `0 0 0 0 ${color}`,
    animation: "cbDemoPulse 1.8s ease-in-out infinite",
    flexShrink: 0,
  }}>
    <style>{`
      @keyframes cbDemoPulse {
        0%, 100% { opacity: 1; transform: scale(1); }
        50%      { opacity: 0.6; transform: scale(1.2); }
      }
    `}</style>
  </span>
);
if (typeof window !== "undefined") {
  window.CbSignupBottomSlot = CbSignupBottomSlot;
}

// ============================================================================
// CbTrustBand — bandeau réassurance en marquee (style "Made in France")
// ============================================================================
// 4 piliers qui défilent en boucle continue (translateX 0 → -50%, items
// dupliqués pour seamless loop). Pause au survol. Bords fade pour adoucir
// l'entrée/sortie. Pas de fond, juste borders haut/bas pour cadrer.
// Chaque item peut avoir un "clin d'œil" visuel inline (jamais d'emoji) :
//   - <CbTitleFrance/> → mot "France" avec drapeau tricolore dans les lettres
//   - <CbIconLock/>    → cadenas SVG pour symboliser RGPD/chiffré
//   - <CbIconClock/>   → horloge SVG pour 24h
//   - <CbIconGift/>    → cadeau SVG pour offre/gratuit
//   - <CbIconNoAd/>    → œil barré pour "0 pub"
//   - <CbIconHub/>     → 1 nœud central + 3 satellites pour "1 compte"
//
// `keyByTone` change la couleur de fond derrière le AN blanc de France pour
// le rendre lisible sur n'importe quel bg (le AN est blanc avec ink-stroke).
// "France" écrit en couleur d'audience + petit drapeau français SVG à droite.
// La couleur du mot est héritée du parent (colorVar dans CbTrustBand) ; le
// drapeau a ses 3 couleurs fixes bleu/blanc/rouge + thin border pour rendre
// la bande blanche visible sur fond clair.
const CbTitleFrance = () => (
  <span style={{ display: "inline-flex", alignItems: "center", gap: 10, lineHeight: 1, letterSpacing: "-0.02em" }}>
    <span>France</span>
    <CbIconFlagFr/>
  </span>
);
// Drapeau France style emoji : coins arrondis, gradients verticaux subtils
// (top brighter, bottom slightly deeper) pour faux relief, thin dark border
// pour la définition, drop-shadow pour donner du poids.
const CbIconFlagFr = () => (
  <svg width="34" height="24" viewBox="0 0 34 24" aria-hidden
    style={{ flexShrink: 0, filter: "drop-shadow(0 1.5px 2px rgba(15,16,32,0.22))" }}>
    <defs>
      <linearGradient id="cb-flag-fr-blue" x1="0" y1="0" x2="0" y2="1">
        <stop offset="0%" stopColor="#1668B8"/>
        <stop offset="100%" stopColor="#003D85"/>
      </linearGradient>
      <linearGradient id="cb-flag-fr-white" x1="0" y1="0" x2="0" y2="1">
        <stop offset="0%" stopColor="#FFFFFF"/>
        <stop offset="100%" stopColor="#E8E8EC"/>
      </linearGradient>
      <linearGradient id="cb-flag-fr-red" x1="0" y1="0" x2="0" y2="1">
        <stop offset="0%" stopColor="#F75A4E"/>
        <stop offset="100%" stopColor="#C81A1A"/>
      </linearGradient>
      <linearGradient id="cb-flag-fr-gloss" x1="0" y1="0" x2="0" y2="1">
        <stop offset="0%" stopColor="rgba(255,255,255,0.32)"/>
        <stop offset="50%" stopColor="rgba(255,255,255,0)"/>
      </linearGradient>
      <clipPath id="cb-flag-fr-clip"><rect x="1" y="2" width="32" height="20" rx="3"/></clipPath>
    </defs>
    <g clipPath="url(#cb-flag-fr-clip)">
      <rect x="1"  y="2" width="11" height="20" fill="url(#cb-flag-fr-blue)"/>
      <rect x="12" y="2" width="10" height="20" fill="url(#cb-flag-fr-white)"/>
      <rect x="22" y="2" width="11" height="20" fill="url(#cb-flag-fr-red)"/>
      <rect x="1"  y="2" width="32" height="9"  fill="url(#cb-flag-fr-gloss)"/>
    </g>
    <rect x="1" y="2" width="32" height="20" rx="3" fill="none"
      stroke="rgba(15,16,32,0.32)" strokeWidth="0.7"/>
  </svg>
);

const CbTitleWithIcon = ({ icon, label, color }) => (
  // Wrapper générique : petit SVG + label, alignés baseline.
  <span style={{ display: "inline-flex", alignItems: "center", gap: 8, color, lineHeight: 1 }}>
    {icon}
    <span>{label}</span>
  </span>
);

// ── Mini-SVGs (24px) pour les "clins d'œil". Tous sont currentColor pour
// hériter de la couleur d'audience (indigo pro / violet client).
const CbIconLock = () => (
  <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor"
    strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
    <rect x="4" y="11" width="16" height="10" rx="2"/>
    <path d="M8 11V8a4 4 0 0 1 8 0v3"/>
    <circle cx="12" cy="16" r="1.2" fill="currentColor"/>
  </svg>
);
const CbIconClock = () => (
  <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor"
    strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
    <circle cx="12" cy="12" r="9"/>
    <path d="M12 7v5l3 2"/>
  </svg>
);
const CbIconGift = () => (
  <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor"
    strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
    <rect x="3" y="10" width="18" height="11" rx="1.5"/>
    <path d="M3 14h18M12 10v11"/>
    <path d="M12 10c-2.5 0-4-1.2-4-3a2 2 0 0 1 4 0c0 1.5 0 3 0 3zM12 10c2.5 0 4-1.2 4-3a2 2 0 0 0-4 0c0 1.5 0 3 0 3z"/>
  </svg>
);
const CbIconNoAd = () => (
  // Cercle barré façon "pas de pub"
  <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor"
    strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
    <circle cx="12" cy="12" r="9"/>
    <path d="M6 6l12 12"/>
    <path d="M9 12h6" opacity="0.4"/>
  </svg>
);
const CbIconHub = () => (
  // 1 nœud central + 3 satellites — illustre "un seul compte pour plusieurs pros"
  <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor"
    strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
    <circle cx="12" cy="12" r="2.5" fill="currentColor"/>
    <circle cx="5" cy="6" r="2"/>
    <circle cx="19" cy="6" r="2"/>
    <circle cx="12" cy="20" r="2"/>
    <path d="M12 12L5 6M12 12l7-6M12 12v8" opacity="0.6"/>
  </svg>
);
if (typeof window !== "undefined") {
  Object.assign(window, { CbTitleFrance, CbIconFlagFr, CbTitleWithIcon, CbIconLock, CbIconClock, CbIconGift, CbIconNoAd, CbIconHub });
}

const CbTrustBand = ({ tone = "accent", items, duration = 38 }) => {
  const isClient = tone === "client";
  const colorVar = isClient ? "var(--cli-accent)" : "var(--accent)";
  const safeItems = items && items.length >= 2 ? items : [];
  // Dupliqué une fois pour seamless loop (translate 0 → -50%).
  const looped = [...safeItems, ...safeItems];
  return (
    <div style={{
      marginTop: 48,
      padding: "20px 0",
      borderTop: "1px solid var(--line)",
      borderBottom: "1px solid var(--line)",
      position: "relative",
      overflow: "hidden",
    }}>
      <style>{`
        @keyframes cbTrustMarquee {
          0%   { transform: translateX(0); }
          100% { transform: translateX(-50%); }
        }
        .cb-trust-track {
          display: inline-flex;
          align-items: center;
          gap: 0;
          animation: cbTrustMarquee ${duration}s linear infinite;
          will-change: transform;
        }
        .cb-trust-track:hover {
          animation-play-state: paused;
        }
        .cb-trust-cell {
          display: inline-flex;
          flex-direction: column;
          align-items: center;
          gap: 5px;
          padding: 4px 44px;
          white-space: nowrap;
          text-align: center;
        }
        .cb-trust-sep {
          width: 1px;
          height: 40px;
          background: var(--line);
          flex-shrink: 0;
        }
        .cb-trust-fade {
          position: absolute;
          top: 0; bottom: 0;
          width: 90px;
          z-index: 2;
          pointer-events: none;
        }
        @media (max-width: 720px) {
          .cb-trust-cell { padding: 4px 28px; }
          .cb-trust-fade { width: 50px; }
        }
      `}</style>
      <div className="cb-trust-fade" style={{
        left: 0,
        background: "linear-gradient(to right, var(--bg), transparent)",
      }}/>
      <div className="cb-trust-fade" style={{
        right: 0,
        background: "linear-gradient(to left, var(--bg), transparent)",
      }}/>
      <div className="cb-trust-track">
        {looped.map((it, i) => (
          <React.Fragment key={i}>
            <div className="cb-trust-cell">
              <div style={{
                fontSize: 22,
                fontWeight: 700,
                color: colorVar,
                letterSpacing: "-0.02em",
                lineHeight: 1.1,
              }}>{it.title}</div>
              <div style={{
                fontSize: 13,
                color: "var(--ink-3)",
                lineHeight: 1.35,
              }}>{it.sub}</div>
            </div>
            <div className="cb-trust-sep"/>
          </React.Fragment>
        ))}
      </div>
    </div>
  );
};
if (typeof window !== "undefined") {
  window.CbTrustBand = CbTrustBand;
}


Object.assign(window, {
  Icon, Logo, Nav, Footer, useIsMobile, OpenAppButton,
  ChangelogPage, CB_VERSION,
  CB_DAY_ANCHOR, cbDayOffsetFrom, cbTodayOffset, cbDateForOffset, cbMondayOffset,
  CB_FR_DAYS_SHORT, CB_FR_MONTHS_SHORT, CB_FR_MONTHS_LONG, cbYmd,
  // Calendar export (cf. v2/src/shared.jsx, helpers ICS + deeplinks)
  cbBuildICS, cbDownloadICS, cbCalendarLinks, CalendarExportButtons,
});
