/* ClientBase, Public booking page (/?book=<slug>) */

// PICKER_DAYS = 6 jours visibles à la fois dans la grille (lundi..samedi).
// La grille glisse par semaines via weekOffset (0 = semaine courante, 1 = +7j…)
// jusqu'à la limite policyMaxAdvanceDays définie par le pro.
// Toutes les helpers prennent un OFFSET ABSOLU depuis aujourd'hui (j 0 = ajd).
const PICKER_DAYS = 6;
const _bookingDateForI = (i) => cbDateForOffset(cbTodayOffset() + i);
const dayLabelOf    = (i) => CB_FR_DAYS_SHORT[_bookingDateForI(i).getDay()];
const dayDateOf     = (i) => _bookingDateForI(i).getDate();
const dayMonthOf    = (i) => CB_FR_MONTHS_SHORT[_bookingDateForI(i).getMonth()];
const dayMonthLongOf = (i) => CB_FR_MONTHS_LONG[_bookingDateForI(i).getMonth()];
const isoIdxOf      = (i) => { const dow = _bookingDateForI(i).getDay(); return dow === 0 ? 7 : dow; };

const fmtHourBooking = (h) => {
  const hh = Math.floor(h);
  const mm = Math.round((h - hh) * 60);
  return `${hh}h${mm.toString().padStart(2, "0")}`;
};

// Resolve schedule for a given ISO weekday (1..7) with legacy fallback
const resolveDayHours = (bs, isoIdx) => {
  if (bs.schedule && bs.schedule[isoIdx]) {
    const d = bs.schedule[isoIdx];
    return {
      open: !!d.open,
      start: d.start, end: d.end,
      break: !!d.break,
      breakStart: d.breakStart != null ? d.breakStart : 12.5,
      breakEnd:   d.breakEnd   != null ? d.breakEnd   : 13.5,
    };
  }
  const daysOpen = bs.daysOpen || [1, 2, 3, 4, 5, 6];
  return {
    open: daysOpen.includes(isoIdx),
    start: (bs.hours && bs.hours.start) || 9,
    end: (bs.hours && bs.hours.end) || 18,
    break: false, breakStart: 12.5, breakEnd: 13.5,
  };
};

// Map 0..5 (Mon..Sat demo) to ISO 1..7 and to a demo date in Apr 2026
const demoDateFor = (i) => cbYmd(_bookingDateForI(i));

const isInVacation = (bs, ymd) => {
  const vacs = bs.vacations || [];
  return vacs.some(v => ymd >= v.start && ymd <= v.end);
};

/* Given a day index (0-5 = today + i) and current data, return ALL slot
   start hours of that day, each tagged { h, taken: boolean }. Taken slots
   are kept in the list so the UI can render them disabled (barré + grisé). */
const generateAvailableSlots = (day, data, serviceId) => {
  const bs = data.bookingSettings || {};
  const service = data.services.find(s => s.id === serviceId);
  if (!service) return [];
  const isoIdx = isoIdxOf(day);
  const dayHours = resolveDayHours(bs, isoIdx);
  if (!dayHours.open) return [];
  const ymd = demoDateFor(day);
  if (isInVacation(bs, ymd)) return [];
  const step = (bs.slotDuration || 30) / 60;
  const slots = [];

  const hasBreak = dayHours.break && dayHours.breakStart != null && dayHours.breakEnd != null
                   && dayHours.breakEnd > dayHours.breakStart;
  const targetOffset = cbTodayOffset() + day;

  // Délai (battement) en heures : prestation > règle globale > 0.
  const globalBuf = (((data.business && data.business.policyBufferMin) || 0)) / 60;
  const bufFor = (svc) => (svc && svc.bufferMin != null ? Math.max(0, svc.bufferMin) / 60 : globalBuf);
  const sBuf = bufFor(service);

  for (let h = dayHours.start; h + service.duration <= dayHours.end + 0.001; h += step) {
    const sStart = h;
    const sEnd = h + service.duration;            // pour les checks horaires/pause
    const sEndWithBuf = sEnd + sBuf;              // ce qui est réellement bloqué côté futur
    if (hasBreak && sStart < dayHours.breakEnd && sEnd > dayHours.breakStart) continue;
    const taken = data.appointments.some(a => {
      if (a.day !== targetOffset) return false;
      // Défense : si `a.d` (durée) manque, on suppose 1h (anti double-booking).
      const aDur = (Number(a.d) > 0) ? Number(a.d) : 1;
      const aStart = Number(a.h);
      if (!Number.isFinite(aStart)) return false;
      // Délai du RDV existant : prestation associée > global.
      const aSvc = data.services && data.services.find(x => x.id === a.serviceId);
      const aBuf = bufFor(aSvc);
      const aEnd = aStart + aDur + aBuf;
      return sStart < aEnd && sEndWithBuf > aStart;
    });
    slots.push({ h: +h.toFixed(2), taken });
  }
  return slots;
};

/* Quand le pro n'a (encore) rien customisé dans Ma page, on synthétise des
   valeurs par défaut intelligentes depuis ce qu'on a déjà sous la main
   (nom du business, ville, catégories de prestations). Les champs vraiment
   saisis par le pro l'emportent toujours. */
const derivedPageCustom = (raw, pro, services, serviceCategories) => {
  const pc = { ...(raw || {}) };
  const biz = pro || {};
  const cats = (serviceCategories || []).slice().sort((a, b) => (a.order || 0) - (b.order || 0));
  const svc = (services || []).filter(s => s.active !== false);
  const businessName = biz.businessName || biz.ownerName || "Mon activité";
  const firstName = (biz.ownerName || businessName || "").trim().split(/\s+/)[0];
  const city = (pc.locationLabel && String(pc.locationLabel).trim()) || biz.city || "";

  // Tagline : "<catégorie> · <ville>" ou variante
  if (!pc.tagline || !String(pc.tagline).trim()) {
    if (cats.length && city) pc.tagline = `${cats[0].name} · ${city}`;
    else if (cats.length)    pc.tagline = cats[0].name;
    else if (svc.length && city) pc.tagline = `${svc[0].name} · ${city}`;
    else if (city)               pc.tagline = `Sur rendez-vous · ${city}`;
    // sinon BookingHeader retombe sur "Prise de rendez-vous en ligne"
  }

  // Mini-bio : phrase d'accueil par défaut
  if (!pc.bio || !String(pc.bio).trim()) {
    pc.bio = `Bienvenue chez ${businessName}. ${firstName ? `${firstName} vous accueille` : "On vous accueille"} uniquement sur rendez-vous, réservez votre créneau en quelques clics.`;
  }

  // Spécialités : on prend les noms des catégories actives (max 4)
  if (!pc.tags || !String(pc.tags).trim()) {
    const tags = cats.map(c => c.name).filter(Boolean).slice(0, 4);
    if (tags.length) pc.tags = tags.join(", ");
  }

  // Lieu : retombe sur la ville du business si vide
  if ((!pc.locationLabel || !String(pc.locationLabel).trim()) && biz.city) {
    pc.locationLabel = biz.city;
  }

  return pc;
};

/* ============================================================
   GiftCardOffer — carte cadeau achetable en ligne par un visiteur.
   Paiement SIMULÉ (prêt à brancher Stripe Checkout) : à la validation,
   on génère un code de carte cadeau et on l'affiche. Aucune config pro
   nécessaire (montants génériques).
   ============================================================ */
const GiftCardOffer = ({ pro, blockBg }) => {
  const [amount, setAmount] = React.useState(50);
  const [custom, setCustom] = React.useState("");
  const [msg, setMsg] = React.useState("");
  const presets = [20, 30, 50, 80];
  const finalAmount = custom ? Math.max(0, parseInt(custom, 10) || 0) : amount;
  const valid = finalAmount >= 10;

  // Paiement via le hub central cbPay → ouvre l'écran unique « bientôt »
  // (cbComingSoon) tant que Stripe n'est pas branché (mi-juillet).
  const pay = () => {
    if (!valid) return;
    window.cbPay.checkout({ kind: "giftcard", amount: finalAmount, message: msg });
  };

  return (
    <section style={{
      marginTop: 10, padding: "22px 22px",
      background: blockBg.bg, border: `1px solid ${blockBg.border}`, borderRadius: 18,
    }}>
      <div style={{
        fontFamily: "var(--ff-mono)", fontSize: 10.5, textTransform: "uppercase",
        letterSpacing: "0.08em", color: "var(--ink-3)", fontWeight: 600, marginBottom: 4, textAlign: "center",
      }}>Idée cadeau</div>
      <h2 style={{
        margin: "0 0 16px", fontFamily: "var(--ff-display)", fontSize: 19, fontWeight: 600,
        letterSpacing: "-0.022em", color: "var(--ink)", textAlign: "center",
      }}>🎁 Offrir une carte cadeau</h2>

      <div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
          <div style={{ display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 8 }}>
            {presets.map(p => {
              const on = !custom && amount === p;
              return (
                <button key={p} onClick={() => { setCustom(""); setAmount(p); }} style={{
                  padding: "12px 6px", borderRadius: 11, cursor: "pointer",
                  fontFamily: "var(--ff-display)", fontSize: 17, fontWeight: 620,
                  background: on ? "var(--accent)" : "var(--surface)", color: on ? "#fff" : "var(--ink)",
                  border: on ? "1px solid var(--accent)" : "1px solid var(--line)",
                }}>{p} €</button>
              );
            })}
          </div>
          <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
            <span style={{ fontSize: 12.5, color: "var(--ink-3)" }}>ou montant libre :</span>
            <input type="number" min="10" value={custom} onChange={e => setCustom(e.target.value)}
              placeholder="ex : 100" style={{
                width: 100, padding: "8px 10px", background: "var(--surface)",
                border: "1px solid var(--line)", borderRadius: 9, fontSize: 13.5, fontFamily: "inherit", color: "var(--ink)",
              }}/>
            <span style={{ fontSize: 14, color: "var(--ink-3)", fontWeight: 600 }}>€</span>
          </div>
          <textarea value={msg} onChange={e => setMsg(e.target.value)} rows={2}
            placeholder="Petit mot pour la personne (optionnel)…" style={{
              width: "100%", padding: "10px 12px", background: "var(--surface)",
              border: "1px solid var(--line)", borderRadius: 9, fontSize: 13.5, fontFamily: "inherit",
              color: "var(--ink)", resize: "vertical",
            }}/>
          <button onClick={pay} disabled={!valid} style={{
            padding: "13px", background: valid ? "var(--accent)" : "var(--line-strong)", color: "#fff",
            border: "none", borderRadius: 11, fontSize: 14.5, fontWeight: 620,
            cursor: valid ? "pointer" : "not-allowed", fontFamily: "inherit",
          }}>
            Offrir {finalAmount || "—"} €
          </button>
          <div style={{ fontSize: 11, color: "var(--ink-4)", textAlign: "center" }}>
            Paiement par carte sécurisé · disponible mi-juillet · minimum 10 €
          </div>
        </div>
    </section>
  );
};

/* ============================================================
   ForfaitsOffer — lots de séances vendus en ligne (config par le pro
   dans « Ma page »). Achat SIMULÉ (prêt Stripe). N'affiche rien si le
   pro n'a pas créé de forfait.
   ============================================================ */
const ForfaitsOffer = ({ pc, blockBg }) => {
  const forfaits = (pc.forfaits || []).filter(f => (f.name || "").trim() && Number(f.price) > 0);
  if (forfaits.length === 0) return null;
  // Paiement via le hub central cbPay → écran unique « bientôt » (mi-juillet).
  const buy = (f) => window.cbPay.checkout({ kind: "forfait", amount: f.price, label: f.name });
  return (
    <section style={{
      marginTop: 10, padding: "22px 22px",
      background: blockBg.bg, border: `1px solid ${blockBg.border}`, borderRadius: 18,
    }}>
      <div style={{
        fontFamily: "var(--ff-mono)", fontSize: 10.5, textTransform: "uppercase",
        letterSpacing: "0.08em", color: "var(--ink-3)", fontWeight: 600, marginBottom: 4, textAlign: "center",
      }}>Économisez</div>
      <h2 style={{
        margin: "0 0 16px", fontFamily: "var(--ff-display)", fontSize: 19, fontWeight: 600,
        letterSpacing: "-0.022em", color: "var(--ink)", textAlign: "center",
      }}>Forfaits</h2>
      <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
        {forfaits.map(f => {
          const per = Math.round(Number(f.price) / Math.max(1, Number(f.sessions)));
          return (
            <div key={f.id} style={{
              display: "flex", alignItems: "center", gap: 12, padding: "14px 16px",
              background: "var(--surface)", border: "1px solid var(--line)", borderRadius: 12,
            }}>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontSize: 14.5, fontWeight: 620, color: "var(--ink)", letterSpacing: "-0.005em" }}>{f.name}</div>
                <div style={{ fontSize: 12.5, color: "var(--ink-3)", marginTop: 2 }}>
                  {f.sessions} séances · {per} €/séance
                </div>
              </div>
              <div style={{ fontFamily: "var(--ff-display)", fontSize: 19, fontWeight: 680, letterSpacing: "-0.02em", flexShrink: 0 }}>
                {f.price} €
              </div>
              <button onClick={() => buy(f)} style={{
                flexShrink: 0, padding: "9px 14px", borderRadius: 10, border: "none",
                background: "var(--accent)", color: "#fff",
                fontSize: 13, fontWeight: 600, cursor: "pointer", fontFamily: "inherit",
              }}>Acheter</button>
            </div>
          );
        })}
      </div>
      <div style={{ fontSize: 11, color: "var(--ink-4)", textAlign: "center", marginTop: 10 }}>
        Paiement par carte · disponible mi-juillet (version bêta).
      </div>
    </section>
  );
};

/* Mode preview : si `preview = { data, pro }` est fourni, la BookingPage saute
   l'appel Supabase et le hijack du viewport. Utilisée dans l'éditeur Ma page
   pour afficher la VRAIE page (et pas un mockup) dans le cadre téléphone. */
