/* ClientBase V2, Module "Ma page" (page de réservation personnalisée)
   - Desktop : éditeur visuel avec aperçu temps réel
   - Mobile  : alerte renvoyant vers desktop
*/

// === Couleurs principales : palette unie, pas de dégradé multicolore ===
// Chaque couleur = { id, label, color (swatch + accent), mode: light|dark }
const PAGE_THEMES = [
  { id: "indigo", label: "Indigo", color: "oklch(55% 0.22 278)", mode: "light" },
  { id: "bleu",   label: "Bleu",   color: "oklch(55% 0.17 245)", mode: "light" },
  { id: "ciel",   label: "Ciel",   color: "oklch(62% 0.13 220)", mode: "light" },
  { id: "teal",   label: "Teal",   color: "oklch(58% 0.12 195)", mode: "light" },
  { id: "vert",   label: "Vert",   color: "oklch(58% 0.15 150)", mode: "light" },
  { id: "jaune",  label: "Jaune",  color: "oklch(78% 0.15 90)",  mode: "light" },
  { id: "ambre",  label: "Ambre",  color: "oklch(62% 0.15 65)",  mode: "light" },
  { id: "orange", label: "Orange", color: "oklch(64% 0.18 40)",  mode: "light" },
  { id: "rouge",  label: "Rouge",  color: "oklch(57% 0.21 25)",  mode: "light" },
  { id: "rose",   label: "Rose",   color: "oklch(62% 0.17 350)", mode: "light" },
  { id: "violet", label: "Violet", color: "oklch(55% 0.20 305)", mode: "light" },
  { id: "noir",   label: "Noir",   color: "oklch(22% 0.01 280)", mode: "light" },
];
// Couleur par défaut quand un theme id inconnu
const _pageThemeOf = (id) => PAGE_THEMES.find(t => t.id === id) || PAGE_THEMES[0];

// Polices d'écriture proposées, variations subtiles autour des sans/serif
// modernes, pas de fantaisie agressive (mono ou manuscrit) qui détonne.
const PAGE_FONTS = [
  { id: "modern",      label: "Moderne",      sample: "Geist",            css: "var(--ff-display)" },
  { id: "minimal",     label: "Minimaliste",  sample: "Space Grotesk",    css: '"Space Grotesk", "Geist", sans-serif' },
  { id: "elegant",     label: "Élégant",      sample: "Fraunces",         css: '"Fraunces", "Geist", serif' },
  { id: "editorial",   label: "Éditorial",    sample: "Playfair Display", css: '"Playfair Display", "Fraunces", "Georgia", serif' },
  { id: "boutique",    label: "Boutique",     sample: "Cormorant",        css: '"Cormorant Garamond", "Fraunces", serif' },
];

// === Couleur des boutons : "auto" (= couleur du thème) ou une couleur unie ===
const PAGE_BUTTON_COLORS = [
  { id: "auto",   label: "Auto",   color: null },
  { id: "indigo", label: "Indigo", color: "oklch(55% 0.22 278)" },
  { id: "bleu",   label: "Bleu",   color: "oklch(55% 0.17 245)" },
  { id: "teal",   label: "Teal",   color: "oklch(58% 0.12 195)" },
  { id: "vert",   label: "Vert",   color: "oklch(58% 0.15 150)" },
  { id: "jaune",  label: "Jaune",  color: "oklch(78% 0.15 90)"  },
  { id: "orange", label: "Orange", color: "oklch(64% 0.18 40)"  },
  { id: "rouge",  label: "Rouge",  color: "oklch(57% 0.21 25)"  },
  { id: "rose",   label: "Rose",   color: "oklch(62% 0.17 350)" },
  { id: "violet", label: "Violet", color: "oklch(55% 0.20 305)" },
  { id: "noir",   label: "Noir",   color: "oklch(25% 0.01 280)" },
  { id: "blanc",  label: "Blanc",  color: "#ffffff" },
];

/* === Détecteur de qualité photo ===
   Analyse une image (dataURL + file) et appelle callback(warning) :
   - warning = string si problème détecté, null sinon
   - on autorise toujours l'upload, on prévient juste l'utilisateur
   Critères de "faible qualité" :
   - Résolution < 800×600px
   - Poids fichier < 30 KB (probablement compressé / vieux JPEG)
   - Ratio bizarre (très petit côté < 400px)
*/
const checkImageQuality = (dataUrl, file, label, callback) => {
  try {
    const img = new Image();
    img.onload = () => {
      const w = img.naturalWidth;
      const h = img.naturalHeight;
      const minSide = Math.min(w, h);
      const fileSize = file.size;
      const issues = [];
      if (w < 800 || h < 600) issues.push(`résolution ${w}×${h}px (idéal : 1200×800 ou +)`);
      else if (minSide < 600) issues.push(`côté trop court (${minSide}px)`);
      if (fileSize < 30_000) issues.push("fichier très léger (peut être flou à l'agrandissement)");
      if (issues.length === 0) {
        callback(null);
        return;
      }
      callback(`⚠️ ${label} : qualité faible, ${issues.join(" · ")}. Vous pouvez la garder, mais une photo plus grande donnera un meilleur rendu.`);
    };
    img.onerror = () => callback(null);
    img.src = dataUrl;
  } catch {
    callback(null);
  }
};

const DEFAULT_PAGE_CUSTOM = {
  theme: "indigo",
  buttonColor: "auto", // "auto" = couleur du thème, sinon une couleur unie
  background: "auto", // "auto" = clair par défaut, "soft", "warm", "dark", "cream"
  font: "modern",
  corners: "rounded", // "rounded" | "soft" | "sharp"
  // Métier / profession affiché en eyebrow au-dessus du nom sur la page
  // publique (ex : "Prothésiste ongulaire", "Coiffeuse", "Esthéticienne…").
  profession: "",
  bio: "",
  tagline: "",        // accroche courte sous le nom
  // Présentation longue de l'entreprise / boutique (paragraphes, jusqu'à 600 c)
  // Affichée dans une section dédiée "À propos" sur la page publique si non vide.
  presentation: "",
  locationLabel: "",  // ville / quartier
  banner: "", // data URL
  gallery: [], // tableau de data URLs OU d'objets { src, caption } (max 4)
  galleryTitle: "Mes réalisations", // éditable par le pro
  // Couleur de fond du bloc unique de la vitrine (sous les stats) :
  // contient photo + bouton réserver + comment ça marche + avis détaillés.
  // Pastel pour ne pas dominer le reste de la page.
  vitrineBlockBg: "gris", // "gris" | "rose" | "bleu" | "vert" | "beige" | "lavande"
  socialInstagram: "",
  socialWhatsapp: "",
  websiteUrl: "",     // site externe (Linktree, portfolio, etc.)
  showReviews: false,
  showWhatsapp: false,
  showCall: false,    // bouton "Appeler" (utilise business.phone)
  showWebsite: false, // affiche websiteUrl en pill dans le header
  showMap: false,
  showSocials: false,
  // Visibilité des grandes sections, permet au pro de cacher ce qu'il n'a
  // pas envie d'afficher (par défaut tout est visible si rempli).
  showBio: true,
  showGallery: true,
  showPresentation: true,
  // Badge "vérifié par ClientBase" (à côté du nom). Visible par défaut
  //, le pro peut le masquer (option premium). En bêta gratuite tout
  // le monde a accès, on affiche juste une note "disponible Pro".
  showVerifiedBadge: true,
};

// Palette de fonds prêts à l'emploi, gardés sobres pour ne pas écraser
// la couleur de thème. `auto` = utilise le bg du theme courant.
const PAGE_BACKGROUNDS = [
  { id: "auto",  label: "Auto",   swatch: "var(--bg)",                bg: null },
  { id: "soft",  label: "Doux",   swatch: "oklch(98% 0.01 270)",      bg: "oklch(98% 0.01 270)" },
  { id: "cream", label: "Crème",  swatch: "oklch(97% 0.02 80)",       bg: "oklch(97% 0.02 80)" },
  { id: "sage",  label: "Sauge",  swatch: "oklch(96% 0.02 160)",      bg: "oklch(96% 0.02 160)" },
  { id: "blush", label: "Blush",  swatch: "oklch(96% 0.025 20)",      bg: "oklch(96% 0.025 20)" },
  { id: "dark",  label: "Sombre", swatch: "oklch(22% 0.01 270)",      bg: "oklch(22% 0.01 270)", ink: "#fff" },
];
// Rayon de bordure selon le style de coins choisi
const CORNER_RADIUS = { rounded: 999, soft: 14, sharp: 4 };
const GALLERY_MAX = 4;