const BookingPage = ({ slug, preview }) => {
  const isPreview = !!preview;
  // Bloque le zoom auto iOS pendant qu'on est sur la page de réservation,
  // restauré au démontage pour ne pas affecter le reste du site.
  // En mode preview, on ne touche pas au viewport (l'éditeur reste actif).
  React.useEffect(() => {
    if (isPreview) return;
    const meta = document.querySelector('meta[name="viewport"]');
    const original = meta ? meta.getAttribute('content') : null;
    if (meta) {
      meta.setAttribute('content',
        'width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover');
    }
    return () => {
      if (meta && original) meta.setAttribute('content', original);
    };
  }, [isPreview]);

  // État de chargement : null=loading, {pro,data}=ok, false=not found
  // En mode preview, on initialise directement depuis les props.
  const [loaded, setLoaded] = React.useState(isPreview ? true : null);
  const [data, setData] = React.useState(isPreview ? preview.data : null);
  const [pro, setPro]   = React.useState(isPreview ? preview.pro  : null);

  // En mode preview, refléter en direct les changements props -> state.
  React.useEffect(() => {
    if (!isPreview) return;
    setData(preview.data);
    setPro(preview.pro);
  }, [isPreview, preview && preview.data, preview && preview.pro]);

  const [step, setStep] = React.useState(1);
  const [serviceId, setServiceId] = React.useState("");
  const [day, setDay] = React.useState(null);
  const [hour, setHour] = React.useState(null);

  // Scroll automatique en haut quand on change d'étape : sinon sur mobile
  // (et même desktop) l'utilisateur reste positionné au bas de la page
  // précédente après "Suivant" → il rate le titre et les CTA du haut.
  React.useEffect(() => {
    if (isPreview) return;
    try { window.scrollTo({ top: 0, behavior: "smooth" }); } catch { window.scrollTo(0, 0); }
  }, [step, isPreview]);
  const [form, setForm] = React.useState({
    firstName: "", lastName: "", email: "", phone: "", social: "", noSocial: false, notes: "", rgpd: false,
    password: "",  // utilisé seulement quand bookingMode === "account" ou "login"
  });
  // Mode choisi à la ChoiceStep : "account" (créer compte + signup),
  // "login" (déjà un compte, signin), "guest" (sans compte). Null tant que
  // l'utilisateur n'a pas choisi.
  const [bookingMode, setBookingMode] = React.useState(null);
  const [error, setError] = React.useState(null);
  const [submitting, setSubmitting] = React.useState(false);
  const [confirmed, setConfirmed] = React.useState(null);

  // Chargement depuis Supabase (RPC publique) ; fallback localStorage en bêta.
  // En mode preview on saute complètement le chargement.
  React.useEffect(() => {
    if (isPreview) return;
    let cancelled = false;
    (async () => {
      // 1) Cloud, avec timeout de 10s pour éviter de rester en "Chargement…"
      // infini si la RPC hang (réseau, Supabase down, etc.).
      if (window.cbSupabase) {
        try {
          const rpcCall = window.cbSupabase.rpc("get_public_booking", { p_slug: slug });
          const timeout = new Promise((_, reject) =>
            setTimeout(() => reject(new Error("RPC timeout (10s)")), 10000)
          );
          const { data: rpc, error } = await Promise.race([rpcCall, timeout]);
          if (cancelled) return;
          try {
            const pc = rpc && rpc.settings && (rpc.settings.page_custom || rpc.settings.pageCustom);
            window.cbDebug && window.cbDebug.log("[BookingPage] page_custom RPC:", pc ? `OK (${Object.keys(pc).length} champs)` : "ABSENT, voir migration 013 Supabase");
          } catch {}
          if (!error && rpc && rpc.business) {
            setPro({
              businessName: rpc.business.name,
              ownerName:    rpc.business.owner,
              bookingSlug:  rpc.business.booking_slug,
              phone:        rpc.business.phone,
              contactEmail: rpc.business.contact_email,
              city:         rpc.business.city,
              avatar:       rpc.business.avatar_data_url || "",
              hue:          rpc.business.hue || 30,
              emailVerified: !!rpc.email_verified,
              policyMaxAdvanceDays: rpc.business.policy_max_advance_days != null
                ? Number(rpc.business.policy_max_advance_days) : 60,
            });
            setData({
              services: (rpc.services || []).map(s => ({
                id: s.id, name: s.name,
                price: Number(s.price || 0),
                duration: Number(s.duration || 1),
                description: s.description || "",
                active: s.active !== false,
                categoryId: s.category_id || s.categoryId || null,
                order: s.order != null ? Number(s.order) : 0,
              })),
              serviceCategories: (rpc.service_categories || rpc.serviceCategories || []).map(c => ({
                id: c.id, name: c.name,
                order: c.order != null ? Number(c.order) : 0,
              })),
              appointments: (rpc.appointments || []).map(a => ({
                id: a.id, day: Number(a.day), h: Number(a.h), d: Number(a.d),
              })),
              bookingSettings: {
                ...(rpc.settings ? {
                  enabled: rpc.settings.enabled,
                  slotDuration: rpc.settings.slot_duration,
                  leadTimeMinutes: rpc.settings.lead_time_minutes,
                  schedule: rpc.settings.schedule || {},
                  preferredSocial: rpc.settings.preferred_social || "instagram",
                  pageCustom: rpc.settings.page_custom || rpc.settings.pageCustom || null,
                } : SEED_DATA.bookingSettings),
                vacations: (rpc.vacations || []).map(v => ({
                  id: v.id, start: v.starts_on, end: v.ends_on, reason: v.reason,
                })),
              },
              reviews: (rpc.reviews || []).map(r => ({
                id: r.id, clientName: r.client_name || r.clientName,
                rating: Number(r.rating || 5),
                comment: r.comment || "",
                serviceName: r.service_name || r.serviceName || "",
                date: r.date || r.created_at || "",
                verified: r.verified !== false,
                reply: r.reply || null,
                visible: r.visible !== false,
              })).filter(r => r.visible),
              // Acomptes : on garde l'objet entier tel que renvoyé par la RPC
              // (snake_case) pour ne pas avoir à mapper chaque champ dans le
              // composant qui le consomme. Sera null si le pro n'a pas activé.
              acompteSettings: rpc.acompte_settings || null,
            });
            setLoaded(true);
            return;
          }
        } catch (e) { console.error("[BookingPage] RPC", e); }
      }
      // 2) Fallback localStorage
      try {
        const localPro = cbAuth.findUserBySlug(slug);
        if (localPro) {
          setPro(localPro);
          try {
            const saved = localStorage.getItem(CB_LS_KEY);
            setData(saved ? { ...SEED_DATA, ...JSON.parse(saved) } : SEED_DATA);
          } catch { setData(SEED_DATA); }
          setLoaded(true);
        } else {
          setLoaded(false);
        }
      } catch (e) {
        // Filet de sécurité ultime : si TOUT échoue, on évite au moins de
        // rester en "Chargement…" infini.
        console.error("[BookingPage] fallback failed", e);
        if (!cancelled) setLoaded(false);
      }
    })().catch(e => {
      console.error("[BookingPage] async fetch crashed", e);
      if (!cancelled) setLoaded(false);
    });
    return () => { cancelled = true; };
  }, [slug, isPreview]);

  // ⚠️ Tous les hooks DOIVENT être déclarés ICI, AVANT les early returns
  // ci-dessous (sinon "Rendered more hooks than during the previous render"
  // quand loaded passe de null à true).
  //
  // Mode "vitrine" (page d'accueil avec présentation, galerie, avis, CTA)
  // ou "booking" (formulaire de prise de RDV en 3 étapes). Par défaut
  // vitrine, on bascule en booking via le CTA ou au step 4 (confirmation).
  const [mode, setMode] = React.useState("vitrine");
  // Sécurité : si confirmé (step 4), on reste forcément en mode booking
  React.useEffect(() => { if (step === 4) setMode("booking"); }, [step]);

  if (loaded === null) {
    return (
      <div style={{
        minHeight: "100vh", display: "flex", alignItems: "center", justifyContent: "center",
        background: "var(--bg)", color: "var(--ink-3)", fontSize: 13.5,
      }}>
        Chargement…
      </div>
    );
  }
  if (loaded === false) return <BookingNotFound slug={slug}/>;

  const bs = (data && data.bookingSettings) || SEED_DATA.bookingSettings;
  const activeServices = (data && data.services ? data.services : []).filter(s => {
    if (s.active === false) return false; // per-service toggle (nouvelle règle)
    if (bs.activeServiceIds && !bs.activeServiceIds.includes(s.id)) return false; // ancien filtre opt-in
    return true;
  });
  const service = activeServices.find(s => s.id === serviceId);

  // Validation form selon le bookingMode. Login : juste email + password.
  // Account : tous les champs + password + RGPD. Guest : tous sauf password.
  const validateForm = () => {
    if (bookingMode === "login") {
      if (!/^[^\s@]+@[^\s@]+\.[a-zA-Z]{2,}$/.test(form.email.trim())) return "Email invalide.";
      if (!form.password || form.password.length < 6) return "Mot de passe trop court.";
      return null;
    }
    if (!form.firstName.trim() || !form.lastName.trim()) return "Votre prénom et nom sont requis.";
    if (!/^[^\s@]+@[^\s@]+\.[a-zA-Z]{2,}$/.test(form.email.trim())) return "Email invalide. Exemple : prenom@exemple.fr";
    if (!form.rgpd) return "Vous devez accepter le traitement de vos données.";
    if (bookingMode === "account" && (!form.password || form.password.length < 8)) {
      return "Mot de passe : 8 caractères minimum pour créer votre compte.";
    }
    return null;
  };

  // requestSubmit = validation côté StepInfo + délègue à submit qui gère
  // le flow auth selon bookingMode. (Avant le flow choice → info, on avait
  // un intermédiaire ChoiceStep ; maintenant choice vient avant info donc
  // on enchaîne direct.)
  const requestSubmit = () => {
    setError(null);
    if (isPreview) {
      setConfirmed({ clientId: "preview", appointmentId: "preview", day, hour, service });
      setStep(4);
      return;
    }
    const err = validateForm();
    if (err) { setError(err); return; }
    // Pas de step intermédiaire : on appelle direct submit() qui gère
    // signUp/signIn AVANT le RPC selon bookingMode.
    submit();
  };

  const submit = async () => {
    setError(null);
    if (isPreview) {
      setConfirmed({ clientId: "preview", appointmentId: "preview", day, hour, service });
      setStep(4);
      return;
    }
    const err = validateForm();
    if (err) { setError(err); return; }

    setSubmitting(true);
    // 1) Auth d'abord (signUp account / signIn login), pour que le RPC
    //    public_book_appointment lie auth.uid() au RDV automatiquement.
    if (window.cbSupabase && (bookingMode === "account" || bookingMode === "login")) {
      try {
        if (bookingMode === "account") {
          const { data: signUpData, error: authErr } = await window.cbSupabase.auth.signUp({
            email: form.email.trim(),
            password: form.password,
            options: {
              // role: "client" → IMPORTANT : sinon EspaceLogin rejette ce
              // compte comme "compte professionnel" au prochain login.
              data: {
                role: "client",
                first_name: form.firstName,
                last_name: form.lastName,
                full_name: `${form.firstName} ${form.lastName}`.trim(),
                phone: form.phone,
              },
              emailRedirectTo: `${window.location.origin}/v2/espace`,
            },
          });
          if (authErr) {
            const msg = (authErr.message || "").toLowerCase();
            if (msg.includes("already registered") || msg.includes("already in use")) {
              // L'email existe → on signIn pour pouvoir checker le rôle
              const { data: sIn, error: signInErr } = await window.cbSupabase.auth.signInWithPassword({
                email: form.email.trim(), password: form.password,
              });
              if (signInErr) {
                setError("Un compte existe déjà à cette adresse. Mot de passe incorrect — utilisez « J'ai déjà un compte ».");
                setSubmitting(false); return;
              }
              // L'utilisateur est connecté : vérifie que c'est bien un client
              const existRole = sIn && sIn.user && sIn.user.user_metadata && sIn.user.user_metadata.role;
              if (existRole === "pro") {
                try { await window.cbSupabase.auth.signOut(); } catch {}
                setError("Cet email est déjà utilisé pour un compte professionnel ClientBase. Utilisez une autre adresse email pour créer votre compte client.");
                setSubmitting(false); return;
              }
              // Pas de role explicite → check DB businesses pour être sûr
              if (existRole !== "client") {
                try {
                  const { count } = await window.cbSupabase
                    .from("businesses")
                    .select("user_id", { count: "exact", head: true })
                    .eq("user_id", sIn.user.id);
                  if ((count || 0) > 0) {
                    try { await window.cbSupabase.auth.signOut(); } catch {}
                    setError("Cet email est déjà utilisé pour un compte professionnel ClientBase. Utilisez une autre adresse email pour créer votre compte client.");
                    setSubmitting(false); return;
                  }
                } catch {}
              }
            } else {
              setError(authErr.message || "Erreur lors de la création du compte.");
              setSubmitting(false); return;
            }
          } else {
            // Marque l'audience client TOUT DE SUITE pour que le routing
            // root (auto-redirect /app si connecté) sache que ce user est
            // un client → l'envoie vers /espace au lieu du dashboard pro.
            // Crucial pour les comptes créés via booking : aucun signIn
            // manuel n'a lieu, donc EspaceLogin ne pose pas ce flag.
            try { localStorage.setItem("cb_last_audience", "client"); } catch {}
            // signUp OK (pas d'erreur). DEUX cas à distinguer :
            //  A. Nouveau compte créé → role:"client" posé via metadata.
            //  B. Email existait déjà comme pro + confirmations Supabase ON
            //     → l'API renvoie silencieusement l'utilisateur existant
            //     SANS mettre à jour son metadata (anti-enumeration). On
            //     se retrouve avec un compte qui semble OK mais qui est
            //     en réalité un pro. Faut détecter ça avant de laisser
            //     l'utilisateur croire qu'il a un compte client.
            const newUser = signUpData && signUpData.user;
            const newRole = newUser && newUser.user_metadata && newUser.user_metadata.role;
            window.cbDebug && window.cbDebug.log("[booking signup] role:", newRole, "user.id:", newUser && newUser.id);
            // Si role === "pro" explicite → rejet immédiat
            if (newRole === "pro") {
              try { await window.cbSupabase.auth.signOut(); } catch {}
              setError("Cet email est déjà utilisé pour un compte professionnel ClientBase. Utilisez une autre adresse email pour créer votre compte client.");
              setSubmitting(false); return;
            }
            // Si role manquant ET user.id présent → DB check businesses
            // (cas legacy : compte sans role mais avec un business attaché).
            if (newRole !== "client" && newUser && newUser.id) {
              try {
                const { count } = await window.cbSupabase
                  .from("businesses")
                  .select("user_id", { count: "exact", head: true })
                  .eq("user_id", newUser.id);
                if ((count || 0) > 0) {
                  try { await window.cbSupabase.auth.signOut(); } catch {}
                  setError("Cet email est déjà utilisé pour un compte professionnel ClientBase. Utilisez une autre adresse email pour créer votre compte client.");
                  setSubmitting(false); return;
                }
              } catch (e) { window.cbDebug && window.cbDebug.warn("[booking signup] DB check pro échec:", e); }
            }
            // SIGN-IN IMMÉDIAT après signUp : grâce au trigger DB
            // auto_confirm_client_email (migration 024), l'email est
            // déjà confirmé pour les comptes role="client" → on peut
            // signInWithPassword tout de suite et obtenir une session
            // active. Si migration 024 pas appliquée, signIn échoue
            // (email pas confirmé) → on continue quand même.
            if (!signUpData.session) {
              try {
                const { error: sIErr } = await window.cbSupabase.auth.signInWithPassword({
                  email: form.email.trim(),
                  password: form.password,
                });
                if (sIErr) {
                  window.cbDebug && window.cbDebug.warn("[booking signup] signIn immédiat échec:", sIErr.message);
                } else {
                  window.cbDebug && window.cbDebug.log("[booking signup] session active après signIn auto");
                }
              } catch (e) { window.cbDebug && window.cbDebug.warn("[booking signup] signIn auto exception:", e); }
            }
            // POSE cb_client_v1 dans tous les cas avec les infos du form.
            // Comme ça, l'espace client marche même si la session
            // Supabase n'est pas (encore) active (migration 024 manquante,
            // confirmations email toujours requises, etc.). Le user voit
            // au moins son nom + email dans l'espace, et peut compléter
            // ensuite quand il aura confirmé son email.
            try {
              localStorage.setItem("cb_client_v1", JSON.stringify({
                id: (signUpData.user && signUpData.user.id) || "me",
                email: form.email.trim(),
                firstName: form.firstName,
                lastName: form.lastName,
                phone: form.phone,
                loginAt: Date.now(),
                createdAt: new Date().toISOString().slice(0, 10),
              }));
              window.cbDebug && window.cbDebug.log("[booking signup] cb_client_v1 posé pour", form.email);
            } catch (e) { window.cbDebug && window.cbDebug.warn("[booking signup] localStorage échec:", e); }
          }
        } else {
          // login
          const { error: signInErr } = await window.cbSupabase.auth.signInWithPassword({
            email: form.email.trim(), password: form.password,
          });
          if (signInErr) {
            setError("Identifiants invalides. Vérifiez votre email et mot de passe.");
            setSubmitting(false); return;
          }
          // En mode login on récupère nom/téléphone depuis client_accounts
          // pour pré-remplir le RDV (le RPC en a besoin).
          try {
            const { data: { user } } = await window.cbSupabase.auth.getUser();
            if (user && (!form.firstName || !form.lastName)) {
              const { data: acc } = await window.cbSupabase
                .from("client_accounts").select("full_name, phone").eq("user_id", user.id).maybeSingle();
              if (acc && acc.full_name) {
                const parts = acc.full_name.trim().split(/\s+/);
                form.firstName = parts.shift() || "Client";
                form.lastName = parts.join(" ") || ".";
              }
              if (acc && acc.phone && !form.phone) form.phone = acc.phone;
            }
          } catch {}
        }
      } catch (e) {
        console.error("[booking auth]", e);
        setError("Erreur de connexion. Réessayez.");
        setSubmitting(false); return;
      }
    }
    // En mode login, les champs requis du RPC peuvent être vides → fallback
    if (bookingMode === "login") {
      if (!form.firstName || !form.firstName.trim()) form.firstName = "Client";
      if (!form.lastName || !form.lastName.trim()) form.lastName = "ClientBase";
    }

    try {
      // Cloud path : RPC publique. Si un client est authentifié (créé compte
      // ou login via la ChoiceStep), la RPC lit auth.uid() et lie le RDV au
      // compte. Sinon (invité) : RDV pris sans lien compte.
      if (window.cbSupabase) {
        const { data: rpc, error } = await window.cbSupabase.rpc("public_book_appointment", {
          p_slug: slug,
          p_service_id: serviceId,
          p_day: cbTodayOffset() + day,  // offset absolu, stable
          p_h: hour,
          p_first: form.firstName,
          p_last: form.lastName,
          p_email: form.email.trim(),
          p_phone: form.phone.trim(),
          p_notes: form.notes.trim(),
          p_social: form.noSocial ? "" : form.social.trim(),
        });
        if (error) {
          setError(_prettifyBookingError(error.message));
          setSubmitting(false);
          return;
        }
        // Le créneau vient d'être pris : on l'injecte dans le state local
        // pour que d'éventuels nouveaux RDV pris dans la même session
        // (pour soi ou un proche) ne puissent pas réutiliser ce slot.
        setData(d => ({
          ...d,
          appointments: [
            ...(d.appointments || []),
            { id: rpc.appointment_id, day: cbTodayOffset() + day, h: hour, d: service.duration },
          ],
        }));
        setConfirmed({
          clientId: rpc.client_id, appointmentId: rpc.appointment_id,
          day, hour, service,
        });
        setStep(4);
        setSubmitting(false);
        return;
      }

      // Fallback localStorage (bêta locale)
      const { nextData, clientId, appointmentId } = commitBooking(data, {
        serviceId, day, h: hour,
        firstName: form.firstName, lastName: form.lastName,
        email: form.email, phone: form.phone, notes: form.notes,
      });
      try { localStorage.setItem(CB_LS_KEY, JSON.stringify(nextData)); }
      catch (e) { /* ignore */ }
      setData(nextData);
      setConfirmed({ clientId, appointmentId, day, hour, service });
      setStep(4);
    } finally {
      setSubmitting(false);
    }
  };

  // On enrichit avec des valeurs par défaut intelligentes pour que la page
  // ne soit jamais "vide" si le pro n'a rien customisé.
  const pageCustom = derivedPageCustom(
    (bs && bs.pageCustom) || null,
    pro,
    activeServices,
    (data && data.serviceCategories) || []
  );
  const themeId     = pageCustom.theme       || "indigo";
  const fontId      = pageCustom.font        || "modern";
  const buttonColor = pageCustom.buttonColor || "auto";
  const background  = pageCustom.background  || "auto";

  // Helpers de navigation entre vues vitrine/booking. Pas de hooks ici
  // (les hooks pour mode/effet sont déclarés tout en haut, avant les
  // early returns, cf. règle React).
  const goBooking = () => { setMode("booking"); setTimeout(() => window.scrollTo({ top: 0, behavior: "smooth" }), 50); };
  const goVitrine = () => { setMode("vitrine"); setStep(1); setServiceId(""); setDay(null); setHour(null); };

  return (
    <div data-cb-theme={themeId} data-cb-font={fontId} style={{
      minHeight: "100vh",
      background: "var(--bg)",
      display: "flex", flexDirection: "column",
    }}>
      <BookingThemeStyles theme={themeId} font={fontId} buttonColor={buttonColor} background={background}/>
      <div className="cb-booking" style={{ display: "contents" }}>
      {/* BookingHeader (cover + avatar style 'simple') affiché uniquement en
          mode booking (flow 3 étapes). En mode vitrine, VitrineProfile a son
          propre cover + avatar (style social/Treatwell mix). */}
      {mode !== "vitrine" && <BookingHeader pro={pro} pageCustom={pageCustom}/>}

      {mode === "vitrine" ? (
        <VitrineProfile
          pro={pro}
          data={data}
          pageCustom={pageCustom}
          activeServices={activeServices}
          onBook={goBooking}
          isPreview={isPreview}
        />
      ) : (
        <>
          {/* Mini bouton retour à la vitrine, sauf en confirmation */}
          {step !== 4 && (
            <div style={{ maxWidth: 640, margin: "16px auto 0", padding: "0 16px" }}>
              <button onClick={goVitrine}
                style={{
                  background: "transparent", border: "none", padding: "6px 10px 6px 0",
                  fontFamily: "inherit", fontSize: 13, color: "var(--ink-3)",
                  cursor: "pointer", display: "inline-flex", alignItems: "center", gap: 4,
                }}>
                ← Retour à la présentation
              </button>
            </div>
          )}

          <main style={{ flex: 1, padding: "16px 16px 72px" }}>
            <div style={{ maxWidth: 640, margin: "0 auto" }}>
              {step !== 4 && <BookingStepper step={step}/>}

              {step === 1 && (
                <>
                  <StepService
                    pro={pro}
                    services={activeServices}
                    categories={(data && data.serviceCategories) || []}
                    serviceId={serviceId}
                    setServiceId={setServiceId}
                    onNext={() => setStep(2)}
                  />
                  <div style={{ marginTop: 28 }}>
                    <CancellationNotice pro={pro} data={data}/>
                  </div>
                </>
              )}

              {step === 2 && service && (
                <StepSlot
                  service={service}
                  data={data}
                  bookingSettings={bs}
                  maxAdvanceDays={pro && pro.policyMaxAdvanceDays != null ? pro.policyMaxAdvanceDays : 60}
                  day={day} setDay={setDay}
                  hour={hour} setHour={setHour}
                  onBack={() => setStep(1)}
                  onNext={() => setStep("choice")}
                />
              )}

              {/* Étape "choice" : APRÈS le créneau, AVANT le formulaire.
                  Compare avec compte / invité, le choix décide ensuite si
                  StepInfo affiche le champ mot de passe (mode "account"). */}
              {step === "choice" && service && day != null && hour != null && (
                <ChoiceStep
                  pro={pro}
                  service={service} day={day} hour={hour}
                  onBack={() => setStep(2)}
                  onChoose={(mode) => { setBookingMode(mode); setStep(3); }}
                  onLoginRequest={() => { setBookingMode("login"); setStep(3); }}
                />
              )}

              {step === 3 && service && day != null && hour != null && (
                <>
                  <StepInfo
                    pro={pro}
                    service={service} day={day} hour={hour}
                    preferredSocial={(bs && bs.preferredSocial) || "instagram"}
                    form={form} setForm={setForm}
                    error={error}
                    submitting={submitting}
                    bookingMode={bookingMode}
                    onBack={() => setStep("choice")}
                    onSubmit={requestSubmit}
                    acompteSettings={data && data.acompteSettings}
                  />
                </>
              )}

              {step === 4 && confirmed && (
                <>
                  <StepConfirmed
                    pro={pro}
                    confirmed={confirmed}
                    bookingMode={bookingMode}
                    form={form}
                    email={form.email}
                    acompteSettings={data && data.acompteSettings}
                    onRestart={() => {
                      setConfirmed(null);
                      setServiceId("");
                      setDay(null);
                      setHour(null);
                      setForm({ firstName: "", lastName: "", email: "", phone: "", notes: "", rgpd: false, password: "" });
                      setBookingMode(null);
                      setStep(1);
                      setMode("vitrine");
                    }}
                  />
                  <div style={{ marginTop: 24 }}>
                    <CancellationNotice pro={pro} data={data}/>
                  </div>
                </>
              )}
            </div>
          </main>
        </>
      )}

      <BookingFooter pro={pro}/>
      </div>
    </div>
  );
};

/* === Vue "vitrine" : page d'accueil de la page publique avec présentation,
   galerie, avis + un grand CTA pour basculer en mode booking.
   Pensée mobile-first (la majorité des visiteurs vient depuis un lien
   Instagram/WhatsApp sur leur téléphone). === */
/* ============================================================
   VitrineProfile — nouvelle vitrine pro style social/Treatwell mix
   ============================================================
   Layout :
   - Cover (banner pageCustom OU gradient pastel basé sur pro.hue)
   - Avatar rond (photo pro.avatar OU initiales gradient)
   - Identité compacte : nom + tagline + meta (note, ville, délai)
   - Boutons sociaux colorés (Insta gradient, TikTok noir, FB, Snap, WhatsApp)
   - CTA principal dark "Prendre RDV"
   - Bio (si présente)
   - Prestations en grille 2 cols (clic → onBook)
   - Avis horizontaux scrollables (si avis)
   - Infos pratiques (adresse, horaires, contact)
   - Trust footer
   Inspiré du prototype /pro-preview.html validé par le client. */