/* === Message mobile, page déjà prête (template par défaut), perso poussée sur ordi === */
const PageMobileNotice = ({ data }) => {
  const slug = (data.business && data.business.bookingSlug) || cbAuth.ensureBookingSlug();
  const url = `${window.location.origin}/?book=${slug || ""}`;
  const ownerName = (data.business && (data.business.owner || data.business.businessName)) || "votre activité";
  return (
    <div style={{
      display: "flex", flexDirection: "column",
      alignItems: "center", textAlign: "center", padding: "20px 14px 32px",
    }}>
      {/* Pastille dégradée */}
      <div style={{
        width: 72, height: 72, borderRadius: 20, marginBottom: 18,
        background: "linear-gradient(135deg, var(--accent) 0%, oklch(48% 0.20 300) 100%)",
        color: "#fff", display: "inline-flex", alignItems: "center", justifyContent: "center",
        boxShadow: "0 18px 40px -16px var(--accent)",
      }}>
        <Icon name="eye" size={32} stroke={1.8}/>
      </div>

      <h3 style={{
        margin: 0, fontFamily: "var(--ff-display)", fontSize: 21, fontWeight: 580,
        letterSpacing: "-0.022em", maxWidth: 340, lineHeight: 1.25,
      }}>
        Votre page est déjà en ligne ✨
      </h3>
      <p style={{
        margin: "9px 0 0", fontSize: 13.5, color: "var(--ink-3)",
        lineHeight: 1.6, maxWidth: 340,
      }}>
        Nous avons monté un <strong style={{ color: "var(--ink-2)" }}>template par défaut</strong> à
        partir des informations de {ownerName}, vos clientes peuvent déjà réserver depuis le lien ci-dessous.
      </p>

      {/* Carte info perso poussée */}
      <div style={{
        width: "100%", maxWidth: 360, marginTop: 18,
        padding: "13px 14px", textAlign: "left",
        background: "var(--surface)", border: "1px solid var(--line)",
        borderLeft: "3px solid var(--accent)", borderRadius: 12,
      }}>
        <div style={{ display: "flex", alignItems: "center", gap: 8, fontSize: 13.5, fontWeight: 580, color: "var(--ink)" }}>
          <Icon name="sparkle" size={14}/> Pour personnaliser plus
        </div>
        <p style={{ margin: "6px 0 0", fontSize: 12.5, color: "var(--ink-3)", lineHeight: 1.55 }}>
          Couleurs, bannière, galerie, accroche, bio… ça se règle depuis un ordinateur.
          L'aperçu en temps réel a besoin d'un grand écran pour rester lisible
          pendant que vous ajustez chaque détail.
        </p>
      </div>

      {/* Actions */}
      <div style={{
        display: "flex", flexDirection: "column", gap: 9,
        marginTop: 18, width: "100%", maxWidth: 360,
      }}>
        <a href={url} target="_blank" rel="noopener"
           className="btn btn-accent cb-press-feedback"
           style={{ width: "100%", justifyContent: "center", textDecoration: "none" }}>
          <Icon name="external" size={14}/> Voir ma page publique
        </a>
        <button
          onClick={() => {
            try {
              if (navigator.share) {
                navigator.share({ title: "ClientBase", url: window.location.origin });
              } else if (navigator.clipboard) {
                navigator.clipboard.writeText(window.location.origin);
                showToast("Lien copié");
              }
            } catch {}
          }}
          className="btn btn-ghost cb-press-feedback"
          style={{ width: "100%", justifyContent: "center" }}>
          M'envoyer le lien pour ouvrir sur ordi
        </button>
      </div>

      {/* URL discrète */}
      <div style={{
        marginTop: 16, fontSize: 11.5,
        color: "var(--ink-4)", maxWidth: 340,
        overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
      }}>
        {url}
      </div>
    </div>
  );
};

/* === Éditeur desktop === */
const AppPage = ({ data, actions }) => {
  const isMobile = useIsMobile(900);

  // Pas de force-reload ici : la version précédente appelait
  // actions.updateBookingSettings(fresh.bookingSettings) au mount, ce qui
  // re-push vers Supabase + écrase les édits en cours dans une race
  // condition. useAppData fait déjà un loadAll() au mount de AppShell et
  // le realtime cb-cloud.subscribe (booking_settings + businesses) garde
  // le state à jour si on édite depuis un autre onglet/device.

  if (isMobile) return <PageMobileNotice data={data}/>;

  const bs = data.bookingSettings || {};
  const pc = { ...DEFAULT_PAGE_CUSTOM, ...(bs.pageCustom || {}) };
  const slug = (data.business && data.business.bookingSlug) || cbAuth.ensureBookingSlug() || "";
  const url = `${window.location.origin}/?book=${slug}`;

  // Indicateur d'état : idle | saving | saved
  // L'écriture passe par actions.updateBookingSettings (state local + cloud).
  // On debounce uniquement le passage saving → saved pour ne pas faire
  // clignoter l'indicateur à chaque frappe clavier.
  const [saveState, setSaveState] = React.useState("idle");
  const _savedTimer = React.useRef(null);
  // Helper qui déclenche le saveState ET appelle l'action, utilisé par
  // patch() pour pageCustom + par updateBusiness wrapper pour le nom.
  const trackSave = (fn) => {
    setSaveState("saving");
    fn();
    if (_savedTimer.current) clearTimeout(_savedTimer.current);
    _savedTimer.current = setTimeout(() => setSaveState("saved"), 650);
  };
  const patch = (changes) => {
    trackSave(() => actions.updateBookingSettings({ pageCustom: { ...pc, ...changes } }));
  };
  // Wrapper updateBusiness : déclenche l'indicateur saveState pour que
  // l'édition du nom/prénom soit visible (sinon le pro pense que ça ne
  // se sauvegarde pas car aucun feedback).
  const updateBusinessTracked = (patchObj) => {
    trackSave(() => actions.updateBusiness(patchObj));
  };
  React.useEffect(() => () => { if (_savedTimer.current) clearTimeout(_savedTimer.current); }, []);

  // upload bannière → data URL + check qualité
  const onPickBanner = (e) => {
    const file = e.target.files && e.target.files[0];
    if (!file) return;
    if (file.size > 2_000_000) {
      showToast("Image trop lourde (max 2 Mo)", "warn");
      return;
    }
    const reader = new FileReader();
    reader.onload = () => {
      const dataUrl = reader.result;
      // Check qualité en chargeant l'image dans un <img>
      checkImageQuality(dataUrl, file, "Bannière", (warning) => {
        patch({ banner: dataUrl });
        if (warning) showToast(warning, "warn");
      });
    };
    reader.readAsDataURL(file);
  };

  // upload photo de profil (avatar) → data URL stocké dans business.avatar
  const onPickAvatar = (e) => {
    const file = e.target.files && e.target.files[0];
    if (!file) return;
    if (file.size > 1_500_000) {
      showToast("Photo trop lourde (max 1,5 Mo)", "warn");
      return;
    }
    const reader = new FileReader();
    reader.onload = () => {
      const dataUrl = reader.result;
      checkImageQuality(dataUrl, file, "Photo de profil", (warning) => {
        updateBusinessTracked({ avatar: dataUrl });
        if (warning) showToast(warning, "warn");
      });
    };
    reader.readAsDataURL(file);
  };

  return (
    <div style={{ display: "grid", gridTemplateColumns: "minmax(0, 400px) 1fr", gap: 24, alignItems: "start" }}>

      {/* ============ Panneau de contrôle (gauche) ============ */}
      <div style={{ display: "flex", flexDirection: "column", gap: 16, minWidth: 0 }}>

        {/* Identité */}
        <EditorCard num={1} title="Identité">
          {/* === Photo de profil, upload + forme === */}
          <EditorLabel>Photo de profil</EditorLabel>
          <div style={{ display: "flex", gap: 12, alignItems: "center", marginBottom: 10 }}>
            {/* Aperçu de l'avatar (image OU initiales sur fond coloré) */}
            <label style={{
              position: "relative", cursor: "pointer", flexShrink: 0,
              width: 64, height: 64,
              borderRadius: pc.avatarShape === "square" ? 14 : "50%",
              background: (data.business && data.business.avatar)
                ? `url(${data.business.avatar}) center/cover`
                : `linear-gradient(135deg, oklch(72% 0.16 ${(data.business && data.business.hue) || 30}), oklch(60% 0.20 ${(((data.business && data.business.hue) || 30)+40)%360}))`,
              display: "flex", alignItems: "center", justifyContent: "center",
              color: "white", fontFamily: "var(--ff-display)",
              fontSize: 22, fontWeight: 600,
              boxShadow: "var(--sh-1)",
              border: "1px solid var(--line)",
            }}>
              <input type="file" accept="image/*" onChange={onPickAvatar}
                style={{ position: "absolute", inset: 0, opacity: 0, cursor: "pointer" }}/>
              {!(data.business && data.business.avatar) &&
                ((data.business && (data.business.owner || data.business.businessName)) || "?")
                  .split(" ").map(x => x[0]).slice(0, 2).join("").toUpperCase()
              }
            </label>
            <div style={{ flex: 1, minWidth: 0 }}>
              <div style={{ display: "flex", gap: 6 }}>
                <label style={{ flex: 1, cursor: "pointer" }}>
                  <input type="file" accept="image/*" onChange={onPickAvatar}
                    style={{ display: "none" }}/>
                  <span className="btn btn-ghost" style={{
                    width: "100%", padding: "8px 12px", fontSize: 12.5,
                    justifyContent: "center", cursor: "pointer",
                  }}>
                    <Icon name="sparkle" size={12}/>
                    {(data.business && data.business.avatar) ? "Changer" : "Ajouter une photo"}
                  </span>
                </label>
                {(data.business && data.business.avatar) && (
                  <button onClick={() => updateBusinessTracked({ avatar: "" })}
                    style={{
                      padding: "8px 12px", fontSize: 12.5,
                      background: "var(--bg-alt)", border: "1px solid var(--line)",
                      borderRadius: 8, cursor: "pointer", color: "var(--ink-3)",
                      fontFamily: "inherit",
                    }}>
                    Retirer
                  </button>
                )}
              </div>
              <p style={{ margin: "5px 0 0", fontSize: 10.5, color: "var(--ink-4)", lineHeight: 1.4 }}>
                Carrée, ~ 400×400 px. Max 1,5 Mo.
              </p>
            </div>
          </div>
          <div style={{ marginBottom: 14 }}>
            <EditorLabel>Forme</EditorLabel>
            <div style={{ display: "flex", gap: 6 }}>
              {[
                { id: "round",  label: "Rond" },
                { id: "square", label: "Carré arrondi" },
              ].map(o => {
                const active = (pc.avatarShape || "round") === o.id;
                return (
                  <button key={o.id} onClick={() => patch({ avatarShape: o.id })}
                    style={{
                      flex: 1, padding: "9px 10px",
                      background: active ? "var(--accent-soft)" : "var(--bg-alt)",
                      border: active ? "1px solid var(--accent)" : "1px solid var(--line)",
                      color: active ? "var(--accent-ink)" : "var(--ink-2)",
                      borderRadius: 9, fontSize: 12.5, fontWeight: active ? 600 : 540,
                      cursor: "pointer", fontFamily: "inherit",
                    }}>
                    {o.label}
                  </button>
                );
              })}
            </div>
          </div>

          {/* === Bannière (image de couverture) === */}
          <EditorLabel>Bannière de couverture</EditorLabel>
          <label style={{
            display: "block", height: 90, marginBottom: 16,
            background: pc.banner
              ? `url(${pc.banner}) center/cover`
              : "linear-gradient(135deg, var(--accent-soft) 0%, var(--bg-alt) 100%)",
            borderRadius: 11, position: "relative", cursor: "pointer",
            border: "1px dashed var(--line-strong)", overflow: "hidden",
          }}>
            <input type="file" accept="image/*" onChange={onPickBanner}
                   style={{ position: "absolute", inset: 0, opacity: 0, cursor: "pointer" }}/>
            <div style={{
              position: "absolute", left: "50%", top: "50%", transform: "translate(-50%, -50%)",
              padding: "5px 11px", background: "rgba(0,0,0,0.55)", color: "#fff",
              borderRadius: 999, fontSize: 11, fontWeight: 540,
              display: "inline-flex", alignItems: "center", gap: 5,
              backdropFilter: "blur(6px)",
            }}>
              <Icon name="sparkle" size={11}/>
              {pc.banner ? "Changer la bannière" : "Ajouter une bannière"}
            </div>
            {pc.banner && (
              <button onClick={e => { e.preventDefault(); e.stopPropagation(); patch({ banner: "" }); }}
                style={{
                  position: "absolute", top: 6, right: 6, padding: "3px 7px",
                  background: "rgba(255,255,255,0.95)", color: "var(--ink)",
                  border: "none", borderRadius: 999, fontSize: 10.5, fontWeight: 540,
                  cursor: "pointer",
                }}>
                Retirer
              </button>
            )}
          </label>

          {/* === Texte === */}
          <EditorField label="Nom affiché"
            value={(data.business && data.business.businessName) || ""}
            placeholder="Nom de votre activité"
            onChange={v => updateBusinessTracked({ businessName: v })}
          />
          <EditorField label="Métier / profession"
            value={pc.profession || ""}
            placeholder="ex. Prothésiste ongulaire, Coiffeuse, Tatoueuse…"
            onChange={v => patch({ profession: v.slice(0, 50) })}
          />
          <EditorField label="Accroche (sous le nom)"
            value={pc.tagline || ""}
            placeholder="ex. Soins minutieux · ambiance cosy"
            onChange={v => patch({ tagline: v.slice(0, 60) })}
          />
          <EditorTextarea label="Mini-bio"
            value={pc.bio}
            placeholder="Présentez-vous en 2-3 phrases. Affiché en haut de votre page."
            onChange={v => patch({ bio: v.slice(0, 200) })}
            maxLength={200}
          />
          <EditorField label="Ville / quartier"
            value={pc.locationLabel || ""}
            placeholder="ex. Lyon 6e · à domicile"
            onChange={v => patch({ locationLabel: v.slice(0, 50) })}
          />
          <EditorField label="Spécialités (3 à 6 mots-clés, séparés par des virgules)"
            value={pc.tags || ""}
            placeholder="ex. Gel, Acrygel, Nail-art"
            onChange={v => patch({ tags: v.slice(0, 120) })}
          />
          {/* Couleur d'avatar (et de la bande derrière), modifie business.hue */}
          <div>
            <EditorLabel>Couleur de l'avatar & bande</EditorLabel>
            <div style={{ display: "grid", gridTemplateColumns: "repeat(8, 1fr)", gap: 5 }}>
              {[
                { h: 0,   label: "Rouge"   },
                { h: 30,  label: "Orange"  },
                { h: 60,  label: "Jaune"   },
                { h: 140, label: "Vert"    },
                { h: 195, label: "Teal"    },
                { h: 240, label: "Bleu"    },
                { h: 290, label: "Violet"  },
                { h: 340, label: "Rose"    },
              ].map(c => {
                const currentHue = (data.business && data.business.hue) != null ? Number(data.business.hue) : 30;
                const active = currentHue === c.h;
                return (
                  <button key={c.h} onClick={() => updateBusinessTracked({ hue: c.h })}
                    title={c.label}
                    style={{
                      aspectRatio: "1", borderRadius: 8,
                      background: `linear-gradient(135deg, oklch(72% 0.16 ${c.h}), oklch(60% 0.20 ${(c.h+40)%360}))`,
                      border: active ? "2px solid var(--ink)" : "2px solid transparent",
                      boxShadow: active ? `0 0 0 2px var(--surface) inset` : "var(--sh-1)",
                      cursor: "pointer", padding: 0,
                      transition: "border-color .15s",
                    }}/>
                );
              })}
            </div>
          </div>
        </EditorCard>

        {/* Design, sélecteur "Couleur du thème" retiré : le thème reste
            indigo ClientBase fixe (visible sur badge vérifié, accents).
            Le pro contrôle Couleur des boutons + Couleur de fond séparément. */}
        <EditorCard num={2} title="Design">
          {/* Couleur des boutons */}
          <EditorLabel>Couleur des boutons</EditorLabel>
          <div style={{ display: "grid", gridTemplateColumns: "repeat(6, 1fr)", gap: 7, marginBottom: 8 }}>
            {PAGE_BUTTON_COLORS.map(bc => {
              const active = (pc.buttonColor || "auto") === bc.id;
              const isAuto = bc.id === "auto";
              const isWhite = bc.id === "blanc";
              return (
                <button key={bc.id}
                  onClick={() => patch({ buttonColor: bc.id })}
                  title={bc.label}
                  style={{
                    position: "relative",
                    aspectRatio: "1", borderRadius: 10,
                    background: isAuto
                      ? "repeating-conic-gradient(var(--bg-alt) 0% 25%, var(--surface) 0% 50%) 0 0 / 10px 10px"
                      : bc.color,
                    border: active ? "2px solid var(--ink)"
                      : isWhite ? "2px solid var(--line)" : "2px solid transparent",
                    boxShadow: active && !isAuto
                      ? `0 0 0 2px var(--surface) inset, 0 5px 12px -3px ${bc.color}`
                      : "var(--sh-1)",
                    cursor: "pointer", padding: 0,
                    transition: "border-color .15s, box-shadow .15s, transform .12s",
                  }}
                  onMouseDown={e => e.currentTarget.style.transform = "scale(0.92)"}
                  onMouseUp={e => e.currentTarget.style.transform = "scale(1)"}
                  onMouseLeave={e => e.currentTarget.style.transform = "scale(1)"}>
                  {isAuto && (
                    <span aria-hidden style={{
                      position: "absolute", inset: 0, display: "flex",
                      alignItems: "center", justifyContent: "center",
                      fontSize: 9, fontWeight: 700, color: "var(--ink-3)",
                    }}>A</span>
                  )}
                  {active && !isAuto && (
                    <span aria-hidden style={{
                      position: "absolute", inset: 0, display: "flex",
                      alignItems: "center", justifyContent: "center",
                      color: isWhite ? "var(--ink)" : "#fff", fontSize: 13, fontWeight: 700,
                      textShadow: isWhite ? "none" : "0 1px 3px rgba(0,0,0,0.5)",
                    }}>✓</span>
                  )}
                  {active && isAuto && (
                    <span aria-hidden style={{
                      position: "absolute", inset: -2, borderRadius: 11,
                      border: "2px solid var(--ink)",
                    }}/>
                  )}
                </button>
              );
            })}
          </div>
          <div style={{
            fontSize: 11.5, color: "var(--ink-3)", marginBottom: 16,
          }}>
            {(pc.buttonColor || "auto") === "auto"
              ? "Boutons : même couleur que la couleur principale"
              : `Boutons : ${PAGE_BUTTON_COLORS.find(b => b.id === pc.buttonColor)?.label || "—"}`}
          </div>

          {/* Couleur de fond, sobre par défaut, quelques teintes neutres */}
          <EditorLabel>Couleur de fond</EditorLabel>
          <div style={{ display: "grid", gridTemplateColumns: "repeat(6, 1fr)", gap: 7, marginBottom: 8 }}>
            {PAGE_BACKGROUNDS.map(bg => {
              const active = (pc.background || "auto") === bg.id;
              const isAuto = bg.id === "auto";
              return (
                <button key={bg.id}
                  onClick={() => patch({ background: bg.id })}
                  title={bg.label}
                  style={{
                    position: "relative",
                    aspectRatio: "1", borderRadius: 10,
                    background: isAuto
                      ? "repeating-conic-gradient(var(--bg-alt) 0% 25%, var(--surface) 0% 50%) 0 0 / 10px 10px"
                      : bg.swatch,
                    border: active ? "2px solid var(--ink)"
                      : "2px solid var(--line)",
                    boxShadow: active ? "0 0 0 2px var(--surface) inset, var(--sh-1)" : "var(--sh-1)",
                    cursor: "pointer", padding: 0,
                    transition: "border-color .15s, box-shadow .15s, transform .12s",
                  }}
                  onMouseDown={e => e.currentTarget.style.transform = "scale(0.92)"}
                  onMouseUp={e => e.currentTarget.style.transform = "scale(1)"}
                  onMouseLeave={e => e.currentTarget.style.transform = "scale(1)"}>
                  {isAuto && (
                    <span aria-hidden style={{
                      position: "absolute", inset: 0, display: "flex",
                      alignItems: "center", justifyContent: "center",
                      fontSize: 9, fontWeight: 700, color: "var(--ink-3)",
                    }}>A</span>
                  )}
                  {active && (
                    <span aria-hidden style={{
                      position: "absolute", inset: -2, borderRadius: 11,
                      border: "2px solid var(--ink)", pointerEvents: "none",
                    }}/>
                  )}
                </button>
              );
            })}
          </div>
          <div style={{
            fontSize: 11.5, color: "var(--ink-3)", marginBottom: 16,
          }}>
            {(pc.background || "auto") === "auto"
              ? "Fond : déterminé par la couleur principale"
              : `Fond : ${PAGE_BACKGROUNDS.find(b => b.id === pc.background)?.label || "—"}`}
          </div>

          {/* === Couleur du bloc principal de la vitrine ===
              Le bloc qui contient photo + bouton réserver + comment ça marche
              + avis. Pastel pour ne pas dominer le reste de la page. */}
          <EditorLabel>Couleur du bloc principal</EditorLabel>
          <div style={{ display: "grid", gridTemplateColumns: "repeat(6, 1fr)", gap: 6, marginBottom: 16 }}>
            {[
              { id: "gris",    label: "Gris",    swatch: "oklch(96% 0.005 270)" },
              { id: "rose",    label: "Rose",    swatch: "oklch(96% 0.03 350)"  },
              { id: "bleu",    label: "Bleu",    swatch: "oklch(96% 0.03 230)"  },
              { id: "vert",    label: "Vert",    swatch: "oklch(96% 0.03 160)"  },
              { id: "beige",   label: "Beige",   swatch: "oklch(96% 0.03 80)"   },
              { id: "lavande", label: "Lavande", swatch: "oklch(96% 0.03 290)"  },
            ].map(o => {
              const active = (pc.vitrineBlockBg || "gris") === o.id;
              return (
                <button key={o.id} onClick={() => patch({ vitrineBlockBg: o.id })}
                  title={o.label}
                  style={{
                    aspectRatio: "1", borderRadius: 9,
                    background: o.swatch,
                    border: active ? "2px solid var(--ink)" : "2px solid var(--line)",
                    boxShadow: active ? "0 0 0 2px var(--surface) inset" : "var(--sh-1)",
                    cursor: "pointer", padding: 0,
                  }}/>
              );
            })}
          </div>

          <EditorLabel>Typographie</EditorLabel>
          <div style={{ display: "grid", gridTemplateColumns: "repeat(2, 1fr)", gap: 6, marginBottom: 14 }}>
            {PAGE_FONTS.map(f => (
              <button key={f.id}
                onClick={() => patch({ font: f.id })}
                style={{
                  padding: "9px 11px", borderRadius: 9,
                  background: pc.font === f.id ? "var(--accent-soft)" : "var(--bg-alt)",
                  border: pc.font === f.id ? "1px solid var(--accent)" : "1px solid var(--line)",
                  color: pc.font === f.id ? "var(--accent-ink)" : "var(--ink-2)",
                  fontFamily: f.css, fontSize: 13, fontWeight: pc.font === f.id ? 600 : 540,
                  cursor: "pointer",
                  textAlign: "left",
                  transition: "background .15s, border-color .15s, color .15s, transform .12s",
                  letterSpacing: "-0.005em",
                }}
                onMouseDown={e => e.currentTarget.style.transform = "scale(0.97)"}
                onMouseUp={e => e.currentTarget.style.transform = "scale(1)"}
                onMouseLeave={e => e.currentTarget.style.transform = "scale(1)"}>
                {f.label}
              </button>
            ))}
          </div>

          {/* Style des coins */}
          <EditorLabel>Style des coins</EditorLabel>
          <div style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 6, marginBottom: 16 }}>
            {[
              { id: "rounded", label: "Arrondi", r: 999 },
              { id: "soft",    label: "Doux",    r: 12 },
              { id: "sharp",   label: "Net",     r: 3 },
            ].map(o => {
              const active = (pc.corners || "rounded") === o.id;
              return (
                <button key={o.id}
                  onClick={() => patch({ corners: o.id })}
                  style={{
                    padding: "10px 8px",
                    background: active ? "var(--accent-soft)" : "var(--bg-alt)",
                    border: active ? "1px solid var(--accent)" : "1px solid var(--line)",
                    color: active ? "var(--accent-ink)" : "var(--ink-2)",
                    cursor: "pointer", fontFamily: "inherit",
                    display: "flex", flexDirection: "column", alignItems: "center", gap: 6,
                    borderRadius: 9, transition: "all .12s ease",
                  }}
                  onMouseDown={e => e.currentTarget.style.transform = "scale(0.96)"}
                  onMouseUp={e => e.currentTarget.style.transform = "scale(1)"}
                  onMouseLeave={e => e.currentTarget.style.transform = "scale(1)"}>
                  <span style={{
                    width: 30, height: 18,
                    background: active ? "var(--accent)" : "var(--line-strong)",
                    borderRadius: o.r,
                  }}/>
                  <span style={{ fontSize: 11.5, fontWeight: active ? 600 : 540 }}>{o.label}</span>
                </button>
              );
            })}
          </div>
        </EditorCard>

        {/* Galerie photos */}
        <EditorCard num={3} title="Galerie photos">
          <EditorField label="Titre de la galerie"
            value={pc.galleryTitle || ""}
            placeholder="ex. Mes réalisations, Mon univers, Avant/après…"
            onChange={v => patch({ galleryTitle: v.slice(0, 40) })}
          />
          <p style={{
            margin: "-4px 0 12px", fontSize: 12, color: "var(--ink-3)", lineHeight: 1.5,
          }}>
            Jusqu'à {GALLERY_MAX} photos affichées en grille. Cliquez sur
            une vignette pour ajouter une légende personnalisée.
          </p>
          <GalleryEditor photos={pc.gallery} onChange={g => patch({ gallery: g })}/>
        </EditorCard>

        {/* Sections */}
        <EditorCard num={4} title="Sections & contacts">
          {/* Visibilité des sections principales, permet de masquer un bloc
              non pertinent (ex : pas de présentation à raconter, pas de photos) */}
          <div style={{
            padding: "10px 12px", marginBottom: 12,
            background: "var(--accent-soft)", borderRadius: 9,
            fontSize: 11.5, color: "var(--accent-ink)", lineHeight: 1.4,
          }}>
            Cochez ce que vous voulez afficher sur votre page publique.
          </div>
          <EditorToggle label="Mini-bio (header)"
            value={pc.showBio !== false} onChange={v => patch({ showBio: v })}/>
          <EditorToggle label="Galerie photos"
            value={pc.showGallery !== false} onChange={v => patch({ showGallery: v })}/>
          {/* Badge "vérifié par ClientBase", option premium (plan Pro+).
              Pendant la bêta gratuite, l'option est dispo pour tous mais on
              indique que c'est normalement réservé Pro. */}
          <div style={{
            padding: "9px 12px", marginTop: 6,
            background: "var(--bg-alt)", borderRadius: 9,
          }}>
            <EditorToggle label="Badge « Vérifié » à côté du nom"
              value={pc.showVerifiedBadge !== false}
              onChange={v => patch({ showVerifiedBadge: v })}/>
            <div style={{
              marginTop: 4, padding: "5px 9px",
              background: "var(--accent-soft)", borderRadius: 6,
              fontSize: 10.5, color: "var(--accent-ink)", fontWeight: 540,
              display: "inline-flex", alignItems: "center", gap: 5,
            }}>
              <Icon name="zap" size={10} stroke={2.2}/>
              Plan Pro · gratuit en bêta
            </div>
          </div>
          {/* Toggle Plan Pro : cache la pub ClientBase au bas de la page
              de réservation publique. Même structure que Badge Vérifié. */}
          <div style={{
            padding: "9px 12px", marginTop: 8,
            background: "var(--bg-alt)", borderRadius: 9,
          }}>
            <EditorToggle label="Pas de pub ClientBase en bas de page"
              value={pc.hideClientBaseAd === true}
              onChange={v => patch({ hideClientBaseAd: v })}/>
            <div style={{
              marginTop: 4, padding: "5px 9px",
              background: "var(--accent-soft)", borderRadius: 6,
              fontSize: 10.5, color: "var(--accent-ink)", fontWeight: 540,
              display: "inline-flex", alignItems: "center", gap: 5,
            }}>
              <Icon name="zap" size={10} stroke={2.2}/>
              Plan Pro · gratuit en bêta
            </div>
          </div>
          <div style={{
            margin: "8px 0 12px", height: 1, background: "var(--line)",
          }}/>
          <EditorToggle label="Bouton WhatsApp direct"
            value={pc.showWhatsapp} onChange={v => patch({ showWhatsapp: v })}/>
          {pc.showWhatsapp && (
            <div style={{ marginLeft: 0, marginTop: -2, marginBottom: 10 }}>
              <input type="tel"
                value={pc.socialWhatsapp || ""}
                placeholder="06 12 34 56 78 (votre numéro WhatsApp)"
                onChange={e => patch({ socialWhatsapp: e.target.value })}
                style={{
                  width: "100%", padding: "8px 12px",
                  background: "var(--bg-alt)", border: "1px solid var(--line)", borderRadius: 9,
                  fontSize: 12.5, fontFamily: "inherit", color: "var(--ink)",
                }}/>
            </div>
          )}
          <EditorToggle label="Réseaux sociaux"
            value={pc.showSocials} onChange={v => patch({ showSocials: v })}/>
          {pc.showSocials && (
            <div style={{ marginTop: -2, marginBottom: 10, display: "flex", flexDirection: "column", gap: 8 }}>
              {[
                { key: "socialInstagram", name: "Instagram", chip: "IG", grad: "linear-gradient(135deg,#F58529,#DD2A7B,#8134AF)", ph: "votre_pseudo", at: true },
                { key: "socialTiktok",    name: "TikTok",    chip: "TT", grad: "#000",                                      ph: "votre_pseudo", at: true },
                { key: "socialFacebook",  name: "Facebook",  chip: "FB", grad: "#1877F2",                                  ph: "nom de page ou URL", at: false },
                { key: "socialSnapchat",  name: "Snapchat",  chip: "SC", grad: "#FFFC00", dark: true,                      ph: "votre_pseudo", at: true },
              ].map(net => {
                const filled = !!(pc[net.key] || "").trim();
                return (
                  <div key={net.key} style={{ display: "flex", alignItems: "center", gap: 9 }}>
                    <span aria-hidden style={{
                      width: 30, height: 30, borderRadius: 8, flexShrink: 0,
                      background: net.grad, color: net.dark ? "#000" : "#fff",
                      display: "inline-flex", alignItems: "center", justifyContent: "center",
                      fontSize: 11, fontWeight: 700, letterSpacing: "0.02em",
                    }}>{net.chip}</span>
                    <div style={{ position: "relative", flex: 1 }}>
                      {net.at && (
                        <span style={{
                          position: "absolute", left: 11, top: "50%", transform: "translateY(-50%)",
                          fontSize: 12.5, color: "var(--ink-4)", pointerEvents: "none",
                        }}>@</span>
                      )}
                      <input type="text"
                        value={pc[net.key] || ""}
                        placeholder={`${net.name} · ${net.ph}`}
                        onChange={e => patch({ [net.key]: net.at ? e.target.value.replace(/^@/, "") : e.target.value })}
                        style={{
                          width: "100%", padding: net.at ? "8px 12px 8px 24px" : "8px 12px",
                          background: "var(--bg-alt)",
                          border: `1px solid ${filled ? "var(--accent)" : "var(--line)"}`,
                          borderRadius: 9, fontSize: 12.5, fontFamily: "inherit", color: "var(--ink)",
                        }}/>
                    </div>
                    {filled && <Icon name="check" size={15} stroke={2.4} style={{ color: "var(--accent)", flexShrink: 0 }}/>}
                  </div>
                );
              })}
              <p style={{ margin: "2px 0 0", fontSize: 11, color: "var(--ink-4)", lineHeight: 1.45 }}>
                Renseignez un compte pour qu'un bouton vers ce réseau apparaisse sur votre page.
              </p>
            </div>
          )}
          {/* Bouton "Appeler", utilise le téléphone du business (paramètres) */}
          <EditorToggle label="Bouton Appeler"
            value={pc.showCall} onChange={v => patch({ showCall: v })}/>
          {pc.showCall && (
            <div style={{
              marginTop: -2, marginBottom: 10, padding: "8px 12px",
              background: "var(--bg-alt)", border: "1px solid var(--line)", borderRadius: 9,
              fontSize: 11.5, color: (data.business && data.business.phone) ? "var(--ink-3)" : "oklch(55% 0.18 30)",
              lineHeight: 1.45,
            }}>
              {(data.business && data.business.phone)
                ? <>📞 Utilise <strong>{data.business.phone}</strong> (modifiable dans Paramètres)</>
                : <>⚠️ Ajoutez votre numéro dans Paramètres pour activer ce bouton.</>}
            </div>
          )}
          {/* Lien site web / Linktree */}
          <EditorToggle label="Lien site web"
            value={pc.showWebsite} onChange={v => patch({ showWebsite: v })}/>
          {pc.showWebsite && (
            <div style={{ marginTop: -2, marginBottom: 10 }}>
              <input type="url"
                value={pc.websiteUrl || ""}
                placeholder="https://monsite.fr (ou Linktree, portfolio…)"
                onChange={e => patch({ websiteUrl: e.target.value.trim() })}
                style={{
                  width: "100%", padding: "8px 12px",
                  background: "var(--bg-alt)", border: "1px solid var(--line)", borderRadius: 9,
                  fontSize: 12.5, fontFamily: "inherit", color: "var(--ink)",
                }}/>
            </div>
          )}
          {/* Réseau social préféré, demandé à la cliente lors de la réservation */}
          <div style={{ marginTop: 6, marginBottom: 6 }}>
            <EditorLabel>Réseau social demandé à la réservation</EditorLabel>
            <select value={bs.preferredSocial || "instagram"}
              onChange={e => actions.updateBookingSettings({ preferredSocial: e.target.value })}
              style={{
                width: "100%", padding: "9px 12px",
                background: "var(--bg-alt)", border: "1px solid var(--line)", borderRadius: 9,
                fontSize: 13, fontFamily: "inherit", color: "var(--ink)", cursor: "pointer",
              }}>
              <option value="instagram">Instagram</option>
              <option value="facebook">Facebook</option>
              <option value="tiktok">TikTok</option>
              <option value="snapchat">Snapchat</option>
              <option value="whatsapp">WhatsApp</option>
              <option value="none">Aucun, ne pas demander</option>
            </select>
            <p style={{ margin: "5px 0 0", fontSize: 11.5, color: "var(--ink-4)", lineHeight: 1.45 }}>
              Vos clientes pourront indiquer leur pseudo sur ce réseau pour faciliter les échanges.
            </p>
          </div>
          <EditorToggle label="Bouton avis Google"
            value={pc.showReviews} onChange={v => patch({ showReviews: v })}/>
          {pc.showReviews && (
            <div style={{ marginTop: -2, marginBottom: 10 }}>
              <input type="url"
                value={pc.googleReviewUrl || ""}
                placeholder="Lien de votre fiche Google (ex. g.page/votre-salon)"
                onChange={e => patch({ googleReviewUrl: e.target.value.trim() })}
                style={{
                  width: "100%", padding: "8px 12px",
                  background: "var(--bg-alt)", border: "1px solid var(--line)", borderRadius: 9,
                  fontSize: 12.5, fontFamily: "inherit", color: "var(--ink)",
                }}/>
              <p style={{ margin: "5px 0 0", fontSize: 11, color: "var(--ink-4)", lineHeight: 1.45 }}>
                Un bouton « Voir nos avis Google » mènera vos clientes vers cette fiche.
              </p>
            </div>
          )}
          <EditorToggle label="Carte du salon" hint="bientôt"
            value={pc.showMap} onChange={v => patch({ showMap: v })} soon/>
        </EditorCard>

        {/* ───────── Forfaits & abonnements clients ───────── */}
        <EditorCard num={5} title="Forfaits & abonnements">
          <p style={{ margin: "-4px 0 12px", fontSize: 12, color: "var(--ink-3)", lineHeight: 1.5 }}>
            Vendez des lots de séances (ex. « 10 séances »). Ils s'affichent sur votre page,
            achetables en ligne.
          </p>
          {(pc.forfaits || []).map(f => (
            <div key={f.id} style={{
              display: "grid", gridTemplateColumns: "1fr 64px 70px 30px", gap: 6, alignItems: "center", marginBottom: 8,
            }}>
              <input type="text" value={f.name} placeholder="Nom (ex. Forfait pose)"
                onChange={e => patch({ forfaits: (pc.forfaits || []).map(x => x.id === f.id ? { ...x, name: e.target.value } : x) })}
                style={{ padding: "8px 10px", background: "var(--bg-alt)", border: "1px solid var(--line)", borderRadius: 8, fontSize: 12.5, fontFamily: "inherit", color: "var(--ink)", minWidth: 0 }}/>
              <div style={{ position: "relative" }}>
                <input type="number" min="1" value={f.sessions} aria-label="Nombre de séances"
                  onChange={e => patch({ forfaits: (pc.forfaits || []).map(x => x.id === f.id ? { ...x, sessions: Math.max(1, Number(e.target.value) || 1) } : x) })}
                  style={{ width: "100%", padding: "8px 6px", background: "var(--bg-alt)", border: "1px solid var(--line)", borderRadius: 8, fontSize: 12.5, fontFamily: "inherit", color: "var(--ink)", textAlign: "center" }}/>
              </div>
              <div style={{ position: "relative" }}>
                <input type="number" min="0" value={f.price} aria-label="Prix en euros"
                  onChange={e => patch({ forfaits: (pc.forfaits || []).map(x => x.id === f.id ? { ...x, price: Math.max(0, Number(e.target.value) || 0) } : x) })}
                  style={{ width: "100%", padding: "8px 16px 8px 8px", background: "var(--bg-alt)", border: "1px solid var(--line)", borderRadius: 8, fontSize: 12.5, fontFamily: "inherit", color: "var(--ink)", textAlign: "right" }}/>
                <span style={{ position: "absolute", right: 7, top: "50%", transform: "translateY(-50%)", fontSize: 12, color: "var(--ink-4)", pointerEvents: "none" }}>€</span>
              </div>
              <button onClick={() => patch({ forfaits: (pc.forfaits || []).filter(x => x.id !== f.id) })}
                aria-label="Supprimer" title="Supprimer" style={{
                  width: 30, height: 30, borderRadius: 8, border: "1px solid var(--line)", background: "var(--surface)",
                  color: "var(--ink-4)", cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center",
                }}>
                <Icon name="trash" size={13}/>
              </button>
            </div>
          ))}
          <button onClick={() => patch({ forfaits: [...(pc.forfaits || []), { id: "f" + Date.now().toString(36), name: "", sessions: 10, price: 0 }] })}
            style={{
              width: "100%", marginTop: 4, padding: "9px", background: "var(--accent-soft)", color: "var(--accent-ink)",
              border: "1px dashed var(--accent)", borderRadius: 9, fontSize: 13, fontWeight: 560, cursor: "pointer",
              fontFamily: "inherit", display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 6,
            }}>
            <Icon name="plus" size={14}/> Ajouter un forfait
          </button>
          {(pc.forfaits || []).length > 0 && (
            <div style={{ fontSize: 11, color: "var(--ink-4)", marginTop: 8, lineHeight: 1.45 }}>
              Colonnes : nom · nombre de séances · prix total.
            </div>
          )}
        </EditorCard>
      </div>

      {/* ============ Aperçu temps réel (droite), sticky en haut ============ */}
      <div style={{
        alignSelf: "start",
        position: "sticky",
        top: 64,
        padding: 18,
        background: "linear-gradient(180deg, oklch(96% 0.02 270), oklch(98% 0.005 270))",
        border: "1px solid var(--line)",
        borderRadius: 18,
        boxShadow: "var(--sh-2)",
      }}>
        {/* barre URL + ouvrir */}
        <div style={{ display: "flex", gap: 10, marginBottom: 16, alignItems: "center" }}>
          <div style={{
            flex: 1, padding: "8px 14px",
            background: "var(--surface)", border: "1px solid var(--line)",
            borderRadius: 999, fontFamily: "var(--ff-text)", fontSize: 13,
            color: "var(--ink-3)", display: "flex", alignItems: "center", gap: 8,
            overflow: "hidden", letterSpacing: "-0.005em",
          }}>
            <Icon name="link" size={13} style={{ flexShrink: 0, color: "var(--ink-4)" }}/>
            <span style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
              clientbase.fr/?book=<strong style={{ color: "var(--accent-ink)", fontWeight: 580 }}>{slug || "votre-lien"}</strong>
            </span>
          </div>
          <button onClick={() => actions.refreshFromCloud && actions.refreshFromCloud()}
            title="Recharger les données depuis le serveur (à utiliser si l'aperçu ne correspond pas à la page publique)"
            style={{
              background: "var(--surface)", border: "1px solid var(--line)",
              borderRadius: 999, width: 34, height: 34,
              display: "inline-flex", alignItems: "center", justifyContent: "center",
              cursor: "pointer", color: "var(--ink-3)", flexShrink: 0,
              transition: "color .15s, border-color .15s",
            }}
            onMouseEnter={e => { e.currentTarget.style.color = "var(--accent-ink)"; e.currentTarget.style.borderColor = "var(--accent-soft-2)"; }}
            onMouseLeave={e => { e.currentTarget.style.color = "var(--ink-3)"; e.currentTarget.style.borderColor = "var(--line)"; }}>
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
              <path d="M21 12a9 9 0 11-3.5-7.1L21 8M21 3v5h-5"/>
            </svg>
          </button>
          <a href={url} target="_blank" rel="noopener"
            className="btn btn-primary"
            style={{ textDecoration: "none", whiteSpace: "nowrap" }}>
            Ouvrir <Icon name="external" size={13}/>
          </a>
        </div>

        {/* Bandeau d'état de sauvegarde, bien visible au-dessus du téléphone,
            pour que le pro voit clairement que ses modifs sont prises en
            compte (auto-save : pas de bouton à cliquer, chaque édit est
            poussé à Supabase et reflété instantanément dans la preview). */}
        <div aria-live="polite" style={{
          margin: "0 0 12px",
          padding: "10px 14px",
          borderRadius: 12,
          background: saveState === "saving" ? "oklch(96% 0.04 80)"
                   : saveState === "saved"  ? "var(--sage-soft)"
                   : "var(--bg-alt)",
          border: `1px solid ${
            saveState === "saving" ? "oklch(86% 0.08 80)"
            : saveState === "saved" ? "oklch(82% 0.07 160)"
            : "var(--line)"
          }`,
          color: saveState === "saving" ? "oklch(45% 0.15 80)"
               : saveState === "saved"  ? "oklch(38% 0.10 160)"
               : "var(--ink-3)",
          fontSize: 13, fontWeight: 540,
          display: "flex", alignItems: "center", gap: 10,
          transition: "background .25s, color .25s, border-color .25s",
        }}>
          <span aria-hidden style={{
            width: 9, height: 9, borderRadius: "50%", flexShrink: 0,
            background: saveState === "saving" ? "oklch(60% 0.15 80)"
                     : saveState === "saved"  ? "var(--sage)"
                     : "var(--ink-4)",
            boxShadow: saveState === "saved" ? "0 0 0 4px color-mix(in oklab, var(--sage) 25%, transparent)" : "none",
            transition: "all .25s ease",
          }}/>
          <span style={{ flex: 1 }}>
            {saveState === "saving"
              ? "Enregistrement de vos modifications…"
              : saveState === "saved"
              ? "Modifications enregistrées · publiées sur votre page"
              : "Sauvegarde automatique active, modifiez à gauche, voyez à droite"}
          </span>
        </div>

        {/* Aperçu LIVE, vraie BookingPage rendue dans le cadre téléphone.
            Aucun écart possible avec ce que verront les clientes. */}
        <PreviewPhoneLive pc={pc} data={data} business={data.business || {}}/>

        <div style={{
          marginTop: 12, textAlign: "center",
          fontSize: 11.5, color: "var(--ink-4)", fontStyle: "italic",
        }}>
          Exactement ce que verront vos clientes sur leur téléphone
        </div>
      </div>
    </div>
  );
};