const VitrineProfile = ({ pro, data, pageCustom, activeServices, onBook, isPreview }) => {
  const pc = pageCustom || {};
  const reviews = (data && data.reviews) || [];
  const hasReviews = reviews.length > 0;
  const avgRating = hasReviews
    ? (reviews.reduce((s, r) => s + (r.rating || 0), 0) / reviews.length).toFixed(1)
    : null;
  const categories = (data && data.serviceCategories) || [];
  const catNameOf = (id) => {
    if (!id) return null;
    const c = categories.find(c => c.id === id);
    return c ? c.name : null;
  };

  // Initiales pour fallback avatar
  const initials = (pro.ownerName || pro.businessName || "CB")
    .split(" ").map(x => x && x[0]).filter(Boolean).slice(0, 2).join("").toUpperCase();
  const hue = pro.hue || 30;
  const hasBanner = !!pc.banner;
  const hasAvatar = !!pro.avatar;

  // Statut du jour pour la meta line
  const todayLabel = (() => {
    const bs = (data && data.bookingSettings) || {};
    const today = new Date();
    const ymd = today.toISOString().slice(0, 10);
    const onVacation = (bs.vacations || []).some(v =>
      v && v.start && v.end && ymd >= v.start && ymd <= v.end
    );
    if (onVacation) return "En vacances";
    const dow = today.getDay() === 0 ? 7 : today.getDay();
    const day = (bs.schedule && bs.schedule[dow]) || null;
    if (!day || !day.open) return "Fermé aujourd'hui";
    const fmt = (h) => {
      const hh = Math.floor(h);
      const mm = Math.round((h - hh) * 60);
      return mm === 0 ? `${hh}h` : `${hh}h${String(mm).padStart(2, "0")}`;
    };
    return `Ouvert ${fmt(day.start || 9)}–${fmt(day.end || 18)}`;
  })();

  // Helpers prix / durée
  const fmtPrice = (p, from) => {
    if (p == null || p === 0) return "Sur devis";
    const v = Number(p).toLocaleString("fr-FR", { minimumFractionDigits: 0, maximumFractionDigits: 0 });
    return from ? `dès ${v} €` : `${v} €`;
  };
  const fmtDur = (d) => {
    const h = Math.floor(d);
    const m = Math.round((d - h) * 60);
    if (h === 0) return `${m} min`;
    if (m === 0) return `${h}h`;
    return `${h}h${String(m).padStart(2, "0")}`;
  };

  // Liens sociaux : on accepte aussi tiktok/facebook/snapchat même si
  // pas encore dans pageCustom (sera ajouté côté éditeur Ma page).
  const socials = [
    { id: "insta",    url: pc.socialInstagram ? `https://instagram.com/${pc.socialInstagram.replace(/^@/, "")}` : null,
      bg: "linear-gradient(45deg,#f09433,#e6683c 25%,#dc2743 50%,#cc2366 75%,#bc1888)", color: "#fff",
      svg: <path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.741 24 12 24c3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 100 12.324 6.162 6.162 0 000-12.324zM12 16a4 4 0 110-8 4 4 0 010 8zm6.406-11.845a1.44 1.44 0 100 2.881 1.44 1.44 0 000-2.881z"/> },
    { id: "tiktok",   url: pc.socialTiktok ? `https://tiktok.com/@${pc.socialTiktok.replace(/^@/, "")}` : null,
      bg: "#000", color: "#fff",
      svg: <path d="M19.59 6.69a4.83 4.83 0 01-3.77-4.25V2h-3.45v13.67a2.89 2.89 0 01-5.2 1.74 2.89 2.89 0 012.31-4.64 2.93 2.93 0 01.88.13V9.4a6.84 6.84 0 00-1-.05A6.33 6.33 0 005.8 20.1a6.34 6.34 0 0010.86-4.43v-7a8.16 8.16 0 004.77 1.52v-3.4a4.85 4.85 0 01-1.84-.1z"/> },
    { id: "facebook", url: pc.socialFacebook ? (pc.socialFacebook.startsWith("http") ? pc.socialFacebook : `https://facebook.com/${pc.socialFacebook}`) : null,
      bg: "#1877F2", color: "#fff",
      svg: <path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/> },
    { id: "snap",     url: pc.socialSnapchat ? `https://snapchat.com/add/${pc.socialSnapchat.replace(/^@/, "")}` : null,
      bg: "#FFFC00", color: "#000",
      svg: <path d="M12.166.493c.18 0 .362.005.546.014.595.043 1.286.142 1.978.378 1.123.376 2.082 1.022 2.823 1.85a6.7 6.7 0 011.382 2.426c.205.659.303 1.34.301 2.04.001.65-.014 1.292-.028 1.937-.005.226-.01.453-.013.68-.001.107.044.165.139.214.063.032.131.054.198.077.142.05.288.094.428.156.34.151.591.39.74.728.224.508.064 1.073-.394 1.421-.149.113-.328.207-.518.272-.232.08-.474.137-.715.196-.087.022-.175.044-.262.066-.158.041-.288.097-.408.176-.118.077-.193.193-.231.327-.04.143-.039.288-.005.43.038.165.124.31.231.444a3.99 3.99 0 001.075.918c.422.252.886.42 1.362.55.286.077.585.124.872.197.243.061.466.16.642.346.176.187.245.405.198.659-.062.336-.288.564-.575.74-.272.166-.572.272-.875.366-.367.114-.74.205-1.116.293-.205.048-.296.124-.355.337-.026.094-.045.19-.067.286-.071.314-.243.494-.564.535-.139.018-.281.011-.422 0-.262-.022-.522-.06-.785-.063-.16-.002-.323.005-.481.026-.319.043-.605.176-.864.367-.518.382-1.063.722-1.65.991-.71.327-1.45.502-2.232.508a5.93 5.93 0 01-.18.003c-.78-.006-1.522-.18-2.232-.508-.587-.27-1.131-.61-1.65-.991a2.31 2.31 0 00-.864-.367 2.95 2.95 0 00-.481-.026c-.263.003-.523.04-.785.063-.14.012-.283.019-.422 0-.32-.04-.493-.221-.564-.534-.022-.096-.04-.193-.067-.287-.058-.213-.15-.289-.355-.337-.376-.088-.749-.18-1.116-.293-.303-.094-.603-.2-.875-.366-.287-.176-.513-.404-.575-.74-.047-.255.022-.473.198-.66.176-.186.4-.284.642-.345.287-.073.586-.12.872-.197a5.39 5.39 0 001.362-.55c.456-.272.808-.59 1.075-.918.107-.134.193-.279.231-.444.034-.143.036-.288-.005-.43-.038-.135-.113-.25-.231-.328a1.34 1.34 0 00-.408-.175c-.087-.022-.175-.044-.262-.066-.241-.06-.483-.117-.715-.197-.19-.064-.369-.158-.518-.272-.458-.348-.618-.913-.394-1.42.149-.34.4-.578.74-.73.14-.06.286-.105.428-.155.067-.024.135-.045.198-.077.094-.05.14-.107.139-.214a17.99 17.99 0 00-.013-.68c-.014-.645-.029-1.287-.028-1.937a6.94 6.94 0 01.302-2.04 6.7 6.7 0 011.381-2.426A6.18 6.18 0 019.642.886C10.334.65 11.025.55 11.621.508c.18-.01.362-.014.545-.014z"/> },
    { id: "whatsapp", url: (pc.socialWhatsapp || pro.phone) ? `https://wa.me/${(pc.socialWhatsapp || pro.phone || "").replace(/\D/g, "")}` : null,
      bg: "#25D366", color: "#fff",
      svg: <path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413z"/> },
  ].filter(s => s.url);

  // Top 4 prestations à afficher dans la grille (preview), reste = "Voir tout"
  const previewServices = activeServices.slice(0, 6);

  // JSON-LD LocalBusiness — injecté dans <head> via useEffect.
  // Permet à Google de comprendre la page pro comme un commerce local :
  // - Le pro apparaît dans Google Maps + résultats locaux
  // - Étoiles + adresse + horaires dans les SERPs
  // - Référencement /lyon/ongles-by-lea boosté
  React.useEffect(() => {
    if (isPreview) return; // pas dans l'éditeur Ma page
    const slug = pro.bookingSlug || "pro";
    const city = (typeof window !== "undefined" && window.cbProCity)
      || (pro.city ? pro.city.toLowerCase().replace(/[^a-z]/g, "-") : null);
    const canonicalUrl = city
      ? `https://clientbase.fr/${city}/${slug}`
      : `https://clientbase.fr/${slug}`;

    const jsonLd = {
      "@context": "https://schema.org",
      "@type": "LocalBusiness",
      "name": pro.businessName || pro.ownerName || "ClientBase Pro",
      "description": pc.bio || pc.tagline || `Prestataire indépendant sur ClientBase`,
      "url": canonicalUrl,
      "inLanguage": "fr-FR",
      "image": pro.avatar || pc.banner || "https://clientbase.fr/assets/logo-1024.png",
      ...(pro.phone && { "telephone": pro.phone }),
      ...(pro.contactEmail && { "email": pro.contactEmail }),
      ...(pro.city && {
        "address": {
          "@type": "PostalAddress",
          "addressLocality": pro.city,
          ...(pro.postalCode && { "postalCode": pro.postalCode }),
          ...(pro.address && { "streetAddress": pro.address }),
          "addressCountry": "FR",
        },
      }),
      ...(hasReviews && {
        "aggregateRating": {
          "@type": "AggregateRating",
          "ratingValue": avgRating,
          "reviewCount": reviews.length,
          "bestRating": "5",
        },
        "review": reviews.slice(0, 10).map(r => ({
          "@type": "Review",
          "reviewRating": {
            "@type": "Rating",
            "ratingValue": r.rating || 5,
            "bestRating": "5",
          },
          "author": { "@type": "Person", "name": r.clientName || "Cliente vérifiée" },
          "reviewBody": r.comment || "",
          ...(r.date && { "datePublished": r.date }),
        })),
      }),
    };
    // Inject ou met à jour le <script type="application/ld+json" id="cb-localbusiness">
    let el = document.getElementById("cb-localbusiness");
    if (!el) {
      el = document.createElement("script");
      el.type = "application/ld+json";
      el.id = "cb-localbusiness";
      document.head.appendChild(el);
    }
    el.textContent = JSON.stringify(jsonLd);
    // Update canonical aussi pour cohérence SEO
    let canon = document.querySelector('link[rel="canonical"]');
    if (canon) canon.href = canonicalUrl;
    return () => {
      try { document.getElementById("cb-localbusiness")?.remove(); } catch {}
    };
  }, [pro, pc, hasReviews, reviews, avgRating, isPreview]);

  return (
    <main style={{ maxWidth: 720, margin: "0 auto", padding: "0 0 80px" }}>

      {/* COVER */}
      <section style={{
        position: "relative",
        height: 160,
        background: hasBanner ? `url(${pc.banner}) center/cover` :
          `linear-gradient(135deg, oklch(88% 0.08 ${hue}) 0%, oklch(85% 0.10 ${(hue+40)%360}) 60%, oklch(82% 0.12 ${(hue+80)%360}) 100%)`,
        overflow: "hidden",
      }}>
        {!hasBanner && (
          <div aria-hidden style={{
            position: "absolute", inset: 0,
            background: "radial-gradient(80% 60% at 70% 20%, rgba(255,255,255,0.4), transparent)",
          }}/>
        )}
      </section>

      {/* AVATAR + ACTIONS */}
      <section style={{
        position: "relative", marginTop: -44, padding: "0 20px",
        display: "flex", alignItems: "flex-end", justifyContent: "space-between", gap: 12,
      }}>
        <div style={{
          width: 88, height: 88, borderRadius: "50%",
          background: hasAvatar ? `url(${pro.avatar}) center/cover` :
            `linear-gradient(135deg, oklch(60% 0.20 ${hue}) 0%, oklch(45% 0.22 ${(hue+30)%360}) 100%)`,
          border: "4px solid var(--bg)",
          display: "inline-flex", alignItems: "center", justifyContent: "center",
          color: "#fff", fontSize: 32, fontWeight: 700, letterSpacing: "-0.02em",
          boxShadow: "0 10px 24px -8px rgba(15,18,30,0.18)",
          flexShrink: 0,
        }}>
          {!hasAvatar && initials}
        </div>
      </section>

      {/* IDENTITÉ */}
      <section style={{ padding: "14px 20px 0" }}>
        <h1 style={{ fontSize: 24, fontWeight: 680, letterSpacing: "-0.025em", margin: 0, lineHeight: 1.15 }}>
          {pro.businessName || "Mon activité"}
        </h1>
        {pc.tagline && (
          <p style={{ fontSize: 14.5, color: "var(--ink-3)", margin: "4px 0 0" }}>
            {pc.tagline}
          </p>
        )}
        <div style={{
          display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap",
          marginTop: 10, fontSize: 13, color: "var(--ink-3)",
        }}>
          {hasReviews && (
            <>
              <span style={{ color: "var(--ink-2)", fontWeight: 540, display: "inline-flex", alignItems: "center", gap: 5 }}>
                <span style={{ color: "#F59E0B" }}>★</span> {avgRating}
                <span style={{ color: "var(--ink-4)", fontWeight: 400 }}>({reviews.length} avis)</span>
              </span>
              <span style={{ color: "var(--line)" }}>·</span>
            </>
          )}
          {pro.city && (
            <>
              <span style={{ display: "inline-flex", alignItems: "center", gap: 5 }}>
                <span aria-hidden style={{ fontSize: 14 }}>📍</span> {pro.city}
              </span>
              <span style={{ color: "var(--line)" }}>·</span>
            </>
          )}
          <span style={{ display: "inline-flex", alignItems: "center", gap: 5 }}>
            <span aria-hidden style={{ fontSize: 14 }}>⏱</span> {todayLabel}
          </span>
        </div>
      </section>

      {/* SOCIAL ICONS */}
      {socials.length > 0 && (
        <section style={{ display: "flex", gap: 10, padding: "14px 20px 0" }}>
          {socials.map(s => (
            <a key={s.id} href={s.url} target="_blank" rel="noopener noreferrer"
              aria-label={s.id}
              style={{
                width: 38, height: 38, borderRadius: 11,
                background: s.bg, color: s.color,
                display: "inline-flex", alignItems: "center", justifyContent: "center",
                transition: "transform .15s ease, box-shadow .2s ease",
                cursor: "pointer", textDecoration: "none",
              }}
              onMouseEnter={(e) => { e.currentTarget.style.transform = "translateY(-2px)"; }}
              onMouseLeave={(e) => { e.currentTarget.style.transform = "translateY(0)"; }}>
              <svg viewBox="0 0 24 24" fill="currentColor" style={{ width: 18, height: 18 }}>
                {s.svg}
              </svg>
            </a>
          ))}
        </section>
      )}

      {/* CTA PRINCIPAL */}
      <button onClick={onBook}
        style={{
          margin: "20px 20px 0", padding: "16px 22px",
          background: "var(--ink)", color: "#fff",
          border: "none", borderRadius: 14,
          fontSize: 16, fontWeight: 600, letterSpacing: "-0.01em",
          display: "flex", alignItems: "center", justifyContent: "center", gap: 10,
          width: "calc(100% - 40px)", cursor: "pointer",
          boxShadow: "0 8px 22px -8px rgba(15,18,30,0.35)",
          fontFamily: "inherit",
          transition: "transform .15s ease, background .2s ease",
        }}
        onMouseEnter={(e) => { e.currentTarget.style.transform = "translateY(-1px)"; e.currentTarget.style.background = "var(--accent)"; }}
        onMouseLeave={(e) => { e.currentTarget.style.transform = "translateY(0)"; e.currentTarget.style.background = "var(--ink)"; }}>
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" style={{ width: 18, height: 18 }}>
          <rect x="3" y="4" width="18" height="18" rx="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/>
        </svg>
        Prendre rendez-vous
      </button>

      {/* BIO */}
      {pc.bio && (
        <p style={{
          margin: "20px 20px 0", padding: "14px 16px",
          background: "var(--surface)", border: "1px solid var(--line)",
          borderRadius: 12, fontSize: 14, color: "var(--ink-2)", lineHeight: 1.6,
        }}>
          {pc.bio}
        </p>
      )}

      {/* PRESTATIONS */}
      {previewServices.length > 0 && (
        <section style={{ padding: "24px 20px 0" }}>
          <div style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between", marginBottom: 12, gap: 10 }}>
            <h2 style={{
              fontSize: 13, fontWeight: 600, letterSpacing: "0.08em",
              textTransform: "uppercase", color: "var(--ink-4)", margin: 0,
            }}>Prestations</h2>
            {activeServices.length > previewServices.length && (
              <button onClick={onBook}
                style={{ background: "transparent", border: "none", padding: 0, cursor: "pointer",
                  fontSize: 12, color: "var(--accent-ink)", fontWeight: 540, fontFamily: "inherit" }}>
                Voir tout ({activeServices.length}) →
              </button>
            )}
          </div>
          <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(140px, 1fr))", gap: 10 }}>
            {previewServices.map(s => (
              <button key={s.id} onClick={onBook}
                style={{
                  padding: "14px 14px", background: "var(--surface)",
                  border: "1px solid var(--line)", borderRadius: 12,
                  cursor: "pointer", textAlign: "left", fontFamily: "inherit",
                  display: "flex", flexDirection: "column", gap: 6,
                  transition: "transform .15s ease, border-color .2s ease, box-shadow .2s ease",
                }}
                onMouseEnter={(e) => { e.currentTarget.style.transform = "translateY(-2px)"; e.currentTarget.style.borderColor = "var(--accent)"; }}
                onMouseLeave={(e) => { e.currentTarget.style.transform = "translateY(0)"; e.currentTarget.style.borderColor = "var(--line)"; }}>
                {catNameOf(s.categoryId) && (
                  <div style={{ fontSize: 10.5, textTransform: "uppercase", letterSpacing: "0.06em",
                    color: "var(--accent-ink)", fontWeight: 600 }}>
                    {catNameOf(s.categoryId)}
                  </div>
                )}
                <div style={{ fontSize: 14.5, fontWeight: 560, letterSpacing: "-0.005em", color: "var(--ink)" }}>
                  {s.name}
                </div>
                <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center",
                  marginTop: "auto", paddingTop: 6 }}>
                  <span style={{ fontSize: 12, color: "var(--ink-4)" }}>{fmtDur(s.duration)}</span>
                  <span style={{ fontSize: 15, fontWeight: 700, color: "var(--ink)", letterSpacing: "-0.015em" }}>
                    {fmtPrice(s.price, s.priceFrom)}
                  </span>
                </div>
              </button>
            ))}
          </div>
        </section>
      )}

      {/* AVIS */}
      {hasReviews && (
        <section style={{ padding: "24px 20px 0" }}>
          <div style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between", marginBottom: 12, gap: 10 }}>
            <h2 style={{ fontSize: 13, fontWeight: 600, letterSpacing: "0.08em",
              textTransform: "uppercase", color: "var(--ink-4)", margin: 0 }}>
              Avis vérifiés
            </h2>
            <span style={{ fontSize: 12, color: "var(--ink-4)" }}>{reviews.length} au total</span>
          </div>
          <div style={{
            display: "flex", gap: 10, overflowX: "auto",
            paddingBottom: 6, margin: "0 -20px", paddingLeft: 20, paddingRight: 20,
            scrollbarWidth: "none",
          }}>
            {reviews.slice(0, 8).map(r => (
              <div key={r.id} style={{
                flex: "0 0 280px", padding: "14px 16px",
                background: "var(--surface)", border: "1px solid var(--line)",
                borderRadius: 12, display: "flex", flexDirection: "column", gap: 6,
              }}>
                <span style={{ color: "#F59E0B", fontSize: 14, letterSpacing: 1, lineHeight: 1 }}>
                  {"★".repeat(Math.round(r.rating || 5))}
                </span>
                <p style={{ fontSize: 13, color: "var(--ink-2)", lineHeight: 1.55, margin: 0 }}>
                  « {r.comment} »
                </p>
                <span style={{ fontSize: 11.5, color: "var(--ink-4)", marginTop: 4 }}>
                  {r.clientName || "Cliente"}{r.date ? ` · ${new Date(r.date).toLocaleDateString("fr-FR", { day: "numeric", month: "short" })}` : ""}
                </span>
              </div>
            ))}
          </div>
        </section>
      )}

      {/* INFOS PRATIQUES */}
      <section style={{ padding: "24px 20px 0" }}>
        <div style={{ marginBottom: 12 }}>
          <h2 style={{ fontSize: 13, fontWeight: 600, letterSpacing: "0.08em",
            textTransform: "uppercase", color: "var(--ink-4)", margin: 0 }}>
            Infos pratiques
          </h2>
        </div>
        <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
          {(pro.city || pc.locationLabel) && (
            <InfoRow icon="📍" title={pc.locationLabel || pro.city} subtitle={pro.address || null}/>
          )}
          {(data && data.bookingSettings && data.bookingSettings.schedule) && (
            <InfoRow icon="⏰" title={todayLabel} subtitle="Voir tous les horaires lors de la prise de RDV"/>
          )}
          {pro.phone && (
            <InfoRow icon="📞" title={pro.phone} subtitle="SMS / WhatsApp privilégiés"/>
          )}
          {pro.contactEmail && (
            <InfoRow icon="✉️" title={pro.contactEmail} subtitle="Réponse sous 24h"/>
          )}
        </div>
      </section>

      {/* TRUST FOOTER */}
      <p style={{
        margin: "28px 20px 0", textAlign: "center", fontSize: 12, color: "var(--ink-4)",
        paddingTop: 18, borderTop: "1px solid var(--line)",
      }}>
        Réservation sécurisée via <strong style={{ color: "var(--accent)" }}>ClientBase.fr</strong> · sans commission · sans inscription obligatoire
      </p>
    </main>
  );
};

// Petit composant pour les lignes d'infos pratiques
const InfoRow = ({ icon, title, subtitle }) => (
  <div style={{
    display: "flex", alignItems: "flex-start", gap: 12,
    padding: "12px 14px", background: "var(--surface)",
    border: "1px solid var(--line)", borderRadius: 11,
  }}>
    <div style={{
      width: 32, height: 32, flexShrink: 0,
      background: "var(--accent-soft)", color: "var(--accent)",
      borderRadius: 9, display: "inline-flex", alignItems: "center", justifyContent: "center",
      fontSize: 15,
    }}>
      {icon}
    </div>
    <div style={{ fontSize: 13.5, color: "var(--ink-2)", minWidth: 0 }}>
      <strong style={{ display: "block", color: "var(--ink)", fontWeight: 580, marginBottom: 2 }}>{title}</strong>
      {subtitle && <small style={{ color: "var(--ink-4)", fontSize: 12, display: "block", marginTop: 1 }}>{subtitle}</small>}
    </div>
  </div>
);

const VitrineView = ({ pro, data, pageCustom, activeServices, onBook, isPreview }) => {
  const pc = pageCustom || {};
  const hasBio = pc.bio && pc.showBio !== false;
  const hasTags = pc.tags && String(pc.tags).trim();
  const hasGallery = pc.gallery && pc.gallery.length > 0 && pc.showGallery !== false;
  const reviews = (data && data.reviews) || [];
  const hasReviews = reviews.length > 0;
  const serviceCount = activeServices.length;
  const categories = (data && data.serviceCategories) || [];
  const SECTION_GAP = 48;

  // Note moyenne calculée depuis les avis (si dispo)
  const avgRating = hasReviews
    ? (reviews.reduce((s, r) => s + (r.rating || 0), 0) / reviews.length).toFixed(1)
    : null;

  // Statut du jour : Vacances > Fermé > Ouvert.
  // Vacances : si une période de vacances en DB englobe aujourd'hui.
  // Fermé : si le pro n'a pas coché ce jour de la semaine dans son schedule.
  // Ouvert : avec les horaires correspondants.
  const todayStatus = (() => {
    const bs = (data && data.bookingSettings) || {};
    const today = new Date();
    const ymd = today.toISOString().slice(0, 10);
    // 1) Vacances en cours ?
    const onVacation = (bs.vacations || []).some(v =>
      v && v.start && v.end && ymd >= v.start && ymd <= v.end
    );
    if (onVacation) return { kind: "vacation", label: "En vacances" };
    // 2) Jour de la semaine dans le schedule
    const dow = today.getDay() === 0 ? 7 : today.getDay(); // ISO
    const day = (bs.schedule && bs.schedule[dow]) || null;
    if (!day || !day.open) return { kind: "closed", label: "Fermé" };
    const fmt = (h) => {
      const hh = Math.floor(h);
      const mm = Math.round((h - hh) * 60);
      return mm === 0 ? `${hh}h` : `${hh}h${String(mm).padStart(2, "0")}`;
    };
    return { kind: "open", label: `${fmt(day.start || 9)}–${fmt(day.end || 18)}` };
  })();
  // Helper formatter prix (utile pour l'aperçu prestations)
  const fmtPrice = (p, from) => {
    if (p == null || p === 0) return "Sur devis";
    const v = Number(p).toLocaleString("fr-FR", { minimumFractionDigits: 0, maximumFractionDigits: 0 });
    return from ? `À partir de ${v} €` : `${v} €`;
  };
  const fmtDuration = (d) => {
    const h = Math.floor(d);
    const m = Math.round((d - h) * 60);
    if (h === 0) return `${m} min`;
    if (m === 0) return `${h}h`;
    return `${h}h${String(m).padStart(2, "0")}`;
  };
  // 3 prestations à afficher en aperçu : on prend les 3 premières actives
  const previewServices = activeServices.slice(0, 3);
  // Catégorie d'une prestation (label visible), pour grouper l'aperçu
  const catNameOf = (catId) => {
    const c = categories.find(c => c.id === catId);
    return c ? c.name : null;
  };

  // Couleur de fond pastel du bloc principal (rose, bleu, vert, beige, lavande, gris)
  const blockBgMap = {
    gris:    { bg: "oklch(96% 0.005 270)", border: "oklch(91% 0.005 270)" },
    rose:    { bg: "oklch(96% 0.03 350)",  border: "oklch(90% 0.04 350)"  },
    bleu:    { bg: "oklch(96% 0.03 230)",  border: "oklch(90% 0.04 230)"  },
    vert:    { bg: "oklch(96% 0.03 160)",  border: "oklch(90% 0.04 160)"  },
    beige:   { bg: "oklch(96% 0.03 80)",   border: "oklch(90% 0.04 80)"   },
    lavande: { bg: "oklch(96% 0.03 290)",  border: "oklch(90% 0.04 290)"  },
  };
  const blockBg = blockBgMap[pc.vitrineBlockBg || "gris"] || blockBgMap.gris;

  // Première photo de la galerie (utilisée comme visuel du bloc principal)
  const firstPhoto = hasGallery
    ? (typeof pc.gallery[0] === "string" ? pc.gallery[0] : (pc.gallery[0] && pc.gallery[0].src))
    : null;

  return (
    <main style={{ flex: 1, background: "var(--bg)", position: "relative" }}>
      <div style={{
        maxWidth: 720,            // un peu plus large (680 → 720) pour plus d'air
        margin: "0 auto",
        padding: "0 10px 24px",   // padding latéral 16 → 10 : blocs plus larges
                                  // padding-bottom 100 → 24 : plus de gros vide
                                  // avant le footer "Propulsé par ClientBase"
      }}>

        {/* ============ 1. Quick stats, juste collé sous le header ============
            Trois chiffres clés en ligne : avis, prestations, statut ouvert/fermé. */}
        <section style={{
          display: "grid",
          gridTemplateColumns: "repeat(3, 1fr)",
          gap: 1,
          background: "var(--line)",
          border: "1px solid var(--line)",
          borderRadius: 14,
          overflow: "hidden",
          marginTop: 14, marginBottom: 14,  // resserre l'espace stats/bloc
        }}>
          <div style={{ padding: "12px 8px", background: "var(--surface)", textAlign: "center" }}>
            <div style={{
              fontFamily: "var(--ff-display)", fontSize: 19, fontWeight: 600,
              letterSpacing: "-0.025em", color: "var(--ink)",
              display: "inline-flex", alignItems: "center", gap: 3,
              fontVariantNumeric: "tabular-nums",
            }}>
              {avgRating || "—"}
              <span style={{ fontSize: 13, color: "oklch(70% 0.16 80)" }}>★</span>
            </div>
            <div style={{
              fontSize: 10.5, color: "var(--ink-4)", marginTop: 2,
              textTransform: "uppercase", letterSpacing: "0.05em", fontWeight: 540,
            }}>
              {hasReviews ? `${reviews.length} avis` : "Avis"}
            </div>
          </div>
          <div style={{ padding: "12px 8px", background: "var(--surface)", textAlign: "center" }}>
            <div style={{
              fontFamily: "var(--ff-display)", fontSize: 19, fontWeight: 600,
              letterSpacing: "-0.025em", color: "var(--ink)",
              fontVariantNumeric: "tabular-nums",
            }}>
              {serviceCount}
            </div>
            <div style={{
              fontSize: 10.5, color: "var(--ink-4)", marginTop: 2,
              textTransform: "uppercase", letterSpacing: "0.05em", fontWeight: 540,
            }}>
              Prestation{serviceCount > 1 ? "s" : ""}
            </div>
          </div>
          <div style={{ padding: "12px 8px", background: "var(--surface)", textAlign: "center" }}>
            {(() => {
              // Couleurs + libellés selon le statut
              const cfg = todayStatus.kind === "open"
                ? { dot: "oklch(55% 0.18 145)", txt: "oklch(45% 0.13 145)", main: "Ouvert", sub: todayStatus.label }
                : todayStatus.kind === "vacation"
                ? { dot: "oklch(65% 0.16 80)",  txt: "oklch(45% 0.15 80)",  main: "Vacances", sub: "Aujourd'hui" }
                : { dot: "oklch(60% 0.18 25)",  txt: "oklch(50% 0.13 25)",  main: "Fermé",    sub: "Aujourd'hui" };
              return (
                <>
                  <div style={{
                    display: "inline-flex", alignItems: "center", gap: 5,
                    fontFamily: "var(--ff-display)", fontSize: 14, fontWeight: 600,
                    letterSpacing: "-0.012em", color: cfg.txt,
                  }}>
                    <span aria-hidden style={{
                      width: 7, height: 7, borderRadius: "50%",
                      background: cfg.dot,
                    }}/>
                    {cfg.main}
                  </div>
                  <div style={{
                    fontSize: 10.5, color: "var(--ink-4)", marginTop: 2,
                    textTransform: "uppercase", letterSpacing: "0.05em", fontWeight: 540,
                  }}>
                    {cfg.sub}
                  </div>
                </>
              );
            })()}
          </div>
        </section>

        {/* ============ 2. LE BLOC UNIQUE COLORÉ ============
            Contient tout l'essentiel : photo (si dispo) + bouton réserver +
            comment ça marche + avis détaillés.
            Fond pastel choisi par le pro (gris / rose / bleu / vert / beige /
            lavande), se distingue clairement du reste de la page. */}
        <section style={{
          padding: "24px 22px",
          background: blockBg.bg,
          border: `1px solid ${blockBg.border}`,
          borderRadius: 18,
          display: "flex", flexDirection: "column", gap: 26,
        }}>

          {/* Photo principale (si galerie remplie), premier visuel impactant.
              Si pas de photo : on saute ce bloc, le bouton vient en premier. */}
          {firstPhoto && (
            <div style={{
              width: "100%", aspectRatio: "16 / 9",
              background: `url(${firstPhoto}) center/cover`,
              borderRadius: 14,
              boxShadow: "0 4px 14px -8px rgba(15,18,30,0.16)",
            }}/>
          )}

          {/* Bio + tags (centrés dans le bloc) si remplis */}
          {(hasBio || hasTags) && (
            <div style={{ textAlign: "center" }}>
              {hasBio && (
                <p style={{
                  margin: 0, fontFamily: "var(--ff-display)",
                  fontSize: 16, color: "var(--ink-2)",
                  lineHeight: 1.55, fontWeight: 500,
                  letterSpacing: "-0.012em",
                  wordBreak: "break-word", overflowWrap: "anywhere",
                }}>
                  {pc.bio}
                </p>
              )}
              {hasTags && (
                <div style={{
                  marginTop: hasBio ? 12 : 0,
                  display: "flex", flexWrap: "wrap", gap: 6,
                  justifyContent: "center",
                }}>
                  {String(pc.tags).split(",").map(t => t.trim()).filter(Boolean).slice(0, 6).map(t => (
                    <span key={t} style={{
                      fontSize: 11.5, padding: "4px 10px",
                      background: "var(--surface)", color: "var(--ink-3)",
                      border: "1px solid var(--line)",
                      borderRadius: 999, fontWeight: 540,
                    }}>· {t}</span>
                  ))}
                </div>
              )}
            </div>
          )}

          {/* Section "Mes services", 3 prestations phares en mini-cards.
              Affichée juste avant le CTA pour que le visiteur ait vu
              concrètement ce qui l'attend avant de cliquer. */}
          {previewServices.length > 0 && (
            <div>
              <div style={{
                fontFamily: "var(--ff-mono)", fontSize: 10.5,
                textTransform: "uppercase", letterSpacing: "0.08em",
                color: "var(--ink-3)", fontWeight: 600,
                marginBottom: 4, textAlign: "center",
              }}>
                Mes services
              </div>
              <h2 style={{
                margin: "0 0 14px", fontFamily: "var(--ff-display)",
                fontSize: 19, fontWeight: 600, letterSpacing: "-0.022em",
                color: "var(--ink)", textAlign: "center",
              }}>
                {serviceCount > 3 ? `${serviceCount} prestations · aperçu` : "Ce que je propose"}
              </h2>
              <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
                {previewServices.map(s => (
                  <button key={s.id} onClick={onBook}
                    style={{
                      width: "100%", textAlign: "left",
                      padding: "12px 14px",
                      background: "var(--surface)",
                      border: "1px solid var(--line)",
                      borderRadius: 11, cursor: "pointer", fontFamily: "inherit",
                      display: "flex", alignItems: "center", gap: 12,
                      transition: "transform .12s, border-color .15s",
                    }}
                    onMouseEnter={e => { e.currentTarget.style.transform = "translateY(-1px)"; e.currentTarget.style.borderColor = "var(--accent-soft-2)"; }}
                    onMouseLeave={e => { e.currentTarget.style.transform = "translateY(0)"; e.currentTarget.style.borderColor = "var(--line)"; }}>
                    <div style={{ flex: 1, minWidth: 0 }}>
                      {catNameOf(s.categoryId) && (
                        <div style={{
                          fontSize: 10, color: "var(--accent-ink)", fontWeight: 600,
                          textTransform: "uppercase", letterSpacing: "0.05em",
                          marginBottom: 2,
                        }}>
                          {catNameOf(s.categoryId)}
                        </div>
                      )}
                      <div style={{
                        fontSize: 13.5, fontWeight: 580, color: "var(--ink)",
                        letterSpacing: "-0.005em",
                      }}>
                        {s.name}
                      </div>
                      <div style={{ fontSize: 11.5, color: "var(--ink-4)", marginTop: 1 }}>
                        {fmtDuration(s.duration)}
                      </div>
                    </div>
                    <div style={{
                      fontSize: 14, fontWeight: 700, color: "var(--ink)",
                      fontVariantNumeric: "tabular-nums", flexShrink: 0,
                    }}>
                      {fmtPrice(s.price, s.priceFrom)}
                    </div>
                  </button>
                ))}
              </div>
              {serviceCount > previewServices.length && (
                <button onClick={onBook}
                  style={{
                    width: "100%", marginTop: 8, padding: "9px 12px",
                    background: "transparent", border: "1px dashed var(--line-strong)",
                    borderRadius: 10, cursor: "pointer", fontFamily: "inherit",
                    fontSize: 12.5, color: "var(--ink-3)", fontWeight: 540,
                    display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 6,
                  }}>
                  Voir les {serviceCount - previewServices.length} autres
                  <Icon name="arrow" size={12}/>
                </button>
              )}
            </div>
          )}

          {/* Gros bouton "Prendre rendez-vous" */}
          <button onClick={onBook}
            className="btn btn-primary btn-lg"
            style={{
              width: "100%", padding: "18px 24px",
              fontSize: 16, fontWeight: 600,
              display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 10,
              boxShadow: "0 4px 14px -8px rgba(15,18,30,0.22)",
              letterSpacing: "-0.012em",
              transition: "transform .15s ease, box-shadow .2s ease",
            }}
            onMouseEnter={e => { e.currentTarget.style.transform = "translateY(-2px)"; e.currentTarget.style.boxShadow = "0 8px 20px -8px rgba(15,18,30,0.26)"; }}
            onMouseLeave={e => { e.currentTarget.style.transform = "translateY(0)"; e.currentTarget.style.boxShadow = "0 4px 14px -8px rgba(15,18,30,0.22)"; }}>
            <Icon name="calendar" size={18}/>
            Prendre rendez-vous
            <Icon name="arrow" size={16}/>
          </button>

          {/* Section avis : on n'affiche QUE si le pro a déjà reçu de vrais
              avis. Pas de placeholder "engagements" (jugé top-down, peu
              authentique). Si pas d'avis, le bloc n'apparait simplement pas. */}
          {hasReviews && (
            <div>
              <div style={{
                fontFamily: "var(--ff-mono)", fontSize: 10.5,
                textTransform: "uppercase", letterSpacing: "0.08em",
                color: "var(--ink-3)", fontWeight: 600,
                marginBottom: 4, textAlign: "center",
              }}>
                {reviews.length} avis vérifiés
              </div>
              <h2 style={{
                margin: "0 0 16px", fontFamily: "var(--ff-display)",
                fontSize: 19, fontWeight: 600, letterSpacing: "-0.022em",
                color: "var(--ink)", textAlign: "center",
              }}>
                Ce qu'elles en disent
              </h2>
              <BookingReviews reviews={reviews} embedded/>
            </div>
          )}

        </section>

        {/* ============ 2e BLOC : Comment ça marche (même couleur, distinct) ============
            Bloc séparé du 1er pour aérer et donner du rythme. Même fond
            pastel pour rester cohérent visuellement. */}
        <section style={{
          marginTop: 10,           // resserre vs ancien 16
          padding: "22px 22px",

          background: blockBg.bg,
          border: `1px solid ${blockBg.border}`,
          borderRadius: 18,
        }}>
          <div style={{
            fontFamily: "var(--ff-mono)", fontSize: 10.5,
            textTransform: "uppercase", letterSpacing: "0.08em",
            color: "var(--ink-3)", fontWeight: 600,
            marginBottom: 4, textAlign: "center",
          }}>
            Comment ça marche
          </div>
          <h2 style={{
            margin: "0 0 16px", fontFamily: "var(--ff-display)",
            fontSize: 19, fontWeight: 600, letterSpacing: "-0.022em",
            color: "var(--ink)", textAlign: "center",
          }}>
            3 étapes, 30 secondes
          </h2>
          <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
            {[
              { n: 1, title: "Choisissez une prestation", desc: "Parcourez le catalogue et sélectionnez ce qui vous plaît." },
              { n: 2, title: "Sélectionnez un créneau", desc: "Voyez en direct les horaires disponibles." },
              { n: 3, title: "Recevez la confirmation", desc: "Email instantané avec tous les détails." },
            ].map(s => (
              <div key={s.n} style={{
                display: "flex", gap: 12, alignItems: "flex-start",
                padding: "12px 14px",
                background: "var(--surface)",
                border: "1px solid var(--line)",
                borderRadius: 11,
              }}>
                <div style={{
                  width: 30, height: 30, borderRadius: "50%",
                  background: "var(--accent-soft)", color: "var(--accent-ink)",
                  display: "inline-flex", alignItems: "center", justifyContent: "center",
                  fontFamily: "var(--ff-display)", fontSize: 14, fontWeight: 600,
                  flexShrink: 0,
                }}>
                  {s.n}
                </div>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{
                    fontSize: 13.5, fontWeight: 580, color: "var(--ink)",
                    letterSpacing: "-0.005em", marginBottom: 1,
                  }}>
                    {s.title}
                  </div>
                  <div style={{ fontSize: 12, color: "var(--ink-3)", lineHeight: 1.5 }}>
                    {s.desc}
                  </div>
                </div>
              </div>
            ))}
          </div>
        </section>

        {/* ============ Forfaits (lots de séances) ============ */}
        <ForfaitsOffer pc={pc} blockBg={blockBg}/>

        {/* ============ Carte cadeau (achat en ligne) ============ */}
        <GiftCardOffer pro={pro} blockBg={blockBg}/>

        {/* ============ 3e BLOC : Suivez-moi + Vérifié ============
            Bloc compact qui regroupe les liens sociaux et un badge
            de confiance "Vérifié par ClientBase". Même style coloré
            que les autres blocs pour rester cohérent. */}
        {(() => {
          const hasInsta = pc.showSocials && pc.socialInstagram;
          const hasTiktok = pc.showSocials && pc.socialTiktok;
          const hasFacebook = pc.showSocials && pc.socialFacebook;
          const hasSnapchat = pc.showSocials && pc.socialSnapchat;
          const hasWhatsapp = pc.showWhatsapp && (pc.socialWhatsapp || pro.phone);
          const hasWebsite = pc.showWebsite && pc.websiteUrl;
          const hasGoogle = pc.showReviews && pc.googleReviewUrl;
          const hasAnySocial = hasInsta || hasTiktok || hasFacebook || hasSnapchat || hasWhatsapp || hasWebsite || hasGoogle;
          if (!hasAnySocial && !pro.emailVerified) return null;
          // Style commun aux pastilles de liens (réseaux, site, avis).
          const pillStyle = {
            display: "inline-flex", alignItems: "center", gap: 7,
            padding: "10px 14px", background: "var(--surface)",
            border: "1px solid var(--line)", borderRadius: 11,
            fontSize: 13, fontWeight: 580, color: "var(--ink)", textDecoration: "none",
            transition: "border-color .15s, transform .12s",
          };
          const pillHover = (hue) => ({
            onMouseEnter: e => { e.currentTarget.style.borderColor = hue; e.currentTarget.style.transform = "translateY(-1px)"; },
            onMouseLeave: e => { e.currentTarget.style.borderColor = "var(--line)"; e.currentTarget.style.transform = "translateY(0)"; },
          });
          const fbHref = /^https?:\/\//.test(pc.socialFacebook || "") ? pc.socialFacebook : `https://facebook.com/${pc.socialFacebook}`;
          const googleHref = /^https?:\/\//.test(pc.googleReviewUrl || "") ? pc.googleReviewUrl : `https://${pc.googleReviewUrl}`;
          return (
            <section style={{
              marginTop: 10,             // resserre vs ancien 16
              padding: "22px 22px",
              background: blockBg.bg,
              border: `1px solid ${blockBg.border}`,
              borderRadius: 18,
              display: "flex", flexDirection: "column", gap: 18,
            }}>
              {/* Suivez-moi */}
              {hasAnySocial && (
                <div>
                  <div style={{
                    fontFamily: "var(--ff-mono)", fontSize: 10.5,
                    textTransform: "uppercase", letterSpacing: "0.08em",
                    color: "var(--ink-3)", fontWeight: 600,
                    marginBottom: 4, textAlign: "center",
                  }}>
                    Restons en contact
                  </div>
                  <h2 style={{
                    margin: "0 0 14px", fontFamily: "var(--ff-display)",
                    fontSize: 19, fontWeight: 600, letterSpacing: "-0.022em",
                    color: "var(--ink)", textAlign: "center",
                  }}>
                    Suivez-moi
                  </h2>
                  <div style={{
                    display: "flex", flexWrap: "wrap", gap: 8,
                    justifyContent: "center",
                  }}>
                    {hasInsta && (
                      <a href={`https://instagram.com/${pc.socialInstagram}`}
                         target="_blank" rel="noopener"
                         style={{
                           display: "inline-flex", alignItems: "center", gap: 7,
                           padding: "10px 14px",
                           background: "var(--surface)",
                           border: "1px solid var(--line)",
                           borderRadius: 11, fontSize: 13, fontWeight: 580,
                           color: "var(--ink)", textDecoration: "none",
                           transition: "border-color .15s, transform .12s",
                         }}
                         onMouseEnter={e => { e.currentTarget.style.borderColor = "oklch(70% 0.14 340)"; e.currentTarget.style.transform = "translateY(-1px)"; }}
                         onMouseLeave={e => { e.currentTarget.style.borderColor = "var(--line)"; e.currentTarget.style.transform = "translateY(0)"; }}>
                        <span style={{ fontSize: 14 }}>📷</span>
                        @{pc.socialInstagram}
                      </a>
                    )}
                    {hasWhatsapp && (
                      <a href={`https://wa.me/${((pc.socialWhatsapp || pro.phone) || "").replace(/\D/g, "")}`}
                         target="_blank" rel="noopener"
                         style={{
                           display: "inline-flex", alignItems: "center", gap: 7,
                           padding: "10px 14px",
                           background: "var(--surface)",
                           border: "1px solid var(--line)",
                           borderRadius: 11, fontSize: 13, fontWeight: 580,
                           color: "var(--ink)", textDecoration: "none",
                           transition: "border-color .15s, transform .12s",
                         }}
                         onMouseEnter={e => { e.currentTarget.style.borderColor = "oklch(70% 0.14 145)"; e.currentTarget.style.transform = "translateY(-1px)"; }}
                         onMouseLeave={e => { e.currentTarget.style.borderColor = "var(--line)"; e.currentTarget.style.transform = "translateY(0)"; }}>
                        <span style={{ fontSize: 14 }}>💬</span>
                        WhatsApp
                      </a>
                    )}
                    {hasWebsite && (
                      <a href={/^https?:\/\//.test(pc.websiteUrl) ? pc.websiteUrl : `https://${pc.websiteUrl}`}
                         target="_blank" rel="noopener"
                         style={{
                           display: "inline-flex", alignItems: "center", gap: 7,
                           padding: "10px 14px",
                           background: "var(--surface)",
                           border: "1px solid var(--line)",
                           borderRadius: 11, fontSize: 13, fontWeight: 580,
                           color: "var(--ink)", textDecoration: "none",
                           transition: "border-color .15s, transform .12s",
                           maxWidth: 220, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
                         }}
                         onMouseEnter={e => { e.currentTarget.style.borderColor = "var(--accent-soft-2)"; e.currentTarget.style.transform = "translateY(-1px)"; }}
                         onMouseLeave={e => { e.currentTarget.style.borderColor = "var(--line)"; e.currentTarget.style.transform = "translateY(0)"; }}>
                        🌐 {pc.websiteUrl.replace(/^https?:\/\/(www\.)?/, "")}
                      </a>
                    )}
                    {hasTiktok && (
                      <a href={`https://tiktok.com/@${pc.socialTiktok}`} target="_blank" rel="noopener"
                         style={pillStyle} {...pillHover("oklch(60% 0.02 260)")}>
                        <span style={{ fontSize: 14 }}>🎵</span>@{pc.socialTiktok}
                      </a>
                    )}
                    {hasFacebook && (
                      <a href={fbHref} target="_blank" rel="noopener"
                         style={pillStyle} {...pillHover("oklch(60% 0.18 250)")}>
                        <span style={{ fontSize: 14 }}>👍</span>Facebook
                      </a>
                    )}
                    {hasSnapchat && (
                      <a href={`https://snapchat.com/add/${pc.socialSnapchat}`} target="_blank" rel="noopener"
                         style={pillStyle} {...pillHover("oklch(85% 0.18 100)")}>
                        <span style={{ fontSize: 14 }}>👻</span>@{pc.socialSnapchat}
                      </a>
                    )}
                    {hasGoogle && (
                      <a href={googleHref} target="_blank" rel="noopener"
                         style={pillStyle} {...pillHover("oklch(70% 0.15 50)")}>
                        <span style={{ fontSize: 14 }}>⭐</span>Voir nos avis Google
                      </a>
                    )}
                  </div>
                </div>
              )}

              {/* Badge Vérifié, mis en avant comme gage de confiance */}
              {pro.emailVerified && (
                <div style={{
                  display: "flex", alignItems: "center", gap: 12,
                  padding: "14px 16px",
                  background: "var(--surface)",
                  border: "1px solid var(--line)",
                  borderRadius: 12,
                }}>
                  <svg width={32} height={32} viewBox="0 0 24 24" style={{ flexShrink: 0 }}
                       role="img" aria-label="Compte vérifié par ClientBase">
                    <path fill="#5A50E6" d="M22.25 12L20.86 10.41L21.43 8.36L19.39 7.74L18.95 5.66L16.83 5.5L15.69 3.71L13.66 4.47L12 3.16L10.34 4.47L8.31 3.71L7.17 5.5L5.05 5.66L4.61 7.74L2.57 8.36L3.14 10.41L1.75 12L3.14 13.59L2.57 15.64L4.61 16.26L5.05 18.34L7.17 18.5L8.31 20.29L10.34 19.53L12 20.84L13.66 19.53L15.69 20.29L16.83 18.5L18.95 18.34L19.39 16.26L21.43 15.64L20.86 13.59L22.25 12Z"/>
                    <path d="M8.5 12L11 14.5L15.5 10" stroke="white" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" fill="none"/>
                  </svg>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{
                      fontSize: 13.5, fontWeight: 580, color: "var(--ink)",
                      letterSpacing: "-0.005em", marginBottom: 1,
                    }}>
                      Compte vérifié par ClientBase
                    </div>
                    <div style={{ fontSize: 11.5, color: "var(--ink-3)", lineHeight: 1.5 }}>
                      Identité confirmée, vous pouvez réserver en toute confiance.
                    </div>
                  </div>
                </div>
              )}
            </section>
          );
        })()}

        {/* ============ Pub ClientBase ============
            Petit encart sympa qui présente ClientBase aux visiteurs.
            Cachée si le pro a activé "Pas de pub ClientBase" dans ses
            paramètres (option Plan Pro). */}
        {!isPreview && !pc.hideClientBaseAd && (
          <a href="/" style={{
            display: "block",
            marginTop: 16, padding: "14px 16px",       // marginTop 28 → 16
            background: "var(--surface)",
            border: "1px solid var(--line)",
            borderRadius: 14,
            textDecoration: "none", color: "inherit",
            transition: "border-color .15s, transform .12s",
          }}
            onMouseEnter={e => { e.currentTarget.style.borderColor = "var(--accent-soft-2)"; e.currentTarget.style.transform = "translateY(-1px)"; }}
            onMouseLeave={e => { e.currentTarget.style.borderColor = "var(--line)"; e.currentTarget.style.transform = "translateY(0)"; }}>
            <div style={{
              display: "flex", alignItems: "center", gap: 12,
            }}>
              {/* Logo CB carré indigo */}
              <div style={{
                width: 40, height: 40, borderRadius: 10,
                background: "#5A50E6",
                display: "inline-flex", alignItems: "center", justifyContent: "center",
                flexShrink: 0,
              }}>
                <svg width="22" height="22" viewBox="0 0 32 32" style={{ display: "block" }}>
                  <path d="M11.5 20.5 L20.5 11.5 M13.5 11.5 L20.5 11.5 L20.5 18.5"
                    stroke="white" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" fill="none"/>
                </svg>
              </div>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{
                  fontSize: 13.5, fontWeight: 600, color: "var(--ink)",
                  letterSpacing: "-0.005em",
                }}>
                  Vous êtes pro&nbsp;? Créez votre page comme celle-ci
                </div>
                <div style={{ fontSize: 11.5, color: "var(--ink-3)", marginTop: 2 }}>
                  ClientBase · gratuit pendant la bêta, sans carte bancaire
                </div>
              </div>
              <Icon name="arrow" size={14} style={{ color: "var(--accent-ink)", flexShrink: 0 }}/>
            </div>
          </a>
        )}

      </div>
    </main>
  );
};

/* Titre de section réutilisable : eyebrow uppercase + h2, donne un
   rythme visuel cohérent sur toute la vitrine (style magazine). */
const SectionTitle = ({ eyebrow, title }) => (
  <div style={{ marginBottom: 14 }}>
    {eyebrow && (
      <div style={{
        fontFamily: "var(--ff-mono)", fontSize: 10.5,
        textTransform: "uppercase", letterSpacing: "0.08em",
        color: "var(--accent-ink)", fontWeight: 600,
        marginBottom: 4,
      }}>
        {eyebrow}
      </div>
    )}
    <h2 style={{
      margin: 0, fontFamily: "var(--ff-display)",
      fontSize: 19, fontWeight: 600, letterSpacing: "-0.022em",
      lineHeight: 1.2, color: "var(--ink)",
    }}>
      {title}
    </h2>
  </div>
);