/* === Sous-composants éditeur === */
const EditorCard = ({ num, title, children }) => (
  <div style={{
    padding: 20, background: "var(--surface)",
    border: "1px solid var(--line)", borderRadius: 16,
    boxShadow: "var(--sh-1)",
  }}>
    <h3 style={{
      margin: "0 0 16px", display: "flex", alignItems: "center", gap: 10,
      fontFamily: "var(--ff-display)", fontSize: 16, fontWeight: 600,
      letterSpacing: "-0.012em",
    }}>
      <span style={{
        width: 26, height: 26, borderRadius: "50%",
        background: "var(--accent)", color: "#fff",
        display: "inline-flex", alignItems: "center", justifyContent: "center",
        fontFamily: "var(--ff-text)", fontSize: 13, fontWeight: 600,
      }}>{num}</span>
      {title}
    </h3>
    {children}
  </div>
);

const EditorLabel = ({ children }) => (
  <label style={{
    display: "block", marginBottom: 7,
    fontFamily: "var(--ff-text)", fontSize: 13.5,
    color: "var(--ink-2)", letterSpacing: "-0.005em",
    fontWeight: 580,
  }}>{children}</label>
);

const EditorField = ({ label, value, onChange, placeholder }) => (
  <div style={{ marginBottom: 14 }}>
    <EditorLabel>{label}</EditorLabel>
    <input
      type="text" value={value || ""} placeholder={placeholder}
      onChange={e => onChange(e.target.value)}
      style={{
        width: "100%", padding: "11px 14px",
        background: "var(--bg-alt)", border: "1px solid var(--line)", borderRadius: 10,
        fontSize: 14, fontFamily: "inherit", color: "var(--ink)",
      }}
    />
  </div>
);