/* ============ Theme styles (couleurs unies + typo) ============ */
// Couleurs principales : id → { color, mode }. Génère le CSS dynamiquement.
const BOOKING_THEME_MAP = {
  indigo: { color: "oklch(55% 0.22 278)", mode: "light" },
  bleu:   { color: "oklch(55% 0.17 245)", mode: "light" },
  ciel:   { color: "oklch(62% 0.13 220)", mode: "light" },
  teal:   { color: "oklch(58% 0.12 195)", mode: "light" },
  vert:   { color: "oklch(58% 0.15 150)", mode: "light" },
  jaune:  { color: "oklch(78% 0.15 90)",  mode: "light" },
  ambre:  { color: "oklch(62% 0.15 65)",  mode: "light" },
  orange: { color: "oklch(64% 0.18 40)",  mode: "light" },
  rouge:  { color: "oklch(57% 0.21 25)",  mode: "light" },
  rose:   { color: "oklch(62% 0.17 350)", mode: "light" },
  violet: { color: "oklch(55% 0.20 305)", mode: "light" },
  noir:   { color: "oklch(22% 0.01 280)", mode: "light" },
};
// Génère les CSS variables d'un thème à couleur unie
const _bookingThemeCss = (themeId) => {
  const t = BOOKING_THEME_MAP[themeId];
  if (!t || themeId === "indigo") return ""; // indigo = défaut, rien à override
  const { color, mode } = t;
  if (mode === "dark") {
    return `
      [data-cb-theme="${themeId}"] {
        --bg: #15141c; --bg-section: #1a1924; --surface: #201f2b;
        --ink: #fafaf7; --ink-2: rgba(250,250,247,0.78); --ink-3: rgba(250,250,247,0.58); --ink-4: rgba(250,250,247,0.4);
        --accent: ${color}; --accent-ink: ${color};
        --accent-soft: rgba(255,255,255,0.07); --accent-soft-2: rgba(255,255,255,0.14);
        --line: rgba(255,255,255,0.09); --line-strong: rgba(255,255,255,0.18);
      }
      [data-cb-theme="${themeId}"] .btn-primary { color: #15141c; }
    `;
  }
  // Mode clair : fond blanc, accent = la couleur choisie
  return `
    [data-cb-theme="${themeId}"] {
      --accent: ${color};
      --accent-ink: color-mix(in oklab, ${color} 78%, #000);
      --accent-soft: color-mix(in oklab, ${color} 12%, #fff);
      --accent-soft-2: color-mix(in oklab, ${color} 22%, #fff);
    }
  `;
};
const BOOKING_THEME_CSS = Object.fromEntries(
  Object.keys(BOOKING_THEME_MAP).map(id => [id, _bookingThemeCss(id)])
);
const BOOKING_FONT_CSS = {
  modern: ``,
  elegant: `
    [data-cb-font="elegant"] h1, [data-cb-font="elegant"] h2, [data-cb-font="elegant"] h3,
    [data-cb-font="elegant"] .cb-display { font-family: "Fraunces", "Geist", serif !important; }
  `,
  audace: `
    [data-cb-font="audace"] h1, [data-cb-font="audace"] h2, [data-cb-font="audace"] h3,
    [data-cb-font="audace"] .cb-display { font-family: var(--ff-mono) !important; letter-spacing: -0.02em; }
  `,
  manuscrit: `
    [data-cb-font="manuscrit"] h1, [data-cb-font="manuscrit"] h2, [data-cb-font="manuscrit"] h3,
    [data-cb-font="manuscrit"] .cb-display { font-family: "Caveat", "Brush Script MT", cursive !important; letter-spacing: 0.005em; }
  `,
  editorial: `
    [data-cb-font="editorial"] h1, [data-cb-font="editorial"] h2, [data-cb-font="editorial"] h3,
    [data-cb-font="editorial"] .cb-display { font-family: "Playfair Display", "Fraunces", "Georgia", serif !important; }
  `,
  minimal: `
    [data-cb-font="minimal"] h1, [data-cb-font="minimal"] h2, [data-cb-font="minimal"] h3,
    [data-cb-font="minimal"] .cb-display { font-family: "Space Grotesk", "Geist", sans-serif !important; letter-spacing: -0.015em; }
  `,
  boutique: `
    [data-cb-font="boutique"] h1, [data-cb-font="boutique"] h2, [data-cb-font="boutique"] h3,
    [data-cb-font="boutique"] .cb-display { font-family: "Cormorant Garamond", "Fraunces", serif !important; font-weight: 500; }
  `,
  techno: `
    [data-cb-font="techno"] h1, [data-cb-font="techno"] h2, [data-cb-font="techno"] h3,
    [data-cb-font="techno"] .cb-display { font-family: "JetBrains Mono", "Geist Mono", monospace !important; letter-spacing: -0.02em; }
  `,
};
// Couleur des boutons : id → couleur unie (ou null = couleur du thème)
const BOOKING_BUTTON_COLORS = {
  auto:   null,
  indigo: "oklch(55% 0.22 278)",
  bleu:   "oklch(55% 0.17 245)",
  teal:   "oklch(58% 0.12 195)",
  vert:   "oklch(58% 0.15 150)",
  jaune:  "oklch(78% 0.15 90)",
  orange: "oklch(64% 0.18 40)",
  rouge:  "oklch(57% 0.21 25)",
  rose:   "oklch(62% 0.17 350)",
  violet: "oklch(55% 0.20 305)",
  noir:   "oklch(25% 0.01 280)",
  blanc:  "#ffffff",
};
// Palette de fonds neutres, applicable par-dessus le thème pour donner une
// touche personnelle sans changer la couleur d'accent.
// Synchronisé avec PAGE_BACKGROUNDS de pages-page-resa.jsx.
const BOOKING_BG_MAP = {
  auto:  null,
  soft:  { bg: "oklch(98% 0.01 270)" },
  cream: { bg: "oklch(97% 0.02 80)" },
  sage:  { bg: "oklch(96% 0.02 160)" },
  blush: { bg: "oklch(96% 0.025 20)" },
  dark:  { bg: "oklch(22% 0.01 270)", ink: "#fafaf7", inkDim: "rgba(250,250,247,0.72)",
           surface: "oklch(26% 0.01 270)", line: "rgba(255,255,255,0.08)" },
};
const BookingThemeStyles = ({ theme, font, buttonColor, background }) => {
  // Override couleur des boutons primaires si l'utilisateur a choisi autre chose qu'"auto"
  const btn = BOOKING_BUTTON_COLORS[buttonColor];
  const btnCss = (buttonColor && buttonColor !== "auto" && btn) ? `
    .cb-booking .btn-primary,
    .cb-booking .btn-accent {
      background: ${btn} !important;
      color: ${buttonColor === "blanc" || buttonColor === "jaune" ? "#1a1a1a" : "#fff"} !important;
      ${buttonColor === "blanc" ? "border: 1px solid var(--line-strong) !important;" : ""}
    }
  ` : "";
  // Override du fond, n'écrase que --bg pour rester compatible avec les
  // couleurs d'accent / boutons / surface du thème actif.
  const bgCfg = background && background !== "auto" ? BOOKING_BG_MAP[background] : null;
  const bgCss = bgCfg ? `
    .cb-booking { --bg: ${bgCfg.bg} !important; }
    .cb-booking [data-cb-theme] { background: ${bgCfg.bg} !important; }
    ${bgCfg.ink ? `
      .cb-booking { --ink: ${bgCfg.ink} !important; --ink-2: ${bgCfg.inkDim} !important; }
      .cb-booking { --surface: ${bgCfg.surface} !important; --line: ${bgCfg.line} !important; }
    ` : ""}
  ` : "";
  return (
    <style>{`
      /* Anti-zoom iOS : tous les champs >=16px */
      .cb-booking input, .cb-booking textarea, .cb-booking select { font-size: 16px !important; }
      @media (max-width: 480px) {
        .cb-book-name-row { grid-template-columns: 1fr !important; }
      }
      ${BOOKING_THEME_CSS[theme] || ""}
      ${BOOKING_FONT_CSS[font] || ""}
      ${btnCss}
      ${bgCss}
    `}</style>
  );
};

/* ============ Header / Footer ============ */
const BookingHeader = ({ pro, pageCustom }) => {
  const pc = pageCustom || {};
  const hasBanner = !!pc.banner;
  const initials = (pro.ownerName || pro.businessName || "CB")
    .split(" ").map(x => x[0]).slice(0, 2).join("").toUpperCase();
  const hue = pro.hue || 30;
  // Bande colorée qui passe DERRIÈRE l'avatar (pas un full banner).
  // Hauteur 96px → l'avatar 80px chevauche, on voit la bande sortir sur
  // les côtés et en haut. Sobre, donne une touche de couleur sans
  // dominer. Si l'utilisateur a uploadé un banner image, on utilise
  // celui-ci à la place du gradient.
  const stripStyle = hasBanner
    ? {
        backgroundImage: `url(${pc.banner})`,
        backgroundSize: "cover",
        backgroundPosition: "center",
      }
    : {
        background: `linear-gradient(135deg, oklch(88% 0.08 ${hue}) 0%, oklch(85% 0.10 ${(hue+40)%360}) 100%)`,
      };
  return (
  <header style={{
    padding: 0,
    // borderBottom retiré, pas de trait entre le header et le contenu,
    // l'enchaînement visuel est plus fluide.
    background: "var(--surface)",
    position: "relative",
  }}>
    {/* Bande colorée derrière l'avatar, 96px de haut */}
    <div aria-hidden style={{
      ...stripStyle,
      height: 96,
      position: "relative",
    }}/>

    {/* Avatar + nom centrés, l'avatar chevauche la bande */}
    <div style={{
      padding: "0 20px 22px",
      textAlign: "center",
      marginTop: -44,
      position: "relative",
    }}>
      {/* Avatar avec bordure blanche pour bien le détacher de la bande */}
      {pro.avatar ? (
        <img src={pro.avatar} alt={pro.ownerName || ""} style={{
          width: 80, height: 80,
          borderRadius: pc.avatarShape === "square" ? 20 : "50%",
          objectFit: "cover",
          margin: "0 auto",
          display: "block",
          border: "4px solid var(--surface)",
          boxShadow: "0 6px 16px -8px rgba(15,18,30,0.18)",
        }}/>
      ) : (
        <div style={{
          width: 80, height: 80,
          borderRadius: pc.avatarShape === "square" ? 20 : "50%",
          background: `linear-gradient(135deg, oklch(72% 0.16 ${hue}), oklch(60% 0.20 ${(hue+40)%360}))`,
          color: "white",
          display: "flex", alignItems: "center", justifyContent: "center",
          fontWeight: 600, fontSize: 28, fontFamily: "var(--ff-display)",
          margin: "0 auto",
          border: "4px solid var(--surface)",
          boxShadow: "0 6px 16px -8px rgba(15,18,30,0.18)",
        }}>
          {initials}
        </div>
      )}

      {/* Eyebrow : métier (si renseigné), au-dessus du nom, petit et discret */}
      {pc.profession && (
        <div style={{
          marginTop: 12,
          fontSize: 11, fontWeight: 600,
          color: "var(--accent-ink)",
          textTransform: "uppercase", letterSpacing: "0.08em",
        }}>
          {pc.profession}
        </div>
      )}

      {/* Nom + badge vérifié, centré, lisible. */}
      <div style={{
        marginTop: pc.profession ? 4 : 14,
        fontFamily: "var(--ff-display)", fontSize: 22, fontWeight: 600,
        letterSpacing: "-0.025em", lineHeight: 1.2,
        display: "inline-flex", alignItems: "center", gap: 7,
        justifyContent: "center",
        flexWrap: "wrap", maxWidth: "100%",
      }}>
        <span style={{
          overflow: "hidden", textOverflow: "ellipsis",
          maxWidth: "100%",
        }}>
          {pro.businessName}
        </span>
        {pro.emailVerified && pc.showVerifiedBadge !== false && (
          <svg width={18} height={18} viewBox="0 0 24 24" style={{ flexShrink: 0 }}
               role="img" aria-label="Compte vérifié par ClientBase">
            <title>Compte vérifié par ClientBase</title>
            {/* Couleur FORCÉE à l'indigo ClientBase (#5A50E6), pas affectée
                par le thème custom du pro. Le badge garde l'identité visuelle
                de la plateforme partout. */}
            <path fill="#5A50E6" d="M22.25 12L20.86 10.41L21.43 8.36L19.39 7.74L18.95 5.66L16.83 5.5L15.69 3.71L13.66 4.47L12 3.16L10.34 4.47L8.31 3.71L7.17 5.5L5.05 5.66L4.61 7.74L2.57 8.36L3.14 10.41L1.75 12L3.14 13.59L2.57 15.64L4.61 16.26L5.05 18.34L7.17 18.5L8.31 20.29L10.34 19.53L12 20.84L13.66 19.53L15.69 20.29L16.83 18.5L18.95 18.34L19.39 16.26L21.43 15.64L20.86 13.59L22.25 12Z"/>
            <path d="M8.5 12L11 14.5L15.5 10" stroke="white" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" fill="none"/>
          </svg>
        )}
      </div>

      {/* Tagline (si renseignée) */}
      {pc.tagline && (
        <div style={{
          fontSize: 13.5, color: "var(--ink-3)",
          marginTop: 4, fontWeight: 500,
        }}>
          {pc.tagline}
        </div>
      )}
      {/* Ville avec une petite épingle 📍, toujours sous le nom dès que
          le pro l'a renseignée (séparée de la tagline pour pas mélanger). */}
      {pc.locationLabel && (
        <div style={{
          fontSize: 12.5, color: "var(--ink-3)",
          marginTop: pc.tagline ? 2 : 4, fontWeight: 500,
          display: "inline-flex", alignItems: "center", gap: 4,
        }}>
          <svg width="11" height="13" viewBox="0 0 24 24" fill="none"
               stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"
               style={{ flexShrink: 0, color: "var(--accent-ink)" }}>
            <path d="M12 22s-7-5-7-12a7 7 0 0 1 14 0c0 7-7 12-7 12z"/>
            <circle cx="12" cy="10" r="2.5"/>
          </svg>
          {pc.locationLabel}
        </div>
      )}
      {/* Fallback si ni tagline ni ville : juste un petit texte par défaut */}
      {!pc.tagline && !pc.locationLabel && (
        <div style={{
          fontSize: 13.5, color: "var(--ink-3)",
          marginTop: 4, fontWeight: 500,
        }}>
          Prise de rendez-vous en ligne
        </div>
      )}
    </div>

    {/* Header, bouton "Appeler" uniquement (action urgente).
        WhatsApp + Insta + Website sont déplacés dans le bloc "Suivez-moi"
        plus bas dans la VitrineView pour éviter les doublons. */}
    {pc.showCall && pro.phone && (
      <div style={{ padding: "0 16px 16px", display: "flex", justifyContent: "center" }}>
        <a href={`tel:${String(pro.phone).replace(/\s/g, "")}`}
           style={{
             display: "inline-flex", alignItems: "center", gap: 6,
             padding: "8px 16px",
             background: "var(--accent-soft)", color: "var(--accent-ink)",
             border: "1px solid var(--accent-soft-2)",
             borderRadius: 999, fontSize: 12.5, fontWeight: 580,
             textDecoration: "none",
           }}>
          📞 Appeler
        </a>
      </div>
    )}
  </header>
  );
};

/* === Bloc avis publics + badge "Vérifié par ClientBase" === */
const BookingReviews = ({ reviews }) => {
  const visible = (reviews || []).filter(r => r.visible !== false);
  if (visible.length === 0) return null;

  const avg = visible.reduce((s, r) => s + (r.rating || 0), 0) / visible.length;
  const counts = [5, 4, 3, 2, 1].map(n => ({
    n, count: visible.filter(r => r.rating === n).length,
  }));
  const [showAll, setShowAll] = React.useState(false);
  const displayed = showAll ? visible : visible.slice(0, 3);
  const [tooltipOpen, setTooltipOpen] = React.useState(false);

  return (
    <section style={{
      maxWidth: 720, margin: "40px auto 0", padding: "0 16px",
    }}>
      {/* Header avec note moyenne + badge */}
      <div style={{
        background: "var(--surface)", border: "1px solid var(--line)",
        borderRadius: 16, padding: "20px 22px", marginBottom: 14,
        boxShadow: "var(--sh-1)",
      }}>
        <div style={{
          display: "flex", justifyContent: "space-between", alignItems: "flex-start",
          gap: 14, flexWrap: "wrap",
        }}>
          <div>
            <div style={{
              fontFamily: "var(--ff-mono)", fontSize: 10.5, color: "var(--ink-4)",
              textTransform: "uppercase", letterSpacing: "0.06em", fontWeight: 540,
              marginBottom: 4,
            }}>
              Avis clientes
            </div>
            <div style={{ display: "flex", alignItems: "baseline", gap: 8 }}>
              <span style={{
                fontFamily: "var(--ff-display)", fontSize: 40, fontWeight: 580,
                color: "var(--ink)", letterSpacing: "-0.03em", lineHeight: 1,
              }}>
                {avg.toFixed(1)}
              </span>
              <span style={{
                display: "inline-flex", fontSize: 18, color: "oklch(70% 0.16 80)",
                letterSpacing: "0.05em",
              }}>
                {[1,2,3,4,5].map(i => (
                  <span key={i} style={{ opacity: i <= Math.round(avg) ? 1 : 0.3 }}>★</span>
                ))}
              </span>
            </div>
            <div style={{ fontSize: 12.5, color: "var(--ink-3)", marginTop: 4 }}>
              {visible.length} avis vérifié{visible.length > 1 ? "s" : ""}
            </div>
          </div>

          {/* Badge "Vérifié par ClientBase" */}
          <div style={{ position: "relative" }}>
            <button
              onClick={() => setTooltipOpen(v => !v)}
              onMouseEnter={() => setTooltipOpen(true)}
              onMouseLeave={() => setTooltipOpen(false)}
              style={{
                display: "inline-flex", alignItems: "center", gap: 7,
                padding: "7px 12px",
                background: "linear-gradient(135deg, var(--accent-soft), oklch(96% 0.04 200))",
                border: "1px solid var(--accent-soft-2)",
                borderRadius: 999, cursor: "help",
                fontSize: 12, fontWeight: 540, color: "var(--accent-ink)",
                fontFamily: "inherit",
              }}>
              <svg width="14" height="14" viewBox="0 0 24 24" style={{ flexShrink: 0 }}>
                <path fill="var(--accent)" d="M22.25 12L20.86 10.41L21.43 8.36L19.39 7.74L18.95 5.66L16.83 5.5L15.69 3.71L13.66 4.47L12 3.16L10.34 4.47L8.31 3.71L7.17 5.5L5.05 5.66L4.61 7.74L2.57 8.36L3.14 10.41L1.75 12L3.14 13.59L2.57 15.64L4.61 16.26L5.05 18.34L7.17 18.5L8.31 20.29L10.34 19.53L12 20.84L13.66 19.53L15.69 20.29L16.83 18.5L18.95 18.34L19.39 16.26L21.43 15.64L20.86 13.59L22.25 12Z"/>
                <path d="M8.5 12L11 14.5L15.5 10" stroke="white" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" fill="none"/>
              </svg>
              Vérifié par ClientBase
            </button>
            {tooltipOpen && (
              <div style={{
                position: "absolute", top: "calc(100% + 8px)", right: 0,
                width: 280, padding: "12px 14px",
                background: "var(--ink)", color: "var(--bg)",
                borderRadius: 11, fontSize: 12, lineHeight: 1.5,
                boxShadow: "0 16px 30px -8px rgba(0,0,0,0.3)",
                zIndex: 10,
              }}>
                <strong style={{ fontWeight: 580 }}>Anti-fraude.</strong> Chaque avis est lié à un vrai RDV terminé. Impossible d'en publier sans avoir été cliente. Le pro ne peut ni les inventer, ni les supprimer après publication.
              </div>
            )}
          </div>
        </div>

        {/* Barre de répartition */}
        <div style={{ marginTop: 16, display: "flex", flexDirection: "column", gap: 4 }}>
          {counts.map(c => {
            const pct = visible.length > 0 ? (c.count / visible.length) * 100 : 0;
            return (
              <div key={c.n} style={{ display: "flex", alignItems: "center", gap: 8, fontSize: 11.5 }}>
                <span style={{
                  width: 24, color: "var(--ink-3)",
                  display: "inline-flex", alignItems: "center", gap: 2,
                }}>{c.n}<span style={{ color: "oklch(70% 0.16 80)", fontSize: 10 }}>★</span></span>
                <div style={{ flex: 1, height: 5, background: "var(--bg-alt)", borderRadius: 999, overflow: "hidden" }}>
                  <div style={{
                    height: "100%", width: `${pct}%`,
                    background: "oklch(70% 0.16 80)",
                    transition: "width .5s cubic-bezier(.22,1,.36,1)",
                  }}/>
                </div>
                <span style={{ width: 22, textAlign: "right", color: "var(--ink-4)", fontFamily: "var(--ff-mono)", fontSize: 11 }}>{c.count}</span>
              </div>
            );
          })}
        </div>
      </div>

      {/* Liste des avis */}
      <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
        {displayed.map(r => (
          <BookingReviewCard key={r.id} review={r}/>
        ))}
      </div>

      {visible.length > 3 && (
        <button onClick={() => setShowAll(v => !v)} style={{
          marginTop: 14, width: "100%", padding: "11px 14px",
          background: "transparent", border: "1px solid var(--line)",
          borderRadius: 11, fontSize: 13, fontWeight: 540, color: "var(--ink-2)",
          cursor: "pointer", fontFamily: "inherit",
        }}>
          {showAll ? "Réduire" : `Voir les ${visible.length - 3} autres avis`}
        </button>
      )}
    </section>
  );
};

const BookingReviewCard = ({ review }) => {
  const r = review;
  return (
    <div style={{
      padding: "14px 16px",
      background: "var(--surface)", border: "1px solid var(--line)",
      borderRadius: 13,
    }}>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 10 }}>
        <div>
          <div style={{ fontSize: 13.5, fontWeight: 540, color: "var(--ink)" }}>
            {r.clientName}
            {r.verified && (
              <span title="Avis vérifié, lié à un vrai RDV"
                style={{
                  display: "inline-flex", alignItems: "center", gap: 3,
                  marginLeft: 7, padding: "1px 7px",
                  background: "var(--sage-soft)", color: "oklch(40% 0.10 160)",
                  borderRadius: 999, fontSize: 10, fontWeight: 580,
                  fontFamily: "var(--ff-mono)", textTransform: "uppercase", letterSpacing: "0.04em",
                  verticalAlign: "middle",
                }}>
                ✓ vérifié
              </span>
            )}
          </div>
          <div style={{ fontSize: 11.5, color: "var(--ink-4)", marginTop: 1 }}>
            {r.serviceName} · {_formatReviewDate(r.date)}
          </div>
        </div>
        <div style={{ display: "inline-flex", fontSize: 15, color: "oklch(70% 0.16 80)", letterSpacing: "0.04em", flexShrink: 0 }}>
          {[1,2,3,4,5].map(i => (
            <span key={i} style={{ opacity: i <= r.rating ? 1 : 0.3 }}>★</span>
          ))}
        </div>
      </div>
      {r.comment && (
        <p style={{
          margin: "8px 0 0", fontSize: 13.5, color: "var(--ink-2)",
          lineHeight: 1.55,
        }}>{r.comment}</p>
      )}
      {r.reply && (
        <div style={{
          marginTop: 10, padding: "10px 12px",
          background: "var(--bg-alt)", borderRadius: 10,
          borderLeft: "3px solid var(--accent)",
          fontSize: 12.5, color: "var(--ink-2)", lineHeight: 1.5,
        }}>
          <div style={{ fontSize: 10.5, color: "var(--ink-4)", fontWeight: 540, marginBottom: 3, textTransform: "uppercase", letterSpacing: "0.04em" }}>
            Réponse du pro
          </div>
          {r.reply}
        </div>
      )}
    </div>
  );
};

const _formatReviewDate = (ymd) => {
  if (!ymd) return "";
  try {
    const d = new Date(ymd + "T00:00:00");
    const m = ["janv.","févr.","mars","avril","mai","juin","juil.","août","sept.","oct.","nov.","déc."][d.getMonth()];
    return `${d.getDate()} ${m} ${d.getFullYear()}`;
  } catch { return ymd; }
};

/* === Section "À propos", paragraphe de présentation du salon / de l'activité.
   Le texte est saisi par le pro dans l'éditeur Ma page (champ `presentation`).
   On préserve les retours à la ligne (whiteSpace: pre-line). === */
const BookingPresentation = ({ text, title }) => {
  if (!text || !String(text).trim()) return null;
  return (
    <section style={{
      maxWidth: 720, margin: "40px auto 0", padding: "0 16px",
    }}>
      <div style={{
        background: "var(--surface)", border: "1px solid var(--line)",
        borderRadius: 16, padding: "22px 24px",
        boxShadow: "var(--sh-1)",
      }}>
        <div style={{
          fontFamily: "var(--ff-mono)", fontSize: 10.5, color: "var(--ink-4)",
          textTransform: "uppercase", letterSpacing: "0.06em", fontWeight: 540,
          marginBottom: 8,
        }}>
          {title || "À propos"}
        </div>
        <p style={{
          margin: 0, fontSize: 14.5, color: "var(--ink-2)",
          lineHeight: 1.65, whiteSpace: "pre-line",
        }}>
          {text}
        </p>
      </div>
    </section>
  );
};

/* === Galerie publique : titre éditable + captions par photo ===
   Accepte les deux formats pour rétro-compat :
   - Ancien : ["dataUrl1", "dataUrl2"]
   - Nouveau : [{src: "dataUrl1", caption: "Pose gel"}, ...]
   Le titre vient de pageCustom.galleryTitle (défaut "Mes réalisations").
   Police : var(--ff-display) (Geist), cohérent avec le reste de la page. */
const BookingGallery = ({ photos, title, embedded }) => {
  const [lightbox, setLightbox] = React.useState(null);
  if (!photos || !photos.length) return null;
  const items = photos.map(it => typeof it === "string" ? { src: it, caption: "" } : it).filter(it => it && it.src);
  if (!items.length) return null;
  const cols = items.length === 1 ? 1 : items.length === 2 ? 2 : items.length === 3 ? 3 : 2;
  return (
    <>
      <div style={{
        maxWidth: 720, margin: embedded ? 0 : "40px auto 0",
        padding: embedded ? 0 : "0 16px",
      }}>
        {/* Titre uniquement si fourni (pour ne pas doublonner avec
            SectionTitle quand le composant est embedded dans VitrineView) */}
        {title && (
          <h3 style={{
            fontFamily: "var(--ff-display)",
            fontSize: 19, fontWeight: 600,
            letterSpacing: "-0.02em",
            margin: "0 0 14px",
            color: "var(--ink)",
          }}>
            {title}
          </h3>
        )}
        <div style={{
          display: "grid",
          gridTemplateColumns: `repeat(${cols}, 1fr)`,
          gap: 8,
        }}>
          {items.slice(0, 4).map((item, i) => (
            <button key={i}
              onClick={() => setLightbox(item)}
              style={{
                aspectRatio: "1 / 1", borderRadius: 14,
                background: `url(${item.src}) center/cover`,
                border: "1px solid var(--line)",
                cursor: "pointer", padding: 0,
                position: "relative", overflow: "hidden",
                transition: "transform .2s cubic-bezier(.32,.72,0,1)",
              }}
              onMouseEnter={e => { e.currentTarget.style.transform = "scale(1.02)"; }}
              onMouseLeave={e => { e.currentTarget.style.transform = "scale(1)"; }}>
              {item.caption && (
                <div style={{
                  position: "absolute", left: 0, right: 0, bottom: 0,
                  padding: "20px 10px 8px",
                  background: "linear-gradient(180deg, transparent 0%, rgba(0,0,0,0.7) 100%)",
                  color: "white", fontSize: 11.5, fontWeight: 540,
                  fontFamily: "var(--ff-text)",
                  textAlign: "left", lineHeight: 1.3,
                  display: "-webkit-box", WebkitLineClamp: 2, WebkitBoxOrient: "vertical",
                  overflow: "hidden",
                }}>
                  {item.caption}
                </div>
              )}
            </button>
          ))}
        </div>
      </div>
      {lightbox && (
        <div onClick={() => setLightbox(null)}
          style={{
            position: "fixed", inset: 0, background: "rgba(0,0,0,0.88)",
            display: "flex", flexDirection: "column",
            alignItems: "center", justifyContent: "center",
            zIndex: 9999, padding: 24, cursor: "zoom-out",
            backdropFilter: "blur(8px)",
          }}>
          <img src={lightbox.src} alt={lightbox.caption || ""} style={{
            maxWidth: "100%", maxHeight: "82%", borderRadius: 16,
            boxShadow: "0 40px 80px -20px rgba(0,0,0,0.6)",
          }}/>
          {lightbox.caption && (
            <div style={{
              marginTop: 18, color: "white", fontSize: 15,
              fontFamily: "var(--ff-text)", textAlign: "center",
              maxWidth: 480,
            }}>
              {lightbox.caption}
            </div>
          )}
        </div>
      )}
    </>
  );
};