const EditorTextarea = ({ label, value, onChange, placeholder, maxLength }) => (
  <div>
    <EditorLabel>{label}</EditorLabel>
    <textarea
      value={value || ""} placeholder={placeholder} maxLength={maxLength}
      onChange={e => onChange(e.target.value)}
      style={{
        width: "100%", padding: "11px 14px",
        background: "var(--bg-alt)", border: "1px solid var(--line)", borderRadius: 10,
        fontSize: 14, fontFamily: "inherit", color: "var(--ink)",
        minHeight: 80, resize: "vertical", lineHeight: 1.5,
      }}
    />
    {maxLength && (
      <div style={{
        textAlign: "right", marginTop: 4,
        fontFamily: "var(--ff-text)", fontSize: 11, color: "var(--ink-4)",
      }}>
        {(value || "").length} / {maxLength}
      </div>
    )}
  </div>
);

const EditorToggle = ({ label, value, onChange, hint, soon }) => (
  <div style={{
    display: "flex", justifyContent: "space-between", alignItems: "center",
    padding: "9px 12px", background: "var(--bg-alt)", borderRadius: 9,
    marginBottom: 6, cursor: soon ? "not-allowed" : "pointer",
    opacity: soon ? 0.7 : 1,
  }} onClick={() => !soon && onChange(!value)}>
    <div style={{ display: "flex", flexDirection: "column", gap: 1, minWidth: 0 }}>
      <span style={{ fontSize: 12.5, fontWeight: 520, color: "var(--ink)" }}>{label}</span>
      {hint && (
        <span style={{
          fontSize: 11, color: "var(--ink-4)", fontFamily: "var(--ff-text)",
          letterSpacing: "-0.005em",
        }}>{hint}</span>
      )}
    </div>
    <div style={{
      width: 34, height: 20, borderRadius: 999,
      background: value && !soon ? "var(--accent)" : "var(--line-strong)",
      position: "relative", flexShrink: 0,
      transition: "background 0.18s ease",
    }}>
      <div style={{
        position: "absolute", top: 2,
        left: value && !soon ? 16 : 2,
        width: 16, height: 16, borderRadius: "50%", background: "#fff",
        transition: "left 0.18s ease",
        boxShadow: "0 1px 3px rgba(0,0,0,0.2)",
      }}/>
    </div>
  </div>
);