const BookingFooter = ({ pro }) => (
  <footer style={{
    padding: "20px 16px",
    borderTop: "1px solid var(--line)",
    background: "var(--bg-section)",
    fontSize: 12, color: "var(--ink-4)",
    textAlign: "center",
  }}>
    Propulsé par <a href="/" style={{ color: "var(--accent-ink)", fontWeight: 520 }}>ClientBase</a> · vos données sont traitées conformément au RGPD.
  </footer>
);

/* ============ Stepper indicator ============ */
const BookingStepper = ({ step }) => {
  const steps = ["Prestation", "Créneau", "Vos infos"];
  return (
    <div style={{
      display: "flex", gap: 6, alignItems: "center", justifyContent: "center",
      marginBottom: 24, fontSize: 12.5,
    }}>
      {steps.map((label, i) => {
        const n = i + 1;
        const done = step > n;
        const active = step === n;
        return (
          <React.Fragment key={n}>
            <div style={{
              display: "inline-flex", alignItems: "center", gap: 6,
              padding: "4px 10px 4px 6px",
              borderRadius: 999,
              background: active ? "var(--accent-soft)" : "transparent",
              color: active ? "var(--accent-ink)" : done ? "var(--sage)" : "var(--ink-4)",
              fontWeight: active ? 600 : 500,
            }}>
              <span style={{
                width: 20, height: 20, borderRadius: 50,
                background: active ? "var(--accent)" : done ? "var(--sage)" : "var(--line-strong)",
                color: "white",
                display: "flex", alignItems: "center", justifyContent: "center",
                fontSize: 11, fontWeight: 600,
              }}>
                {done ? <Icon name="check" size={10} stroke={3}/> : n}
              </span>
              <span style={{ display: "none" }} className="cb-show-sm">{label}</span>
              <span className="cb-hide-sm">{label}</span>
            </div>
            {i < steps.length - 1 && (
              <span aria-hidden style={{
                width: 14, height: 1,
                background: step > n ? "var(--sage)" : "var(--line-strong)",
              }}/>
            )}
          </React.Fragment>
        );
      })}
    </div>
  );
};

/* ============ Step 1, Choose service ============ */
const StepService = ({ pro, services, categories, serviceId, setServiceId, onNext }) => {
  // Groupe les services par catégorie, dans l'ordre choisi par le pro
  const sortedCategories = (categories || []).slice().sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
  const groups = (() => {
    const byCat = {};
    sortedCategories.forEach(c => { byCat[c.id] = { cat: c, items: [] }; });
    byCat["__none__"] = { cat: null, items: [] };
    services.forEach(s => {
      const k = s.categoryId && byCat[s.categoryId] ? s.categoryId : "__none__";
      byCat[k].items.push(s);
    });
    Object.values(byCat).forEach(g => g.items.sort((a, b) => (a.order ?? 0) - (b.order ?? 0)));
    const out = sortedCategories.map(c => byCat[c.id]).filter(g => g.items.length > 0);
    if (byCat["__none__"].items.length > 0) out.push(byCat["__none__"]);
    return out;
  })();
  const hasMultipleGroups = groups.length > 1;

  return (
    <div>
      <h1 style={{
        fontSize: "clamp(26px, 4vw, 36px)",
        letterSpacing: "-0.03em", marginBottom: 6,
      }}>
        Prenez rendez-vous avec <span style={{ color: "var(--accent)" }}>{pro.businessName}</span>.
      </h1>
      <p style={{ fontSize: 15, color: "var(--ink-3)", marginBottom: 24 }}>
        Choisissez la prestation que vous souhaitez.
      </p>

      <div style={{ display: "flex", flexDirection: "column", gap: 18 }}>
        {groups.map((g, gi) => (
          <div key={g.cat ? g.cat.id : "__none__"}>
            {hasMultipleGroups && (
              <div style={{
                display: "flex", alignItems: "center", gap: 9,
                marginBottom: 10, paddingBottom: 6,
                borderBottom: "1px solid var(--line)",
              }}>
                <span style={{
                  width: 4, height: 14, borderRadius: 2,
                  background: g.cat ? "var(--accent)" : "var(--ink-4)",
                  flexShrink: 0,
                }}/>
                <span style={{
                  fontFamily: "var(--ff-display)", fontSize: 15, fontWeight: 580,
                  color: g.cat ? "var(--ink)" : "var(--ink-3)",
                  letterSpacing: "-0.014em",
                  fontStyle: g.cat ? "normal" : "italic",
                }}>
                  {g.cat ? g.cat.name : "Autres prestations"}
                </span>
              </div>
            )}
            <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
              {g.items.map(s => {
                const active = s.id === serviceId;
                return (
                  <button key={s.id} onClick={() => setServiceId(s.id)} style={{
                    padding: "14px 16px",
                    background: active ? "var(--accent-soft)" : "var(--surface)",
                    border: "1px solid " + (active ? "var(--accent)" : "var(--line)"),
                    borderRadius: 12, cursor: "pointer", fontFamily: "inherit",
                    textAlign: "left", display: "flex", gap: 12, alignItems: "center",
                    width: "100%",
                  }}>
                    <div style={{
                      width: 20, height: 20, borderRadius: 50,
                      border: `2px solid ${active ? "var(--accent)" : "var(--line-strong)"}`,
                      background: active ? "var(--accent)" : "transparent",
                      display: "flex", alignItems: "center", justifyContent: "center",
                      flexShrink: 0,
                    }}>
                      {active && <span style={{ width: 6, height: 6, borderRadius: 50, background: "white" }}/>}
                    </div>
                    <div style={{ flex: 1, minWidth: 0 }}>
                      <div style={{ fontWeight: 520, fontSize: 15, color: "var(--ink)" }}>{s.name}</div>
                      <div style={{ fontSize: 13, color: "var(--ink-4)", marginTop: 2 }}>
                        Durée {s.duration >= 1 ? `${Math.floor(s.duration)}h${s.duration % 1 ? (s.duration % 1) * 60 : ""}` : `${s.duration * 60} min`}
                      </div>
                    </div>
                    <div style={{
                      fontFamily: "var(--ff-display)", fontSize: 20, fontWeight: 580,
                      letterSpacing: "-0.02em", color: active ? "var(--accent-ink)" : "var(--ink)",
                    }}>
                      {s.price} €
                    </div>
                  </button>
                );
              })}
            </div>
          </div>
        ))}
      </div>

      <div style={{ marginTop: 24 }}>
        <button className="btn btn-accent btn-lg" disabled={!serviceId} onClick={onNext} style={{
          width: "100%", opacity: serviceId ? 1 : 0.5, cursor: serviceId ? "pointer" : "not-allowed",
        }}>
          Choisir un créneau <Icon name="arrow" size={15}/>
        </button>
      </div>
    </div>
  );
};

/* ============ Step 2, Choose date + time slot ============ */
const StepSlot = ({ service, data, bookingSettings, maxAdvanceDays, day, setDay, hour, setHour, onBack, onNext }) => {
  const bs = bookingSettings;
  // weekOffset = nombre de semaines (×7 jours) glissées par rapport à aujourd'hui.
  // Init depuis `day` si déjà sélectionné (revient à l'étape 2 depuis 3) pour
  // que la semaine affichée corresponde au jour choisi.
  const maxAdv = Math.max(1, Math.min(Number(maxAdvanceDays) || 60, 365));
  const maxWeekOffset = Math.max(0, Math.floor((maxAdv - 1) / 7));
  const [weekOffset, setWeekOffset] = React.useState(() =>
    day != null ? Math.min(Math.floor(day / 7), maxWeekOffset) : 0
  );

  // Un jour absolu (offset depuis ajd) est OUVERT si :
  //   - le pro a ses horaires ouverts ce jour (resolveDayHours)
  //   - pas en vacances
  //   - dans la fenêtre maxAdvanceDays
  const openDayAbs = (abs) => {
    if (abs < 0 || abs >= maxAdv) return false;
    const h = resolveDayHours(bs, isoIdxOf(abs));
    if (!h.open) return false;
    if (isInVacation(bs, demoDateFor(abs))) return false;
    return true;
  };

  const selectedSlots = day != null
    ? generateAvailableSlots(day, data, service.id)
    : [];
  const dayVacation = day != null ? (bs.vacations || []).find(v => demoDateFor(day) >= v.start && demoDateFor(day) <= v.end) : null;
  const upcomingVacations = (bs.vacations || []).filter(v => v.end >= demoDateFor(0));

  // Premier et dernier offset visibles dans la semaine courante du picker
  const visibleStart = weekOffset * 7;
  const visibleEnd   = visibleStart + PICKER_DAYS - 1;
  const canPrev = weekOffset > 0;
  // Suivant possible tant que le 1er jour de la prochaine semaine reste dans la fenêtre
  const canNext = (weekOffset + 1) * 7 < maxAdv;
  const goPrev = () => {
    if (!canPrev) return;
    setWeekOffset(w => w - 1);
    setDay(null); setHour(null);
  };
  const goNext = () => {
    if (!canNext) return;
    setWeekOffset(w => w + 1);
    setDay(null); setHour(null);
  };

  return (
    <div>
      <h2 style={{ fontSize: "clamp(22px, 3vw, 28px)", letterSpacing: "-0.02em", marginBottom: 4 }}>
        Quand vous convient-il&nbsp;?
      </h2>
      <p style={{ fontSize: 14, color: "var(--ink-3)", marginBottom: 20 }}>
        Pour {service.name} · {service.duration >= 1 ? `${Math.floor(service.duration)}h${service.duration % 1 ? (service.duration % 1) * 60 : ""}` : `${service.duration * 60} min`}
      </p>

      {/* Vacation notice banner */}
      {upcomingVacations.length > 0 && (
        <div style={{
          padding: "12px 14px", marginBottom: 18,
          background: "var(--warn-soft)", border: "1px solid var(--warn-soft-2)",
          borderRadius: 10, fontSize: 12.5, color: "var(--warn-ink)", lineHeight: 1.5,
        }}>
          <div style={{ fontWeight: 600, marginBottom: 2 }}>À noter, périodes fermées</div>
          {upcomingVacations.slice(0, 3).map(v => (
            <div key={v.id} style={{ marginTop: 2 }}>
              • {v.reason}&nbsp;: du {new Date(v.start).toLocaleDateString("fr-FR")} au {new Date(v.end).toLocaleDateString("fr-FR")}
            </div>
          ))}
        </div>
      )}

      {/* Week navigation : chevrons gauche/droite + label de la semaine.
          La grille en dessous glisse par semaines (6 jours visibles). */}
      <div style={{
        display: "flex", alignItems: "center", justifyContent: "space-between",
        gap: 8, marginBottom: 10,
      }}>
        <button onClick={goPrev} disabled={!canPrev}
          aria-label="Semaine précédente"
          style={{
            width: 34, height: 34, padding: 0,
            background: canPrev ? "var(--surface)" : "var(--bg-alt)",
            border: "1px solid var(--line)", borderRadius: 10,
            cursor: canPrev ? "pointer" : "not-allowed",
            color: canPrev ? "var(--ink-2)" : "var(--ink-4)",
            opacity: canPrev ? 1 : 0.5,
            display: "inline-flex", alignItems: "center", justifyContent: "center",
            fontFamily: "inherit", fontSize: 16, lineHeight: 1,
          }}>
          ‹
        </button>
        <div style={{
          fontSize: 12.5, color: "var(--ink-3)", fontWeight: 540,
          fontFamily: "inherit", letterSpacing: "-0.005em",
        }}>
          {weekOffset === 0 ? "Cette semaine" : `Du ${dayDateOf(visibleStart)} ${dayMonthOf(visibleStart)} au ${dayDateOf(visibleEnd)} ${dayMonthOf(visibleEnd)}`}
        </div>
        <button onClick={goNext} disabled={!canNext}
          aria-label="Semaine suivante"
          title={!canNext ? `Réservations limitées à ${maxAdv} jours à l'avance` : ""}
          style={{
            width: 34, height: 34, padding: 0,
            background: canNext ? "var(--surface)" : "var(--bg-alt)",
            border: "1px solid var(--line)", borderRadius: 10,
            cursor: canNext ? "pointer" : "not-allowed",
            color: canNext ? "var(--ink-2)" : "var(--ink-4)",
            opacity: canNext ? 1 : 0.5,
            display: "inline-flex", alignItems: "center", justifyContent: "center",
            fontFamily: "inherit", fontSize: 16, lineHeight: 1,
          }}>
          ›
        </button>
      </div>

      {/* Day picker */}
      <div style={{
        display: "grid", gridTemplateColumns: "repeat(6, 1fr)", gap: 6,
        marginBottom: 18,
      }}>
        {Array.from({ length: PICKER_DAYS }, (_, i) => {
          const abs = visibleStart + i;
          const isOpen = openDayAbs(abs);
          const active = day === abs;
          return (
            <button key={abs} onClick={() => { if (isOpen) { setDay(abs); setHour(null); } }}
              disabled={!isOpen}
              style={{
                padding: "10px 8px",
                background: active ? "var(--accent)" : isOpen ? "var(--surface)" : "var(--bg-alt)",
                border: "1px solid " + (active ? "var(--accent)" : "var(--line)"),
                color: active ? "white" : isOpen ? "var(--ink-2)" : "var(--ink-4)",
                borderRadius: 10, cursor: isOpen ? "pointer" : "not-allowed",
                fontFamily: "inherit",
                display: "flex", flexDirection: "column", alignItems: "center",
                gap: 2, opacity: isOpen ? 1 : 0.5,
              }}>
              <span style={{ fontSize: 11, opacity: active ? 0.85 : 1 }}>{dayLabelOf(abs)}</span>
              <span style={{ fontFamily: "var(--ff-display)", fontSize: 18, fontWeight: 600, letterSpacing: "-0.02em" }}>
                {dayDateOf(abs)}
              </span>
            </button>
          );
        })}
      </div>

      {/* Time slots */}
      {day != null ? (
        dayVacation ? (
          <div style={{
            padding: 20, textAlign: "center",
            background: "var(--warn-soft)", border: "1px solid var(--warn-soft-2)",
            borderRadius: 10, color: "var(--warn-ink)", fontSize: 14,
          }}>
            Fermé ce jour, {dayVacation.reason}. Choisissez un autre jour.
          </div>
        ) : selectedSlots.length === 0 ? (
          /* selectedSlots vide = soit jour fermé, soit aucun créneau de la
             durée requise ne rentre dans les horaires (ex: presta 2h, mais
             pro ferme à 18h et heure courante 17h). */
          <div style={{
            padding: 20, textAlign: "center",
            background: "var(--bg-alt)", border: "1px dashed var(--line-strong)",
            borderRadius: 10, color: "var(--ink-3)", fontSize: 14,
          }}>
            Aucun créneau ce jour pour une prestation de {service.duration < 1 ? Math.round(service.duration*60)+" min" : (service.duration === 1 ? "1 h" : service.duration+" h")}.
            <br/><span style={{ fontSize: 12.5 }}>Essayez un autre jour.</span>
          </div>
        ) : selectedSlots.every(s => s.taken) ? (
          /* Tous les slots existent mais sont tous déjà pris : message
             explicite "complet" + bouton implicite (changer de jour). */
          <div style={{
            padding: 20, textAlign: "center",
            background: "var(--warn-soft)", border: "1px solid var(--warn-soft-2)",
            borderRadius: 10, color: "var(--warn-ink)", fontSize: 14, lineHeight: 1.5,
          }}>
            <strong>Complet ce jour-là.</strong>
            <br/><span style={{ fontSize: 12.5, color: "var(--ink-2)" }}>
              Tous les créneaux sont déjà réservés. Essayez un autre jour ci-dessus.
            </span>
          </div>
        ) : (
          <div>
            {(() => {
              const avail = selectedSlots.filter(s => !s.taken).length;
              const taken = selectedSlots.length - avail;
              return (
                <div style={{ fontSize: 12.5, color: "var(--ink-3)", marginBottom: 10 }}>
                  {avail} créneau{avail > 1 ? "x" : ""} libre{avail > 1 ? "s" : ""} {dayLabelOf(day)} {dayDateOf(day)}
                  {taken > 0 && (
                    <span style={{ color: "var(--ink-4)" }}> · {taken} déjà pris</span>
                  )}
                </div>
              );
            })()}
            <div style={{
              display: "grid",
              gridTemplateColumns: "repeat(auto-fill, minmax(76px, 1fr))",
              gap: 6,
            }}>
              {selectedSlots.map(({ h, taken }) => {
                const active = hour === h;
                if (taken) {
                  return (
                    <div key={h} title="Créneau déjà pris" style={{
                      padding: "10px 8px",
                      background: "var(--bg-alt)",
                      color: "var(--ink-4)",
                      border: "1px solid var(--line)",
                      borderRadius: 8,
                      fontFamily: "inherit", fontSize: 13, fontWeight: 520,
                      textAlign: "center",
                      textDecoration: "line-through",
                      textDecorationColor: "var(--ink-4)",
                      textDecorationThickness: "1.5px",
                      cursor: "not-allowed", userSelect: "none",
                      opacity: 0.7,
                    }}>{fmtHourBooking(h)}</div>
                  );
                }
                return (
                  <button key={h} onClick={() => setHour(h)} style={{
                    padding: "10px 8px",
                    background: active ? "var(--accent)" : "var(--surface)",
                    color: active ? "white" : "var(--ink)",
                    border: "1px solid " + (active ? "var(--accent)" : "var(--line)"),
                    borderRadius: 8, cursor: "pointer",
                    fontFamily: "inherit", fontSize: 13, fontWeight: 520,
                  }}>{fmtHourBooking(h)}</button>
                );
              })}
            </div>
          </div>
        )
      ) : (
        <div style={{ padding: 20, textAlign: "center", color: "var(--ink-4)", fontSize: 14 }}>
          Choisissez un jour pour voir les créneaux disponibles.
        </div>
      )}

      <div style={{ marginTop: 28, display: "flex", gap: 10 }}>
        <button className="btn btn-ghost btn-lg" onClick={onBack} style={{ flex: "0 0 auto" }}>
          ← Retour
        </button>
        <button className="btn btn-accent btn-lg" disabled={day == null || hour == null}
          onClick={onNext}
          style={{
            flex: 1, opacity: (day != null && hour != null) ? 1 : 0.5,
            cursor: (day != null && hour != null) ? "pointer" : "not-allowed",
          }}>
          Continuer <Icon name="arrow" size={15}/>
        </button>
      </div>
    </div>
  );
};

/* ============ Cancellation notice, pro contact ============ */
const CancellationNotice = ({ pro, data }) => {
  const phone = data.business && data.business.phone ? data.business.phone : null;
  const email = pro.email;
  return (
    <div style={{
      padding: "16px 18px", marginBottom: 16,
      background: "var(--surface)", border: "1px solid var(--line)",
      borderRadius: 14, boxShadow: "0 1px 2px rgba(15,18,30,0.03)",
      display: "flex", gap: 14, alignItems: "flex-start",
    }}>
      <div style={{
        width: 36, height: 36, borderRadius: 10, flexShrink: 0,
        background: "var(--accent-soft)", color: "var(--accent-ink)",
        display: "flex", alignItems: "center", justifyContent: "center",
      }}>
        <Icon name="calendar" size={17}/>
      </div>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ fontSize: 14, fontWeight: 580, color: "var(--ink)", lineHeight: 1.35 }}>
          Annuler ou modifier votre RDV
        </div>
        <div style={{ fontSize: 13, color: "var(--ink-3)", marginTop: 4, lineHeight: 1.5 }}>
          Contactez <strong style={{ color: "var(--ink-2)" }}>{pro.businessName}</strong> directement&nbsp;:
        </div>
        <div style={{ marginTop: 10, display: "flex", flexWrap: "wrap", gap: 6 }}>
          {phone && (
            <a href={`tel:${phone.replace(/\s+/g, "")}`} style={{
              display: "inline-flex", alignItems: "center", gap: 6,
              padding: "7px 12px",
              background: "var(--bg-alt)", border: "1px solid var(--line)",
              borderRadius: 999, fontSize: 13, fontWeight: 520,
              color: "var(--ink)", textDecoration: "none",
            }}>
              <Icon name="phone" size={13} style={{ color: "var(--accent-ink)" }}/> {phone}
            </a>
          )}
          {email && (
            <a href={`mailto:${email}`} style={{
              display: "inline-flex", alignItems: "center", gap: 6,
              padding: "7px 12px",
              background: "var(--bg-alt)", border: "1px solid var(--line)",
              borderRadius: 999, fontSize: 13, fontWeight: 520,
              color: "var(--ink)", textDecoration: "none",
              maxWidth: "100%", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", minWidth: 0,
            }}>
              <Icon name="mail" size={13} style={{ color: "var(--accent-ink)", flexShrink: 0 }}/>
              <span style={{ overflow: "hidden", textOverflow: "ellipsis" }}>{email}</span>
            </a>
          )}
        </div>
      </div>
    </div>
  );
};

const _SOCIAL_BOOK_LABEL = {
  instagram: "Instagram",
  facebook:  "Facebook",
  tiktok:    "TikTok",
  snapchat:  "Snapchat",
  whatsapp:  "WhatsApp",
};
const _SOCIAL_BOOK_PLACEHOLDER = {
  instagram: "@votrepseudo",
  facebook:  "Votre nom Facebook",
  tiktok:    "@votrepseudo",
  snapchat:  "votrepseudo",
  whatsapp:  "06 12 34 56 78",
};

/* ============ Step 3, Account choice (compte / invité) ============
   Affichée APRÈS le créneau, AVANT le formulaire d'info.
   Comparatif visuel 2 cartes :
     · Avec compte ClientBase (recommandé) → mode "account"
     · En invité                             → mode "guest"
   + petit lien discret "J'ai déjà un compte → se connecter" → mode "login"
   Le choix décide ensuite si StepInfo affiche un champ mot de passe.
   Aucune validation ni RPC ici : juste un aiguillage de flow. */
const ChoiceStep = ({ pro, service, day, hour, onBack, onChoose, onLoginRequest }) => {
  const cardBase = {
    padding: 20, background: "var(--surface)",
    border: "1px solid var(--line-strong)", borderRadius: 16,
    fontFamily: "inherit", textAlign: "left",
    transition: "border-color .15s, transform .12s, box-shadow .15s",
    display: "flex", flexDirection: "column", gap: 10,
  };
  return (
    <div>
      <h2 style={{ fontSize: "clamp(22px, 3vw, 28px)", letterSpacing: "-0.02em", marginBottom: 4 }}>
        Comment souhaitez-vous réserver&nbsp;?
      </h2>
      <p style={{ fontSize: 14, color: "var(--ink-3)", marginBottom: 18 }}>
        Choisissez le parcours qui vous convient. Vous pouvez créer un compte ClientBase pour gérer vos RDV ou rester en invité (plus rapide mais limité).
      </p>

      {/* Summary card RDV en cours */}
      <div style={{
        padding: "12px 14px", marginBottom: 18,
        background: "var(--bg-alt)", border: "1px solid var(--line)",
        borderRadius: 10, display: "flex", gap: 10, alignItems: "center",
      }}>
        <div style={{
          width: 32, height: 32, borderRadius: 9,
          background: "var(--accent-soft)", color: "var(--accent-ink)",
          display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0,
        }}>
          <Icon name="calendar" size={16}/>
        </div>
        <div style={{ flex: 1, minWidth: 0, fontSize: 12.5, color: "var(--ink-2)" }}>
          <strong style={{ color: "var(--ink)" }}>{service.name}</strong> · {dayLabelOf(day)} {dayDateOf(day)} {dayMonthLongOf(day)} · {fmtHourBooking(hour)}
        </div>
      </div>

      {/* Grille 2 cartes : avec compte / en invité.
          Style responsive : 2 cols ≥640px, stack <640px. */}
      <style>{`
        .cb-choice-grid {
          display: grid;
          grid-template-columns: 1fr 1fr;
          gap: 12px;
        }
        @media (max-width: 640px) {
          .cb-choice-grid { grid-template-columns: 1fr; }
        }
      `}</style>
      <div className="cb-choice-grid">
        {/* ===== Carte 1 : AVEC COMPTE (recommandé) ===== */}
        <button
          onClick={() => onChoose("account")}
          style={{
            ...cardBase,
            borderColor: "var(--accent)",
            background: "var(--accent-soft)",
            cursor: "pointer",
            boxShadow: "0 6px 20px -10px var(--accent)",
          }}
          onMouseEnter={e => { e.currentTarget.style.transform = "translateY(-2px)"; e.currentTarget.style.boxShadow = "0 12px 28px -10px var(--accent)"; }}
          onMouseLeave={e => { e.currentTarget.style.transform = "translateY(0)"; e.currentTarget.style.boxShadow = "0 6px 20px -10px var(--accent)"; }}>
          <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
            <div style={{
              width: 38, height: 38, borderRadius: 11,
              background: "var(--accent)", color: "#fff",
              display: "flex", alignItems: "center", justifyContent: "center",
              fontSize: 20, fontWeight: 700, fontFamily: "var(--ff-display)",
              flexShrink: 0,
            }}>+</div>
            <div style={{ flex: 1, minWidth: 0 }}>
              <div style={{ fontSize: 15.5, fontWeight: 600, color: "var(--ink)", letterSpacing: "-0.01em" }}>
                Avec un compte
              </div>
              <div style={{
                display: "inline-block", marginTop: 3,
                fontSize: 10.5, fontWeight: 600, padding: "2px 7px",
                background: "var(--sage)", color: "#fff",
                borderRadius: 999, letterSpacing: "0.04em", textTransform: "uppercase",
              }}>Recommandé</div>
            </div>
          </div>
          <ul style={{
            margin: 0, padding: 0, listStyle: "none",
            fontSize: 13, color: "var(--ink-2)", lineHeight: 1.6,
            display: "flex", flexDirection: "column", gap: 4,
          }}>
            <li style={{ display: "flex", gap: 7 }}>
              <span style={{ color: "var(--accent)", fontWeight: 700, flexShrink: 0 }}>✓</span>
              Annulez ou déplacez en <strong>2 clics</strong>
            </li>
            <li style={{ display: "flex", gap: 7 }}>
              <span style={{ color: "var(--accent)", fontWeight: 700, flexShrink: 0 }}>✓</span>
              Vos RDV chez <strong>tous vos pros</strong> centralisés
            </li>
            <li style={{ display: "flex", gap: 7 }}>
              <span style={{ color: "var(--accent)", fontWeight: 700, flexShrink: 0 }}>✓</span>
              Rappels automatiques avant chaque RDV
            </li>
            <li style={{ display: "flex", gap: 7 }}>
              <span style={{ color: "var(--accent)", fontWeight: 700, flexShrink: 0 }}>✓</span>
              Gratuit, sans pub, sans CB
            </li>
          </ul>
          <div style={{
            marginTop: 4, padding: "10px 14px",
            background: "var(--accent)", color: "#fff",
            borderRadius: 10, textAlign: "center",
            fontSize: 13.5, fontWeight: 600,
          }}>
            Continuer avec compte →
          </div>
        </button>

        {/* ===== Carte 2 : EN INVITÉ ===== */}
        <button
          onClick={() => onChoose("guest")}
          style={{
            ...cardBase,
            cursor: "pointer",
          }}
          onMouseEnter={e => { e.currentTarget.style.transform = "translateY(-2px)"; e.currentTarget.style.borderColor = "var(--ink-4)"; }}
          onMouseLeave={e => { e.currentTarget.style.transform = "translateY(0)"; e.currentTarget.style.borderColor = "var(--line-strong)"; }}>
          <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
            <div style={{
              width: 38, height: 38, borderRadius: 11,
              background: "var(--bg-alt)", color: "var(--ink-3)",
              display: "flex", alignItems: "center", justifyContent: "center",
              fontSize: 20, fontWeight: 600, flexShrink: 0,
            }}>→</div>
            <div style={{ flex: 1, minWidth: 0 }}>
              <div style={{ fontSize: 15.5, fontWeight: 600, color: "var(--ink)", letterSpacing: "-0.01em" }}>
                En invité
              </div>
              <div style={{ fontSize: 12, color: "var(--ink-4)", marginTop: 3 }}>
                Plus rapide, mais sans suivi
              </div>
            </div>
          </div>
          <ul style={{
            margin: 0, padding: 0, listStyle: "none",
            fontSize: 13, color: "var(--ink-2)", lineHeight: 1.6,
            display: "flex", flexDirection: "column", gap: 4,
          }}>
            <li style={{ display: "flex", gap: 7 }}>
              <span style={{ color: "var(--sage)", fontWeight: 700, flexShrink: 0 }}>✓</span>
              Pas de mot de passe à créer
            </li>
            <li style={{ display: "flex", gap: 7 }}>
              <span style={{ color: "oklch(60% 0.16 25)", fontWeight: 700, flexShrink: 0 }}>×</span>
              Pas d'annulation/déplacement en ligne
            </li>
            <li style={{ display: "flex", gap: 7 }}>
              <span style={{ color: "oklch(60% 0.16 25)", fontWeight: 700, flexShrink: 0 }}>×</span>
              Contactez <strong>{pro && pro.businessName}</strong> directement pour toute modif
            </li>
            <li style={{ display: "flex", gap: 7 }}>
              <span style={{ color: "oklch(60% 0.16 25)", fontWeight: 700, flexShrink: 0 }}>×</span>
              Pas d'historique chez vos autres pros
            </li>
          </ul>
          <div style={{
            marginTop: 4, padding: "10px 14px",
            background: "var(--surface)", color: "var(--ink-2)",
            border: "1px solid var(--line-strong)",
            borderRadius: 10, textAlign: "center",
            fontSize: 13.5, fontWeight: 540,
          }}>
            Continuer en invité →
          </div>
        </button>
      </div>

      {/* Lien discret : déjà un compte ClientBase */}
      <div style={{ marginTop: 18, textAlign: "center" }}>
        <button onClick={onLoginRequest}
          style={{
            fontSize: 13, color: "var(--accent-ink)", fontWeight: 540,
            background: "transparent", border: "none", cursor: "pointer", fontFamily: "inherit",
            padding: "6px 10px",
          }}>
          J'ai déjà un compte ClientBase → me connecter
        </button>
      </div>

      <div style={{ marginTop: 6, textAlign: "center" }}>
        <button onClick={onBack}
          style={{
            fontSize: 12.5, color: "var(--ink-4)", background: "transparent",
            border: "none", cursor: "pointer", fontFamily: "inherit",
            padding: "4px 10px",
          }}>← Changer de créneau</button>
      </div>
    </div>
  );
};

/* ============ Step 4, Contact info + RGPD ============
   bookingMode :
     · "account" → form complet + champ mot de passe (signUp Supabase)
     · "guest"   → form complet sans mot de passe (RDV anonyme)
     · "login"   → form ultra-court (email + mot de passe seulement)
   Le submit délégué (onSubmit) lit form.password + bookingMode pour
   décider du flow auth avant la création du RDV. */
const StepInfo = ({ pro, service, day, hour, preferredSocial, form, setForm, error, submitting, onBack, onSubmit, acompteSettings, bookingMode }) => {
  const isLogin   = bookingMode === "login";
  const isAccount = bookingMode === "account";
  // Titre + sous-titre adaptés au mode pour que le client comprenne d'emblée
  // dans quel parcours il est.
  const title = isLogin
    ? "Connexion à votre compte"
    : isAccount
      ? "Vos coordonnées + mot de passe"
      : "Pour finir, vos coordonnées.";
  const subtitle = isLogin
    ? "Renseignez vos identifiants ClientBase pour lier ce RDV à votre compte."
    : isAccount
      ? "Vos infos servent à créer votre compte ClientBase + à confirmer le RDV."
      : "On les utilise uniquement pour vous contacter au sujet de ce rendez-vous.";
  return (
  <div>
    <h2 style={{ fontSize: "clamp(22px, 3vw, 28px)", letterSpacing: "-0.02em", marginBottom: 4 }}>
      {title}
    </h2>
    <p style={{ fontSize: 14, color: "var(--ink-3)", marginBottom: 20 }}>
      {subtitle}
    </p>

    {/* Notice pré-soumission : si acomptes activés, prévenir le client
        qu'un acompte sera demandé après confirmation (montant affiché). */}
    {acompteSettings && acompteSettings.enabled && (() => {
      const amount = _computeAcompte(service.price, acompteSettings);
      if (amount <= 0) return null;
      return (
        <div style={{
          marginBottom: 18, padding: "10px 14px",
          background: "var(--accent-soft)", border: "1px solid var(--accent-soft-2)",
          borderRadius: 10, fontSize: 13, color: "var(--accent-ink)",
          display: "flex", alignItems: "center", gap: 10,
        }}>
          <Icon name="zap" size={14} style={{ flexShrink: 0 }}/>
          <div style={{ flex: 1 }}>
            Un acompte de <strong>{amount.toFixed(2).replace(/\.00$/, "")} €</strong> sera demandé après confirmation pour sécuriser votre créneau.
          </div>
        </div>
      );
    })()}

    {/* Summary card */}
    <div style={{
      padding: "14px 16px", marginBottom: 20,
      background: "var(--bg-alt)", border: "1px solid var(--line)",
      borderRadius: 10, display: "flex", gap: 12, alignItems: "center", flexWrap: "wrap",
    }}>
      <div style={{
        width: 36, height: 36, borderRadius: 9,
        background: "var(--accent-soft)", color: "var(--accent-ink)",
        display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0,
      }}>
        <Icon name="calendar" size={18}/>
      </div>
      <div style={{ flex: 1, minWidth: 180 }}>
        <div style={{ fontSize: 13.5, fontWeight: 520, color: "var(--ink)" }}>
          {service.name} · {service.price} €
        </div>
        <div style={{ fontSize: 12.5, color: "var(--ink-3)", marginTop: 2 }}>
          {dayLabelOf(day)} {dayDateOf(day)} {dayMonthLongOf(day)} · {fmtHourBooking(hour)}
        </div>
      </div>
    </div>

    {error && (
      <div style={{
        padding: "10px 14px", marginBottom: 14,
        background: "oklch(96% 0.04 25)", border: "1px solid oklch(88% 0.06 25)",
        color: "oklch(42% 0.15 25)", borderRadius: 8, fontSize: 13.5,
      }}>{error}</div>
    )}

    <div style={{ display: "grid", gap: 12 }}>
      {/* Mode "login" : form ultra-court — juste email + mot de passe.
          On utilise les champs existants form.email + form.password pour
          ne pas dupliquer le state. */}
      {isLogin ? (
        <>
          <FormField label="Email" type="email" value={form.email}
            onChange={v => setForm({ ...form, email: v })}
            placeholder="vous@exemple.fr" required autoFocus/>
          <FormField label="Mot de passe" type="password" value={form.password || ""}
            onChange={v => setForm({ ...form, password: v })}
            placeholder="Votre mot de passe ClientBase" required/>
        </>
      ) : (
        <>
          <div className="cb-book-name-row" style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10 }}>
            <FormField label="Prénom" value={form.firstName} onChange={v => setForm({ ...form, firstName: v })} placeholder="Léa" required autoFocus/>
            <FormField label="Nom" value={form.lastName} onChange={v => setForm({ ...form, lastName: v })} placeholder="Morel" required/>
          </div>
          <FormField label="Email" type="email" value={form.email} onChange={v => setForm({ ...form, email: v })} placeholder="lea@exemple.fr" required/>
          <FormField label="Téléphone" value={form.phone} onChange={v => setForm({ ...form, phone: v })} placeholder="06 12 34 56 78"/>

          {/* Mode "account" : champ mot de passe en plus + hint sur email confirmation */}
          {isAccount && (
            <div style={{
              padding: 14, background: "var(--accent-soft)",
              border: "1px solid var(--accent-soft-2)", borderRadius: 10,
            }}>
              <div style={{ fontSize: 12.5, color: "var(--accent-ink)", fontWeight: 600, marginBottom: 8,
                display: "inline-flex", alignItems: "center", gap: 6 }}>
                <Icon name="zap" size={12}/> Votre nouveau compte ClientBase
              </div>
              <FormField label="Choisissez un mot de passe" type="password"
                value={form.password || ""}
                onChange={v => setForm({ ...form, password: v })}
                placeholder="8 caractères minimum" required/>
              <div style={{ fontSize: 11.5, color: "var(--ink-3)", marginTop: 8, lineHeight: 1.5 }}>
                Un email de confirmation sera envoyé à <strong>{form.email || "votre adresse"}</strong> pour activer votre compte et accéder à votre espace personnel.
              </div>
            </div>
          )}
        </>
      )}

      {/* Champs réseau / notes / RGPD : cachés en mode login (juste email+pwd).
          En mode account/guest, on les garde tous pour collecter le contexte. */}
      {!isLogin && (
        <>
          {preferredSocial && preferredSocial !== "none" && (
            <div>
              <FormField
                label={`Votre ${_SOCIAL_BOOK_LABEL[preferredSocial] || "réseau"} (optionnel)`}
                value={form.social}
                onChange={v => setForm({ ...form, social: v, noSocial: false })}
                placeholder={_SOCIAL_BOOK_PLACEHOLDER[preferredSocial] || "@pseudo"}
                disabled={form.noSocial}
              />
              <label style={{
                display: "flex", alignItems: "center", gap: 8,
                marginTop: 8, fontSize: 12.5, color: "var(--ink-3)", cursor: "pointer",
              }}>
                <input type="checkbox" checked={form.noSocial}
                  onChange={e => setForm({ ...form, noSocial: e.target.checked, social: e.target.checked ? "" : form.social })}
                  style={{ accentColor: "var(--accent)", width: 15, height: 15 }}/>
                Je n'ai pas de {_SOCIAL_BOOK_LABEL[preferredSocial] || "compte"}
              </label>
              <div style={{ fontSize: 11.5, color: "var(--ink-4)", marginTop: 6, lineHeight: 1.45 }}>
                Permet à <strong>{pro && pro.businessName ? pro.businessName : "votre prestataire"}</strong> de vous contacter facilement au sujet de ce rendez-vous.
              </div>
            </div>
          )}

          <FormTextarea label="Une précision pour la pro ? (optionnel)" value={form.notes} onChange={v => setForm({ ...form, notes: v })} placeholder="Allergie éventuelle, couleur souhaitée…" rows={2}/>

          <div style={{
            marginTop: 4, padding: "12px 14px",
            background: "var(--bg-alt)", border: "1px solid var(--line)",
            borderRadius: 10, fontSize: 12, color: "var(--ink-3)", lineHeight: 1.55,
          }}>
            <div style={{ fontWeight: 520, color: "var(--ink-2)", marginBottom: 4 }}>
              Ce que deviennent vos informations
            </div>
            <p style={{ margin: 0 }}>
              Vos coordonnées sont transmises à <strong>{pro && pro.name ? pro.name : "la prestataire"}</strong>, responsable de leur traitement, pour gérer ce rendez-vous et son suivi.
              Elles sont hébergées en France (Paris) via <strong>ClientBase</strong>, sous-traitant technique.
              Aucun envoi à des tiers, aucune publicité. Vous pouvez à tout moment demander l'accès, la rectification ou la suppression de vos données auprès de la prestataire, ou en écrivant à <a href="mailto:clientbase.fr@gmail.com" style={{ color: "var(--accent-ink)", fontWeight: 520 }}>clientbase.fr@gmail.com</a>.
              Plus d'infos&nbsp;: <a href="/legal" target="_blank" rel="noopener noreferrer" style={{ color: "var(--accent-ink)", fontWeight: 520 }}>politique de confidentialité</a>.
            </p>
          </div>

          <label style={{
            display: "flex", gap: 10, alignItems: "flex-start",
            fontSize: 12.5, color: "var(--ink-3)", lineHeight: 1.5,
            cursor: "pointer", padding: "8px 0",
          }}>
            <input type="checkbox" checked={form.rgpd} onChange={e => setForm({ ...form, rgpd: e.target.checked })}
              style={{ accentColor: "var(--accent)", width: 16, height: 16, flexShrink: 0, marginTop: 2 }}/>
            <span>
              J'ai lu et j'accepte que mes données soient utilisées comme décrit ci-dessus pour traiter ce rendez-vous.
            </span>
          </label>
        </>
      )}
    </div>

    <div style={{ marginTop: 22, display: "flex", gap: 10 }}>
      <button className="btn btn-ghost btn-lg" onClick={onBack} style={{ flex: "0 0 auto" }}>
        ← Retour
      </button>
      <button className="btn btn-accent btn-lg" onClick={onSubmit} disabled={submitting} style={{ flex: 1, opacity: submitting ? 0.7 : 1 }}>
        {submitting ? "Envoi…"
          : isLogin   ? "Se connecter et confirmer"
          : isAccount ? "Créer mon compte et confirmer"
          : "Confirmer mon rendez-vous"} {!submitting && <Icon name="check" size={14} stroke={2.6}/>}
      </button>
    </div>
  </div>
  );
};

/* ============ Step 4, Success ============ */
// Construit l'event ICS à partir du RDV confirmé (date + heure + durée
// service + nom pro + adresse si dispo). Retourne null si data incomplet.
const _bookingICSEvent = (pro, confirmed) => {
  if (!confirmed || !confirmed.service) return null;
  const baseDate = _bookingDateForI(confirmed.day);
  if (!baseDate) return null;
  const hh = Math.floor(confirmed.hour);
  const mm = Math.round((confirmed.hour - hh) * 60);
  const start = new Date(baseDate);
  start.setHours(hh, mm, 0, 0);
  const durHours = confirmed.service.duration || 1;
  const end = new Date(start.getTime() + durHours * 60 * 60 * 1000);
  const businessName = pro.businessName || pro.business_name || "Pro";
  const ownerName    = pro.ownerName || pro.owner_name || "";
  const location     = [pro.address, pro.postal_code, pro.city].filter(Boolean).join(", ");
  const descLines = [
    `Prestation : ${confirmed.service.name}`,
    `Prix : ${confirmed.service.price} €`,
    `Chez : ${businessName}${ownerName ? ` (${ownerName})` : ""}`,
    pro.phone ? `Téléphone : ${pro.phone}` : "",
    "",
    "Réservé via ClientBase",
  ].filter(Boolean).join("\n");
  return {
    uid: `booking-${confirmed.day}-${confirmed.hour}-${pro.bookingSlug || "pro"}@clientbase.fr`,
    start, end,
    title: `${confirmed.service.name} — ${businessName}`,
    description: descLines,
    location,
  };
};

// Calcule le montant de l'acompte selon le mode du pro :
//   - mode 'fixed'   : on prend min_amount comme somme fixe, plafonné au prix
//   - mode 'percent' : max(price * percent / 100, min_amount), plafonné au prix
// 'percent' est le défaut si le mode n'est pas posé (compatibilité).
const _computeAcompte = (price, settings) => {
  if (!settings || !settings.enabled) return 0;
  const p = Number(price) || 0;
  const min = Number(settings.min_amount || 0);
  const mode = settings.mode || "percent";
  let wanted;
  if (mode === "fixed") {
    wanted = min;
  } else {
    const pct = Number(settings.percent || 0);
    wanted = Math.max(p * pct / 100, min);
  }
  return Math.min(wanted, p || wanted);
};

// Nouveau banner après création compte client : on détecte si une session
// Supabase est active (le trigger DB auto-confirme les emails clients +
// signIn auto dans le booking submit → en théorie OUI). Si oui, banner
// "Votre compte est prêt" avec bouton direct vers espace. Sinon, banner
// "Confirmez votre email" + bouton resend (ancien comportement, fallback).
const AccountCreatedBanner = ({ email }) => {
  const [hasSession, setHasSession] = React.useState(null); // null = loading
  React.useEffect(() => {
    let alive = true;
    (async () => {
      if (!window.cbSupabase) { if (alive) setHasSession(false); return; }
      try {
        const { data } = await window.cbSupabase.auth.getSession();
        if (alive) setHasSession(!!(data && data.session));
      } catch { if (alive) setHasSession(false); }
    })();
    return () => { alive = false; };
  }, []);

  if (hasSession === null) return null; // ne flash rien pendant le check
  if (hasSession) {
    return (
      <div style={{
        marginTop: 22, maxWidth: 480, margin: "22px auto 0",
        padding: "16px 18px",
        background: "var(--sage-soft)",
        border: "1px solid oklch(82% 0.07 160)",
        borderRadius: 14, textAlign: "left",
        display: "flex", gap: 14, alignItems: "flex-start",
      }}>
        <span style={{
          width: 40, height: 40, borderRadius: 11, flexShrink: 0,
          background: "var(--sage)", color: "#fff",
          display: "inline-flex", alignItems: "center", justifyContent: "center",
        }}>
          <Icon name="check" size={20} stroke={2.6}/>
        </span>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: 14.5, fontWeight: 600, color: "oklch(38% 0.08 160)", letterSpacing: "-0.01em" }}>
            🎉 Votre compte ClientBase est prêt
          </div>
          <div style={{ fontSize: 13, color: "var(--ink-2)", marginTop: 4, lineHeight: 1.55 }}>
            Compte créé avec <strong>{email}</strong> · vous êtes déjà connecté(e). Retrouvez ce RDV et tous vos futurs RDV dans votre espace personnel.
          </div>
          <a href="/?espace=me" style={{
            display: "inline-flex", alignItems: "center", gap: 6,
            marginTop: 10,
            padding: "8px 14px",
            background: "var(--sage)", color: "#fff",
            borderRadius: 999, fontSize: 13, fontWeight: 540,
            textDecoration: "none",
          }}>
            Accéder à mon espace →
          </a>
        </div>
      </div>
    );
  }
  // Fallback : pas de session active (ex: trigger DB pas appliqué côté
  // Supabase, ou erreur). On retombe sur l'ancien banner email confirm.
  return <EmailConfirmBanner email={email}/>;
};