/* === Galerie editor === */
/* Helpers galerie : on accepte les deux formats pour rétro-compat :
   - Ancien : ["dataUrl1", "dataUrl2"]
   - Nouveau : [{src: "dataUrl1", caption: "Pose gel"}, ...]
   Normaliser en {src, caption} pour les manipulations. */
const _normalizeGalleryItem = (item) =>
  typeof item === "string" ? { src: item, caption: "" } : (item || { src: "", caption: "" });

const GalleryEditor = ({ photos = [], onChange }) => {
  const items = photos.map(_normalizeGalleryItem);
  const slots = Array.from({ length: GALLERY_MAX }, (_, i) => items[i] || null);
  // Index du slot dont on édite la caption (état local, pas besoin de prop).
  const [editingIdx, setEditingIdx] = React.useState(-1);

  const handleFile = (idx, file) => {
    if (!file) return;
    if (file.size > 2_000_000) {
      showToast("Image trop lourde (max 2 Mo)", "warn");
      return;
    }
    const reader = new FileReader();
    reader.onload = () => {
      const dataUrl = reader.result;
      checkImageQuality(dataUrl, file, `Photo ${idx + 1}`, (warning) => {
        const next = [...items];
        next[idx] = { src: dataUrl, caption: (items[idx] && items[idx].caption) || "" };
        onChange(next.filter(it => it && it.src));
        if (warning) showToast(warning, "warn");
      });
    };
    reader.readAsDataURL(file);
  };

  const removeAt = (idx) => {
    const next = items.filter((_, i) => i !== idx);
    onChange(next);
    if (editingIdx === idx) setEditingIdx(-1);
  };

  const setCaption = (idx, caption) => {
    const next = [...items];
    if (!next[idx]) return;
    next[idx] = { ...next[idx], caption: caption.slice(0, 60) };
    onChange(next.filter(it => it && it.src));
  };

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8 }}>
        {slots.map((item, i) => {
          const src = item && item.src;
          const caption = (item && item.caption) || "";
          return (
            <div key={i} style={{ position: "relative" }}>
              <label style={{
                display: "block",
                position: "relative", aspectRatio: "1 / 1",
                background: src
                  ? `url(${src}) center/cover`
                  : "linear-gradient(135deg, var(--bg-alt), oklch(94% 0.01 270))",
                borderRadius: 11,
                border: src ? "1px solid var(--line)" : "1px dashed var(--line-strong)",
                cursor: "pointer", overflow: "hidden",
              }}>
                <input type="file" accept="image/*"
                  onChange={e => handleFile(i, e.target.files && e.target.files[0])}
                  style={{ position: "absolute", inset: 0, opacity: 0, cursor: "pointer" }}/>
                {!src ? (
                  <div style={{
                    position: "absolute", inset: 0,
                    display: "flex", flexDirection: "column",
                    alignItems: "center", justifyContent: "center", gap: 6,
                    color: "var(--ink-4)", fontSize: 11.5, fontWeight: 540,
                  }}>
                    <Icon name="plus" size={20} stroke={1.8}/>
                    <span>Photo {i + 1}</span>
                  </div>
                ) : (
                  <>
                    {/* Caption preview en bas de la photo (si remplie) */}
                    {caption && (
                      <div style={{
                        position: "absolute", left: 0, right: 0, bottom: 0,
                        padding: "16px 8px 6px",
                        background: "linear-gradient(180deg, transparent 0%, rgba(0,0,0,0.65) 100%)",
                        color: "white", fontSize: 11, fontWeight: 540,
                        lineHeight: 1.3,
                        overflow: "hidden", textOverflow: "ellipsis",
                        display: "-webkit-box", WebkitLineClamp: 2, WebkitBoxOrient: "vertical",
                      }}>
                        {caption}
                      </div>
                    )}
                    {/* Bouton supprimer */}
                    <button
                      onClick={e => { e.preventDefault(); e.stopPropagation(); removeAt(i); }}
                      title="Retirer cette photo"
                      style={{
                        position: "absolute", top: 6, right: 6,
                        width: 22, height: 22, borderRadius: "50%",
                        background: "rgba(0,0,0,0.65)", color: "#fff",
                        border: "none", cursor: "pointer",
                        display: "flex", alignItems: "center", justifyContent: "center",
                        backdropFilter: "blur(6px)",
                      }}>
                      <Icon name="close" size={11} stroke={2.4}/>
                    </button>
                    {/* Bouton éditer caption */}
                    <button
                      onClick={e => {
                        e.preventDefault(); e.stopPropagation();
                        setEditingIdx(editingIdx === i ? -1 : i);
                      }}
                      title={caption ? "Modifier la légende" : "Ajouter une légende"}
                      style={{
                        position: "absolute", top: 6, left: 6,
                        padding: "3px 8px", borderRadius: 999,
                        background: "rgba(0,0,0,0.65)", color: "#fff",
                        border: "none", cursor: "pointer",
                        fontSize: 10.5, fontWeight: 540, fontFamily: "inherit",
                        backdropFilter: "blur(6px)",
                      }}>
                      {caption ? "✎ Légende" : "+ Légende"}
                    </button>
                  </>
                )}
              </label>
              {/* Input caption, ouvert au clic sur le bouton "+ Légende" */}
              {src && editingIdx === i && (
                <input
                  type="text"
                  autoFocus
                  value={caption}
                  placeholder={`Légende photo ${i + 1}…`}
                  onChange={e => setCaption(i, e.target.value)}
                  onBlur={() => setEditingIdx(-1)}
                  onKeyDown={e => { if (e.key === "Enter" || e.key === "Escape") setEditingIdx(-1); }}
                  style={{
                    marginTop: 6, width: "100%", padding: "7px 10px",
                    background: "var(--bg-alt)", border: "1px solid var(--accent)",
                    borderRadius: 8, fontSize: 12, fontFamily: "inherit",
                    color: "var(--ink)", outline: "none",
                  }}/>
              )}
            </div>
          );
        })}
      </div>
    </div>
  );
};