// Banner avec bouton "Renvoyer l'email" + lien diagnostic spam.
// Cooldown 30s entre 2 renvois pour éviter de spammer Supabase.
const EmailConfirmBanner = ({ email }) => {
  const [resending, setResending] = React.useState(false);
  const [sentAt, setSentAt] = React.useState(0);
  const [error, setError] = React.useState(null);
  const [showHelp, setShowHelp] = React.useState(false);
  const cooldown = 30;
  const remaining = Math.max(0, cooldown - Math.floor((Date.now() - sentAt) / 1000));
  const [, tick] = React.useState(0);
  React.useEffect(() => {
    if (remaining <= 0) return;
    const t = setInterval(() => tick(x => x + 1), 1000);
    return () => clearInterval(t);
  }, [remaining]);

  const resend = async () => {
    if (remaining > 0 || resending) return;
    setError(null); setResending(true);
    try {
      if (!window.cbSupabase) { setError("Service indisponible."); return; }
      const { error: e } = await window.cbSupabase.auth.resend({
        type: "signup",
        email,
        options: { emailRedirectTo: `${window.location.origin}/v2/espace` },
      });
      if (e) {
        const msg = (e.message || "").toLowerCase();
        if (msg.includes("rate") || msg.includes("too many")) {
          setError("Trop de tentatives. Réessayez dans quelques minutes.");
        } else if (msg.includes("not found") || msg.includes("user")) {
          setError("Aucun compte trouvé à cette adresse.");
        } else {
          setError(e.message || "Échec du renvoi.");
        }
      } else {
        setSentAt(Date.now());
      }
    } finally { setResending(false); }
  };

  return (
    <div style={{
      marginTop: 22, maxWidth: 480, margin: "22px auto 0",
      padding: "16px 18px",
      background: "var(--accent-soft)",
      border: "1px solid var(--accent-soft-2)",
      borderRadius: 14, textAlign: "left",
    }}>
      <div style={{ display: "flex", gap: 14, alignItems: "flex-start" }}>
        <span style={{
          width: 40, height: 40, borderRadius: 11, flexShrink: 0,
          background: "var(--accent)", color: "#fff",
          display: "inline-flex", alignItems: "center", justifyContent: "center",
        }}>
          <Icon name="mail" size={18}/>
        </span>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: 14.5, fontWeight: 600, color: "var(--accent-ink)", letterSpacing: "-0.01em" }}>
            📬 Confirmez votre email
          </div>
          <div style={{ fontSize: 13, color: "var(--ink-2)", marginTop: 4, lineHeight: 1.55 }}>
            Un email vient d'être envoyé à <strong>{email}</strong>. Cliquez sur le lien pour activer votre compte et retrouver ce RDV dans votre espace personnel.
          </div>
          <div style={{
            marginTop: 8, padding: "8px 10px",
            background: "rgba(255,255,255,0.6)", border: "1px solid var(--accent-soft-2)",
            borderRadius: 8, fontSize: 11.5, color: "var(--ink-3)", lineHeight: 1.5,
          }}>
            <strong style={{ color: "var(--ink-2)" }}>Bon à savoir&nbsp;:</strong> votre rendez-vous est <strong>déjà réservé</strong>. Vous pourrez l'annuler ou le déplacer en 2 clics dès que votre compte sera confirmé.
          </div>
          {error && (
            <div style={{
              marginTop: 8, padding: "7px 10px",
              background: "oklch(96% 0.04 25)", border: "1px solid oklch(88% 0.06 25)",
              borderRadius: 8, fontSize: 12, color: "oklch(42% 0.15 25)",
            }}>{error}</div>
          )}
          <div style={{ marginTop: 10, display: "flex", gap: 10, flexWrap: "wrap", alignItems: "center" }}>
            <button onClick={resend} disabled={remaining > 0 || resending}
              style={{
                padding: "7px 13px", borderRadius: 999,
                background: (remaining > 0 || resending) ? "var(--bg-alt)" : "var(--accent)",
                color: (remaining > 0 || resending) ? "var(--ink-4)" : "#fff",
                border: "none", fontFamily: "inherit", fontSize: 12.5, fontWeight: 540,
                cursor: (remaining > 0 || resending) ? "not-allowed" : "pointer",
              }}>
              {resending ? "Envoi…"
                : remaining > 0 ? `Renvoyer (${remaining}s)`
                : "Renvoyer l'email"}
            </button>
            <button onClick={() => setShowHelp(s => !s)}
              style={{
                background: "transparent", border: "none", cursor: "pointer",
                color: "var(--accent-ink)", fontSize: 12, fontFamily: "inherit",
                textDecoration: "underline", padding: 0,
              }}>
              {showHelp ? "Masquer" : "Pas reçu d'email ?"}
            </button>
          </div>
          {showHelp && (
            <div style={{
              marginTop: 10, padding: "10px 12px",
              background: "var(--surface)", border: "1px solid var(--line)",
              borderRadius: 9, fontSize: 12, color: "var(--ink-2)", lineHeight: 1.55,
            }}>
              <ul style={{ margin: 0, padding: "0 0 0 18px", display: "flex", flexDirection: "column", gap: 4 }}>
                <li><strong>Vérifiez les courriers indésirables / spam</strong> (l'email vient de <code style={{ fontFamily: "var(--ff-mono)", fontSize: 11 }}>noreply@mail.app.supabase.io</code>).</li>
                <li>Attendez 1-2 min, parfois la délivrance prend du temps.</li>
                <li>Vérifiez que <strong>{email}</strong> est bien votre vraie adresse (pas de faute).</li>
                <li>Cliquez « Renvoyer l'email » ci-dessus.</li>
                <li>Toujours rien&nbsp;? Écrivez-nous à <a href="mailto:clientbase.fr@gmail.com" style={{ color: "var(--accent-ink)", fontWeight: 540 }}>clientbase.fr@gmail.com</a>.</li>
              </ul>
            </div>
          )}
        </div>
      </div>
    </div>
  );
};

const StepConfirmed = ({ pro, confirmed, acompteSettings, bookingMode, email, form, onRestart }) => (
  <div style={{ textAlign: "center", padding: "24px 0" }}>
    <div style={{
      width: 72, height: 72, borderRadius: 50, margin: "0 auto",
      background: "var(--sage-soft)", color: "var(--sage)",
      display: "flex", alignItems: "center", justifyContent: "center",
    }}>
      <Icon name="check" size={36} stroke={2.6}/>
    </div>
    <h2 style={{
      marginTop: 20, fontSize: "clamp(26px, 3.4vw, 34px)",
      letterSpacing: "-0.03em",
    }}>
      C'est confirmé&nbsp;!
    </h2>
    <p style={{
      marginTop: 12, fontSize: 15.5, color: "var(--ink-2)",
      maxWidth: 440, margin: "12px auto 0", lineHeight: 1.55,
    }}>
      Votre rendez-vous chez <strong>{pro.businessName}</strong> est réservé pour <strong>{dayLabelOf(confirmed.day)} {dayDateOf(confirmed.day)} {dayMonthLongOf(confirmed.day)}</strong> à <strong>{fmtHourBooking(confirmed.hour)}</strong>.
    </p>

    <div style={{
      marginTop: 24, padding: "16px 18px", maxWidth: 440, margin: "24px auto 0",
      background: "var(--surface)", border: "1px solid var(--line)", borderRadius: 12,
      display: "flex", flexDirection: "column", gap: 8,
      fontSize: 13.5, color: "var(--ink-2)",
    }}>
      <div style={{ display: "flex", justifyContent: "space-between" }}>
        <span style={{ color: "var(--ink-4)" }}>Prestation</span>
        <span style={{ fontWeight: 520 }}>{confirmed.service.name}</span>
      </div>
      <div style={{ display: "flex", justifyContent: "space-between" }}>
        <span style={{ color: "var(--ink-4)" }}>Durée</span>
        <span style={{ fontFamily: "inherit" }}>
          {confirmed.service.duration >= 1
            ? `${Math.floor(confirmed.service.duration)}h${confirmed.service.duration % 1 ? (confirmed.service.duration % 1) * 60 : ""}`
            : `${confirmed.service.duration * 60} min`}
        </span>
      </div>
      <div style={{ display: "flex", justifyContent: "space-between" }}>
        <span style={{ color: "var(--ink-4)" }}>Prix</span>
        <span style={{ fontFamily: "inherit", fontWeight: 520 }}>{confirmed.service.price} €</span>
      </div>
    </div>

    {/* Bandeau "Confirmez votre email" — mode account uniquement. Le RDV
        est DÉJÀ pris (créneau sécurisé). Le lien email pointe vers
        /v2/espace qui appelle link_my_orphan_appointments(). Bouton
        Renvoyer + lien "Pas reçu ?" pour diagnostic spam / config SMTP. */}
    {bookingMode === "account" && email && <AccountCreatedBanner email={email}/>}

    {/* Bloc ACOMPTE (si activé par le pro) : montant calculé +
        lien de paiement externe (PayPal, Stripe, etc.). ClientBase ne
        traite pas le paiement — c'est juste un pont vers la solution
        du pro. Affiché en évidence pour que le client ne le rate pas. */}
    {acompteSettings && acompteSettings.enabled && (() => {
      const amount = _computeAcompte(confirmed.service.price, acompteSettings);
      if (amount <= 0) return null;
      const hasLink = !!(acompteSettings.payment_link && acompteSettings.payment_link.trim());
      const provider = acompteSettings.payment_provider || "le lien ci-dessous";
      return (
        <div style={{
          marginTop: 24, maxWidth: 440, margin: "24px auto 0",
          padding: "18px 18px",
          background: "var(--accent-soft)",
          border: "1px solid var(--accent-soft-2)",
          borderRadius: 14, textAlign: "left",
        }}>
          <div style={{
            display: "flex", alignItems: "center", gap: 10,
            fontSize: 12.5, fontWeight: 700, color: "var(--accent-ink)",
            textTransform: "uppercase", letterSpacing: "0.06em",
            marginBottom: 10,
          }}>
            <span style={{
              width: 22, height: 22, borderRadius: 999,
              background: "var(--accent)", color: "#fff",
              display: "inline-flex", alignItems: "center", justifyContent: "center",
              fontSize: 13, fontWeight: 700,
            }}>€</span>
            Acompte à verser
          </div>
          <div style={{
            fontSize: 26, fontWeight: 700, color: "var(--ink)",
            letterSpacing: "-0.025em", lineHeight: 1.1,
          }}>
            {amount.toFixed(2).replace(/\.00$/, "")} €
          </div>
          <div style={{ fontSize: 13, color: "var(--ink-2)", marginTop: 6, lineHeight: 1.5 }}>
            Pour confirmer votre rendez-vous, merci de verser un acompte de{" "}
            <strong>{amount.toFixed(2).replace(/\.00$/, "")} €</strong> via {provider}.
          </div>
          {acompteSettings.policy_text && (
            <div style={{
              marginTop: 10, padding: "8px 12px",
              background: "rgba(255,255,255,0.6)", border: "1px solid var(--accent-soft-2)",
              borderRadius: 9, fontSize: 12, color: "var(--ink-3)", lineHeight: 1.5,
            }}>
              {acompteSettings.policy_text}
            </div>
          )}
          {hasLink ? (
            <a href={acompteSettings.payment_link} target="_blank" rel="noopener noreferrer"
              style={{
                marginTop: 14, display: "flex", alignItems: "center", justifyContent: "center", gap: 8,
                padding: "13px 18px",
                background: "var(--accent)", color: "#fff",
                borderRadius: 12, textDecoration: "none",
                fontSize: 14.5, fontWeight: 600,
                boxShadow: "0 12px 28px -12px var(--accent)",
              }}>
              Payer mon acompte sur {acompteSettings.payment_provider || "le lien du pro"}
              <Icon name="external" size={14}/>
            </a>
          ) : (
            <div style={{
              marginTop: 12, padding: "10px 12px",
              background: "var(--warn-soft)", border: "1px solid var(--warn-soft-2)",
              borderRadius: 9, fontSize: 12.5, color: "var(--warn-ink)",
            }}>
              Le pro vous contactera pour vous transmettre les modalités de paiement de l'acompte.
            </div>
          )}
          {acompteSettings.payment_instructions && (
            <div style={{
              marginTop: 10, fontSize: 11.5, color: "var(--ink-3)", lineHeight: 1.5,
            }}>
              <strong style={{ color: "var(--ink-2)" }}>À noter&nbsp;:</strong> {acompteSettings.payment_instructions}
            </div>
          )}
        </div>
      );
    })()}

    {/* Bloc « Ajouter à mon calendrier » : 3 boutons logos colorés
        (Google / Apple / Outlook). Génère un ICS pour Apple, des
        deeplinks pour les autres. Marche sans backend, c'est juste
        un fichier que le browser télécharge ou une URL qu'il ouvre. */}
    {(() => {
      const ev = _bookingICSEvent(pro, confirmed);
      if (!ev) return null;
      const Cmp = window.CalendarExportButtons;
      if (!Cmp) return null;
      return (
        <div style={{
          marginTop: 24, maxWidth: 440, margin: "24px auto 0",
          padding: "14px 16px",
          background: "var(--surface)", border: "1px solid var(--line)",
          borderRadius: 12,
        }}>
          <div style={{
            fontSize: 12.5, fontWeight: 600, color: "var(--ink-3)",
            textTransform: "uppercase", letterSpacing: "0.06em",
            marginBottom: 10,
          }}>
            📅 Ajouter à mon calendrier
          </div>
          <Cmp event={ev} filename={`rdv-${pro.businessName || "clientbase"}-${ev.start.toISOString().slice(0,10)}`}/>
        </div>
      );
    })()}

    <div style={{
      marginTop: 20, fontSize: 13, color: "var(--ink-4)",
    }}>
      Un email de confirmation sera envoyé (en bêta, cette étape est simulée).
    </div>

    {/* CTA espace cliente — message adapté au parcours qu'a suivi le client :
        · "account" → ils ont créé un compte → CTA "Se connecter à mon compte"
          (le bandeau email confirm leur dit déjà d'aller cliquer le lien)
        · "login" → ils sont déjà connectés → on ne ré-affiche pas le CTA
        · "guest" → ils n'ont pas de compte → CTA "Créer un compte avec mes
          infos" + pré-remplit le signup avec ce qu'ils viennent de saisir
        Aucun CTA ne s'affiche en mode preview (jamais utile à un pro qui
        teste sa page). */}
    {bookingMode !== "login" && (() => {
      const isAccount = bookingMode === "account";
      const isGuest   = !isAccount && bookingMode !== "login";
      const title = isAccount
        ? "Votre compte ClientBase est créé"
        : "Créez un compte avec vos infos";
      const body = isAccount
        ? `Cliquez le lien reçu dans votre email pour activer votre compte, puis connectez-vous pour retrouver vos RDV chez ${pro.businessName} et chez tous vos pros ClientBase.`
        : `Vos coordonnées (nom, email, téléphone) sont déjà prêtes — il ne reste qu'un mot de passe à choisir. Gratuit, RGPD.`;
      const ctaLabel = isAccount
        ? "Se connecter à mon compte →"
        : "Créer mon compte avec mes infos →";
      // Pré-remplit le signup espace client via URL params : le formulaire
      // d'inscription les lit pour ne pas faire re-saisir.
      const ctaHref = isAccount
        ? "/v2/?espace=" // espace login
        : `/v2/?espace=signup&prefill=1&first=${encodeURIComponent(form?.firstName || "")}&last=${encodeURIComponent(form?.lastName || "")}&email=${encodeURIComponent(form?.email || email || "")}&phone=${encodeURIComponent(form?.phone || "")}`;
      return (
        <div style={{
          marginTop: 24, maxWidth: 440, margin: "24px auto 0",
          padding: "16px 18px",
          background: "linear-gradient(135deg, var(--accent-soft), oklch(96% 0.04 200))",
          border: "1px solid var(--accent-soft-2)",
          borderRadius: 14,
          textAlign: "left",
        }}>
          <div style={{ display: "flex", alignItems: "flex-start", gap: 12 }}>
            <span style={{
              width: 36, height: 36, borderRadius: 9,
              background: "var(--surface)", color: "var(--accent-ink)",
              display: "inline-flex", alignItems: "center", justifyContent: "center",
              flexShrink: 0, fontSize: 18,
            }}>{isAccount ? "🔓" : "🪪"}</span>
            <div style={{ flex: 1, minWidth: 0 }}>
              <div style={{ fontSize: 14, fontWeight: 580, color: "var(--ink)", marginBottom: 3 }}>
                {title}
              </div>
              <div style={{ fontSize: 12.5, color: "var(--ink-3)", lineHeight: 1.5, marginBottom: 10 }}>
                {body}
              </div>
              <a href={ctaHref} style={{
                display: "inline-flex", alignItems: "center", gap: 5,
                padding: "7px 13px",
                background: "var(--accent)", color: "#fff",
                borderRadius: 999, fontSize: 12.5, fontWeight: 540,
                textDecoration: "none",
              }}>
                {ctaLabel}
              </a>
            </div>
          </div>
        </div>
      );
    })()}

    <div style={{ marginTop: 16 }}>
      <button className="btn btn-ghost" onClick={onRestart}>
        Réserver un autre rendez-vous
      </button>
    </div>
  </div>
);

/* ============ Slug not found / no pro ============ */
const BookingNotFound = ({ slug }) => (
  <div style={{
    minHeight: "100vh", background: "var(--bg)",
    display: "flex", alignItems: "center", justifyContent: "center",
    padding: "32px 20px",
  }}>
    <div style={{
      maxWidth: 480, textAlign: "center",
      padding: 32,
      background: "var(--surface)", border: "1px solid var(--line)",
      borderRadius: 16, boxShadow: "var(--sh-2)",
    }}>
      <div style={{ display: "flex", justifyContent: "center", marginBottom: 16 }}>
        <Logo size={28}/>
      </div>
      <h1 style={{ fontSize: 22, marginBottom: 8 }}>Lien de réservation introuvable</h1>
      <p style={{ fontSize: 14.5, color: "var(--ink-3)", lineHeight: 1.55 }}>
        Le lien <code style={{ fontFamily: "inherit", background: "var(--bg-alt)", padding: "2px 6px", borderRadius: 4, fontSize: 12.5 }}>?book={slug}</code> ne correspond à aucun compte sur cet appareil.
      </p>
      <div style={{
        marginTop: 20, padding: "12px 14px",
        background: "var(--bg-alt)", border: "1px dashed var(--line-strong)",
        borderRadius: 10, fontSize: 12.5, color: "var(--ink-3)", lineHeight: 1.5, textAlign: "left",
      }}>
        <strong style={{ color: "var(--ink-2)" }}>Note bêta&nbsp;:</strong> pendant la bêta, les réservations se font en local, dans le même navigateur que celui de la pro. Une vraie synchro serveur arrivera au lancement officiel.
      </div>
      <a href="/" className="btn btn-ghost" style={{ marginTop: 20 }}>
        Retour à l'accueil
      </a>
    </div>
  </div>
);

/* ================================================================
   PUBLIC LOYALTY CARD PAGE, /?card=<slug>&cli=<clientId>
================================================================ */
const CardPage = ({ slug, clientId }) => {
  const pro = cbAuth.findUserBySlug(slug);
  const [data, setData] = React.useState(() => {
    try {
      const saved = localStorage.getItem(CB_LS_KEY);
      if (saved) return { ...SEED_DATA, ...JSON.parse(saved) };
    } catch (e) { /* ignore */ }
    return SEED_DATA;
  });

  // Poll localStorage every 3s to reflect pro-side changes
  React.useEffect(() => {
    const t = setInterval(() => {
      try {
        const saved = localStorage.getItem(CB_LS_KEY);
        if (saved) setData({ ...SEED_DATA, ...JSON.parse(saved) });
      } catch (e) { /* ignore */ }
    }, 3000);
    return () => clearInterval(t);
  }, []);

  if (!pro) return <BookingNotFound slug={slug}/>;
  const client = data.clients.find(c => c.id === clientId);
  if (!client) {
    return (
      <div style={{
        minHeight: "100vh", background: "var(--bg)",
        display: "flex", alignItems: "center", justifyContent: "center", padding: 24,
      }}>
        <div style={{
          maxWidth: 420, textAlign: "center", padding: 32,
          background: "var(--surface)", border: "1px solid var(--line)",
          borderRadius: 16, boxShadow: "var(--sh-2)",
        }}>
          <h1 style={{ fontSize: 20, marginBottom: 8 }}>Carte introuvable</h1>
          <p style={{ fontSize: 14, color: "var(--ink-3)" }}>
            Ce lien de fidélité n'est plus valide. Demandez-en un nouveau à {pro.businessName}.
          </p>
        </div>
      </div>
    );
  }

  const total = data.fideliteRules.visits;
  const remaining = Math.max(0, total - client.fid);
  const rowCount = Math.ceil(total / 5);

  return (
    <div style={{
      minHeight: "100vh", background: "var(--bg)",
      display: "flex", flexDirection: "column",
    }}>
      <BookingHeader pro={pro}/>
      <main style={{ flex: 1, padding: "32px 16px 48px" }}>
        <div style={{ maxWidth: 440, margin: "0 auto" }}>
          <h1 style={{
            fontSize: "clamp(22px, 3vw, 28px)", letterSpacing: "-0.025em",
            marginBottom: 4, textAlign: "center",
          }}>
            Votre carte de fidélité
          </h1>
          <p style={{
            fontSize: 14, color: "var(--ink-3)",
            textAlign: "center", marginBottom: 20,
          }}>
            Chez <strong>{pro.businessName}</strong>
          </p>

          {/* The card */}
          <div style={{
            padding: 24,
            background: "linear-gradient(135deg, var(--accent) 0%, oklch(45% 0.22 288) 100%)",
            borderRadius: 18, color: "white", boxShadow: "var(--sh-3)",
          }}>
            <div style={{ display: "flex", justifyContent: "space-between", alignItems: "start" }}>
              <div>
                <div style={{ fontSize: 10.5, textTransform: "uppercase", letterSpacing: "0.1em", opacity: 0.85 }}>
                  {pro.businessName}
                </div>
                <div style={{
                  fontFamily: "var(--ff-display)", fontSize: 22,
                  fontWeight: 580, letterSpacing: "-0.025em", marginTop: 6,
                }}>{client.name}</div>
              </div>
              <Logo size={26} withWordmark={false}/>
            </div>

            <div style={{
              marginTop: 26,
              display: "grid",
              gridTemplateColumns: `repeat(${Math.min(total, 5)}, 1fr)`,
              gap: 8,
            }}>
              {Array.from({ length: total }).map((_, i) => {
                const stamped = i < client.fid;
                return (
                  <div key={i} style={{
                    aspectRatio: 1, borderRadius: 50,
                    background: stamped ? "rgba(255,255,255,0.95)" : "rgba(255,255,255,0.15)",
                    border: stamped ? "none" : "1.5px dashed rgba(255,255,255,0.4)",
                    display: "flex", alignItems: "center", justifyContent: "center",
                    color: "var(--accent)",
                  }}>
                    {stamped && <Icon name="check" size={16} stroke={3}/>}
                  </div>
                );
              })}
            </div>

            <div style={{
              marginTop: 20, padding: "12px 14px",
              background: "rgba(255,255,255,0.15)", borderRadius: 10,
              fontSize: 13, lineHeight: 1.5,
            }}>
              {client.fid >= total
                ? <><strong>🎁 Votre récompense est prête&nbsp;!</strong> {data.fideliteRules.rewardLabel}.</>
                : remaining === 1
                  ? <>Plus qu'<strong>une visite</strong> pour&nbsp;: {data.fideliteRules.rewardLabel}</>
                  : <>Encore <strong>{remaining} visites</strong> avant&nbsp;: {data.fideliteRules.rewardLabel}</>
              }
            </div>
          </div>

          {/* Info */}
          <div style={{
            marginTop: 16, padding: "12px 14px",
            background: "var(--bg-alt)", border: "1px dashed var(--line-strong)",
            borderRadius: 10, fontSize: 12.5, color: "var(--ink-3)", lineHeight: 1.5,
          }}>
            Cette carte se met à jour automatiquement après chaque visite. Conservez ce lien pour suivre votre progression.
          </div>

          {/* Book button */}
          <div style={{ marginTop: 20 }}>
            <a href={`?book=${slug}`} className="btn btn-accent btn-lg" style={{ width: "100%" }}>
              Prendre mon prochain RDV <Icon name="arrow" size={15}/>
            </a>
          </div>
        </div>
      </main>
      <BookingFooter pro={pro}/>
    </div>
  );
};

const _prettifyBookingError = (msg) => {
  const m = (msg || "").toUpperCase();
  if (m.includes("SLUG_NOT_FOUND") || m.includes("BOOKING_NOT_FOUND")) return "Ce professionnel n'existe plus.";
  if (m.includes("SERVICE_DISABLED")) return "Cette prestation n'est plus disponible, choisissez-en une autre.";
  if (m.includes("SERVICE_NOT_FOUND")) return "Cette prestation n'est plus disponible.";
  if (m.includes("CLIENT_NAME_REQUIRED")) return "Prénom et nom requis.";
  if (m.includes("EMAIL_INVALID"))      return "Email invalide.";
  if (m.includes("FIRST_REQUIRED") || m.includes("LAST_REQUIRED")) return "Prénom et nom requis.";
  if (m.includes("SLOT_REQUIRED"))      return "Créneau invalide.";
  if (m.includes("PUBLIC_BOOK_APPOINTMENT") && m.includes("DOES NOT EXIST")) {
    return "Le système de réservation n'est pas encore activé pour ce salon. Contactez-le par message direct.";
  }
  return "Une erreur est survenue. Réessayez ou contactez le salon directement.";
};

Object.assign(window, {
  BookingPage, BookingNotFound, CardPage,
  generateAvailableSlots, fmtHourBooking,
});