/* === PhoneIframe : rend ses enfants DANS un iframe étroit ===
   Pourquoi un iframe ? La largeur de l'iframe devient le « viewport » interne,
   donc les media queries mobiles (@media max-width:480px) de la BookingPage se
   déclenchent naturellement → vrai rendu mobile, sans compression/déformation.
   On recopie les feuilles de style globales (tokens, thèmes, polices) dans
   l'iframe pour que les couleurs/fontes s'appliquent, puis on « porte » les
   enfants React dans le body de l'iframe (createPortal). */
const PhoneIframe = ({ children, style }) => {
  const ref = React.useRef(null);
  const [mount, setMount] = React.useState(null);
  React.useEffect(() => {
    const iframe = ref.current;
    if (!iframe) return;
    const doc = iframe.contentDocument || (iframe.contentWindow && iframe.contentWindow.document);
    if (!doc) return;
    doc.documentElement.innerHTML = "<head></head><body></body>";
    // Recopie les CSS globales (tokens.css, themes.css, polices Google).
    document.querySelectorAll('link[rel="stylesheet"]').forEach(l => {
      try { doc.head.appendChild(l.cloneNode(true)); } catch {}
    });
    const base = doc.createElement("style");
    base.textContent = "html,body{margin:0;padding:0;background:var(--bg);}*{box-sizing:border-box;}::-webkit-scrollbar{width:0;height:0;}";
    doc.head.appendChild(base);
    // Reporte le thème (clair/sombre) du dashboard sur l'iframe.
    const themeAttr = document.documentElement.getAttribute("data-theme");
    if (themeAttr) doc.documentElement.setAttribute("data-theme", themeAttr);
    // Empêche la navigation hors de l'aperçu quand on clique un lien interne.
    doc.addEventListener("click", (e) => {
      const a = e.target.closest && e.target.closest("a[href]");
      if (!a) return;
      const href = a.getAttribute("href") || "";
      if (href && !href.startsWith("#") && a.target !== "_blank") e.preventDefault();
    }, true);
    setMount(doc.body);
  }, []);
  return (
    <>
      <iframe ref={ref} title="Aperçu de votre page" scrolling="yes"
        style={{ border: "none", display: "block", ...style }}/>
      {mount && window.ReactDOM.createPortal(children, mount)}
    </>
  );
};

/* === Phone preview, VRAIE BookingPage rendue en mode preview ===
   Plus de mockup statique : on instancie <BookingPage preview={...}/> directement
   à l'intérieur du cadre téléphone (via PhoneIframe). Ce que voit le pro = ce que
   verront ses clientes. Les modifs sont propagées en direct via la prop pc. */
const PreviewPhoneLive = ({ pc, data, business }) => {
  const previewPro = {
    businessName: business.businessName || business.name || "Mon activité",
    ownerName:    business.owner || business.ownerName || "",
    bookingSlug:  business.bookingSlug || "preview",
    avatar:       business.avatar || "",
    hue:          business.hue || 30,
    phone:        business.phone || "",
    contactEmail: business.contactEmail || "",
    city:         business.city || "",
    emailVerified: true,
    // Important : repasser policyMaxAdvanceDays dans le preview pour que
    // le picker reflète le délai max d'avance du pro (sinon il retombe sur
    // 60 jours par défaut et l'utilisateur croit que le réglage est inerte).
    policyMaxAdvanceDays: business.policyMaxAdvanceDays != null ? Number(business.policyMaxAdvanceDays) : 60,
  };
  // On force pageCustom dans bookingSettings pour que le rendu reflète les
  // dernières modifs du panneau de gauche (sans attendre l'aller-retour cloud).
  const previewData = {
    ...data,
    bookingSettings: {
      ...(data.bookingSettings || {}),
      pageCustom: pc,
    },
  };

  // Empêche les liens internes (logo home, lien CGU, etc.) de naviguer hors
  // de l'éditeur depuis l'aperçu : on intercepte en phase de capture.
  const onClickCapture = (e) => {
    const a = e.target.closest && e.target.closest("a[href]");
    if (!a) return;
    const href = a.getAttribute("href");
    if (!href || href.startsWith("#")) return;
    // On laisse passer les liens externes (target=_blank, http*://).
    if (a.target === "_blank") return;
    if (/^https?:\/\//.test(href) && !href.startsWith(window.location.origin)) return;
    e.preventDefault();
  };

  // Heure live affichée dans la status bar du mockup (mise à jour 30 s).
  const [now, setNow] = React.useState(() => new Date());
  React.useEffect(() => {
    const t = setInterval(() => setNow(new Date()), 30000);
    return () => clearInterval(t);
  }, []);
  const hh = String(now.getHours()).padStart(2, "0");
  const mm = String(now.getMinutes()).padStart(2, "0");

  // Cadre redessiné : Dynamic Island plus discret, status bar réaliste,
  // scrollbar masquée à l'intérieur du téléphone.
  const FRAME_W = 320;
  const FRAME_H = 660;
  const STATUS_H = 44;     // hauteur status bar (espace pour heure descendue ≈ iOS réel)
  const ISLAND_W = 92;
  const ISLAND_H = 26;
  return (
    <div style={{
      width: FRAME_W, height: FRAME_H, background: "#0a0a10",
      borderRadius: 46, padding: 8, margin: "0 auto",
      position: "relative",
      boxShadow:
        "0 30px 70px -30px rgba(15,18,30,0.45), 0 0 0 1px oklch(45% 0.02 260), inset 0 0 0 2px oklch(18% 0.01 260)",
    }}>
      {/* Boutons latéraux iPhone, détail discret, ne touche pas le contenu */}
      <div aria-hidden style={{ position: "absolute", left: -2, top: 110, width: 2, height: 30, background: "#1a1a22", borderRadius: 2 }}/>
      <div aria-hidden style={{ position: "absolute", left: -2, top: 160, width: 2, height: 54, background: "#1a1a22", borderRadius: 2 }}/>
      <div aria-hidden style={{ position: "absolute", left: -2, top: 224, width: 2, height: 54, background: "#1a1a22", borderRadius: 2 }}/>
      <div aria-hidden style={{ position: "absolute", right: -2, top: 150, width: 2, height: 80, background: "#1a1a22", borderRadius: 2 }}/>

      <div
        className="cb-page-preview-scroll"
        style={{
          position: "relative", width: "100%", height: "100%",
          borderRadius: 38, overflow: "hidden",
          background: "var(--bg)",
          display: "flex", flexDirection: "column",
        }}>
        {/* Status bar + Dynamic Island groupés dans un seul wrapper sticky.
            44px de haut, contenu (heure/icônes) ALIGNÉ EN BAS pour laisser
            de l'espace avec le bord arrondi haut du téléphone (look iOS
            réaliste : la status bar est dans la zone "safe area" en
            dessous de l'encoche). */}
        <div aria-hidden style={{
          position: "relative", zIndex: 6,
          height: STATUS_H, flexShrink: 0,
          background: "var(--bg)",
          display: "flex", alignItems: "flex-end", // ← descend les icônes vers le bas de la barre
          padding: "0 22px 8px",
          color: "var(--ink)",
          fontFamily: "var(--ff-display)",
          fontSize: 12.5, fontWeight: 600,
          letterSpacing: "-0.01em",
        }}>
          <span style={{ marginLeft: 6 }}>{hh}:{mm}</span>
          <span style={{ flex: 1 }}/>
          {/* Icônes système stylisées */}
          <span aria-hidden style={{ display: "inline-flex", alignItems: "center", gap: 5 }}>
            {/* Signal */}
            <span style={{ display: "inline-flex", alignItems: "flex-end", gap: 1 }}>
              {[3, 5, 7, 9].map(h => (
                <span key={h} style={{ width: 2.5, height: h, background: "currentColor", borderRadius: 1 }}/>
              ))}
            </span>
            {/* Wifi (arcs simplifiés) */}
            <svg width="13" height="9" viewBox="0 0 16 11" fill="none" style={{ marginLeft: 1 }}>
              <path d="M8 11l2-2.5a3 3 0 00-4 0L8 11z" fill="currentColor"/>
              <path d="M2 5.5a8 8 0 0112 0M4.5 8a4.5 4.5 0 017 0" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round"/>
            </svg>
            {/* Batterie */}
            <span style={{
              display: "inline-flex", alignItems: "center",
              width: 22, height: 10, border: "1px solid currentColor",
              borderRadius: 3, padding: 1, marginLeft: 2, position: "relative",
            }}>
              <span style={{ width: "82%", height: "100%", background: "currentColor", borderRadius: 1 }}/>
              <span style={{ position: "absolute", right: -3, top: 2, width: 2, height: 4, background: "currentColor", borderRadius: 1 }}/>
            </span>
          </span>
          {/* Dynamic Island, centré horizontalement, top:8 pour le coller
              au sommet du téléphone (zone safe area au-dessus de l'heure). */}
          <span aria-hidden style={{
            position: "absolute", top: 8, left: "50%", transform: "translateX(-50%)",
            width: ISLAND_W, height: ISLAND_H, background: "#000",
            borderRadius: 999, zIndex: 1,
            boxShadow: "0 1px 2px rgba(0,0,0,0.4)",
          }}/>
        </div>
        <PhoneIframe style={{ flex: 1, width: "100%", minHeight: 0 }}>
          <BookingPage slug="preview" preview={{ data: previewData, pro: previewPro }}/>
        </PhoneIframe>
      </div>
      {/* Overrides ciblés : la BookingPage n'a pas accès aux media queries
          (le viewport reste celui du desktop), on force le rendu "compact"
          pour le cadre 304px : tout est rétréci pour ne PAS déborder. */}
      <style>{`
        /* Scrollbar invisible, ne sort plus du cadre arrondi du téléphone */
        .cb-page-preview-scroll::-webkit-scrollbar { width: 0; height: 0; display: none; }
        .cb-page-preview-scroll { scrollbar-width: none; -ms-overflow-style: none; }
        .cb-page-preview-scroll [data-cb-theme] {
          min-height: 0 !important;
        }
        /* Largeur stricte : aucun élément ne déborde du cadre 304px */
        .cb-page-preview-scroll * { max-width: 100% !important; }
        .cb-page-preview-scroll p,
        .cb-page-preview-scroll h1,
        .cb-page-preview-scroll h2,
        .cb-page-preview-scroll h3,
        .cb-page-preview-scroll span,
        .cb-page-preview-scroll div {
          word-break: break-word !important;
          overflow-wrap: anywhere !important;
        }
        .cb-page-preview-scroll main {
          padding: 14px 12px 32px !important;
        }
        .cb-page-preview-scroll .cb-book-name-row {
          grid-template-columns: 1fr !important;
        }
        .cb-page-preview-scroll input,
        .cb-page-preview-scroll textarea,
        .cb-page-preview-scroll select {
          font-size: 13px !important;
        }
        .cb-page-preview-scroll header > div {
          padding-left: 12px !important; padding-right: 12px !important;
        }
        /* H1 du formulaire ("Prenez rendez-vous avec X.") trop gros en desktop
           clamp → on force une taille petite pour rentrer dans 304px sans
           déborder ni couper le prénom du pro sur 2 lignes. */
        .cb-page-preview-scroll h1 {
          font-size: 22px !important;
          line-height: 1.15 !important;
          letter-spacing: -0.025em !important;
        }
        .cb-page-preview-scroll h2 {
          font-size: 16px !important;
          line-height: 1.2 !important;
        }
        .cb-page-preview-scroll h3 {
          font-size: 14.5px !important;
        }
        /* Boutons CTA plus compacts dans le cadre étroit */
        .cb-page-preview-scroll .btn-lg,
        .cb-page-preview-scroll .btn-primary,
        .cb-page-preview-scroll .btn-accent {
          padding: 10px 14px !important;
          font-size: 13.5px !important;
        }
        /* Stepper : numéros + labels plus serrés */
        .cb-page-preview-scroll [class*="cb-step"] {
          font-size: 11px !important;
        }
        /* Listes de prestations : padding compact */
        .cb-page-preview-scroll [class*="cb-service"],
        .cb-page-preview-scroll [class*="cb-prest"] {
          padding: 10px 12px !important;
        }
        /* Bio italique : taille réduite pour ne pas écraser le header */
        .cb-page-preview-scroll header p {
          font-size: 12px !important;
          line-height: 1.5 !important;
        }
        /* Tags + boutons sociaux du header : plus petits */
        .cb-page-preview-scroll header [style*="border-radius: 999"] {
          font-size: 10.5px !important;
        }
        /* Cancellation notice card sous le formulaire */
        .cb-page-preview-scroll section { padding: 0 !important; }
      `}</style>
    </div>
  );
};

// Exports globaux (chargé via Babel standalone)
window.AppPage = AppPage;
window.PAGE_THEMES = PAGE_THEMES;
window.PAGE_FONTS = PAGE_FONTS;
window.DEFAULT_PAGE_CUSTOM = DEFAULT_PAGE_CUSTOM;
