// SEO GUY Web V2 — composants partagés const { useState, useEffect, useRef } = React; function Icon({ name, size = 18 }) { const paths = { home: "M3 10.5 12 3l9 7.5M5 9.5V21h5v-6h4v6h5V9.5", docs: "M7 3h7l5 5v13H7zM14 3v5h5M9.5 13h6M9.5 16.5h6", chart: "M4 20V4M4 20h16M8 16v-5M12 16V7M16 16v-8", gear: "M12 9.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5M12 3v2.5M12 18.5V21M5.6 5.6l1.8 1.8M16.6 16.6l1.8 1.8M3 12h2.5M18.5 12H21M5.6 18.4l1.8-1.8M16.6 7.4l1.8-1.8", check: "M5 12.5l4.5 4.5L19 7.5", bolt: "M13 3 5 13h6l-1 8 8-10h-6z", spark: "M12 3v6M12 15v6M3 12h6M15 12h6", arrow: "M5 12h14M13 6l6 6-6 6", calendar: "M4 6h16v15H4zM4 10h16M8 3v4M16 3v4", eye: "M2 12s3.5-6.5 10-6.5S22 12 22 12s-3.5 6.5-10 6.5S2 12 2 12M12 9.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5", target: "M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18M12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8M12 11.5a.5.5 0 1 0 0 1 .5.5 0 0 0 0-1", mic: "M12 3a3 3 0 0 0-3 3v6a3 3 0 0 0 6 0V6a3 3 0 0 0-3-3M5 11v1a7 7 0 0 0 14 0v-1M12 19v3M8.5 22h7", users: "M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2M9 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8M23 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75", trending: "M3 17l4-8 4 4 4-6 4 4", plus: "M12 5v14M5 12h14", globe: "M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18M3 12h18", star: "M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01z", wand: "M15 4V2M15 16v-2M8 9h2M20 9h2M17.8 11.8 19 13M17.8 6.2 19 5M3 21l9-9M12.2 6.2 11 5", card: "M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 0 0 3-3V8a3 3 0 0 0-3-3H6a3 3 0 0 0-3 3v8a3 3 0 0 0 3 3", user: "M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2M12 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8", send: "M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z", menu: "M4 7h16M4 12h16M4 17h16", close: "M6 6l12 12M18 6 6 18", image: "M4 5h16v14H4zM4 15l4.2-4.2 3 3L16 9.5 20 14.5M9 9a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3", }; const p = paths[name]; if (!p) return null; return ( ); } function StatusPill({ children, tone = "live" }) { return {tone === "live" && }{children}; } function Pill({ children, tone }) { return {children}; } /** Anneau score SEO : jusqu'à 85 % en bleu, 15 % bonus humanisation en or. */ const SEO_SCORE_HUMAN_NOTE = "Contenu suffisamment humanisé pour performer"; const SEO_SCORE_CAP = 85; const SEO_SCORE_BONUS = 15; const SEO_RING_BLUE = "#2563eb"; const SEO_RING_GOLD = "#f5c518"; let __sgRingUid = 0; function ScoreRing({ value, size = 44 }) { const v = Math.min(100, Math.max(0, Number(value) || 0)); const r = size / 2 - 4; const c = 2 * Math.PI * r; const cx = size / 2; const cy = size / 2; const seoPct = Math.min(v, SEO_SCORE_CAP); const blueLen = (seoPct / 100) * c; const goldLen = (SEO_SCORE_BONUS / 100) * c; const goldRotate = -90 + (SEO_SCORE_CAP / 100) * 360; const uidRef = React.useRef(null); if (!uidRef.current) uidRef.current = `sg-ring-gold-${++__sgRingUid}`; const uid = uidRef.current; return (
{Math.round(v)}
); } function SeoScoreHumanNote({ className = "" }) { return (

{SEO_SCORE_HUMAN_NOTE}

); } const NAV_MAIN = [ { id: "home", label: "Accueil", icon: "home" }, { id: "assistants", label: "Assistants Premium", icon: "wand" }, { id: "performance", label: "Performance", icon: "chart" }, { id: "articles", label: "Articles", icon: "docs" }, ]; const NAV_ADVANCED = [ { id: "settings", label: "Réglages", icon: "gear" }, { id: "gsc", label: "Search Console", icon: "globe" }, { id: "config", label: "Ciblage marketing", icon: "target" }, { id: "billing", label: "Abonnements", icon: "card" }, ]; function Sidebar({ active, onNav, showMascot, onAccount, siteLabel, planLabel, mobileOpen, onClose }) { function navigate(id) { onNav(id); onClose?.(); } return ( <>
); } function MobileTopBar({ onMenu, title }) { return (
S SEOGUY
{title ? {title} : }
); } function PageHeader({ greeting, title, sub, status, actions }) { return (
{greeting &&

{greeting}

}

{title}

{sub &&

{sub}

}
{status && {status}} {actions}
); } function HeroBand({ guyImage, eyebrow, title, tip, pills, actions, showMascot }) { if (!showMascot) return null; return (
{eyebrow &&

{eyebrow}

} {title &&

{title}

} {tip &&

{tip}

} {pills?.length > 0 && (
{pills.map((p) => {p})}
)} {actions &&
{actions}
}
SEO GUY
); } function AlertList({ alerts, onNav }) { if (!alerts?.length) return null; return (
{alerts.map((a) => ( ))}
); } function KpiCard({ icon, label, value, delta, up }) { const isEmoji = typeof icon === "string" && icon.length <= 4; return (
{isEmoji ? {icon} : } {delta}

{label}

{value}

); } /** Éditeur HTML léger (contentEditable) — conserve le balisage. */ function HtmlEditor({ value, onChange, disabled }) { const ref = useRef(null); const skipSync = useRef(false); const ready = useRef(false); useEffect(() => { if (!ref.current) return; if (!ready.current) { ref.current.innerHTML = value || ""; ready.current = true; return; } if (skipSync.current) { skipSync.current = false; return; } if (document.activeElement === ref.current) return; const next = value || ""; if (ref.current.innerHTML !== next) ref.current.innerHTML = next; }, [value]); function emit() { if (!ref.current) return; skipSync.current = true; onChange?.(ref.current.innerHTML); } function run(cmd, arg) { if (disabled) return; ref.current?.focus(); try { document.execCommand(cmd, false, arg); } catch { /* ignore */ } emit(); } function setBlock(tag) { // execCommand attend souvent la forme

, etc. run("formatBlock", `<${tag}>`); } function addLink() { const url = window.prompt("URL du lien :", "https://"); if (!url) return; run("createLink", url); } // Titre 1 = H2, Titre 2 = H3, Titre 3 = H4 (H1 réservé au titre de l'article) const tools = [ { label: "Titre 1", title: "Titre 1 (H2)", onClick: () => setBlock("h2") }, { label: "Titre 2", title: "Titre 2 (H3)", onClick: () => setBlock("h3") }, { label: "Titre 3", title: "Titre 3 (H4)", onClick: () => setBlock("h4") }, { label: "¶", title: "Paragraphe", onClick: () => setBlock("p") }, { label: "« »", title: "Citation", onClick: () => setBlock("blockquote") }, { sep: true }, { label: "B", title: "Gras", className: "is-bold", onClick: () => run("bold") }, { label: "I", title: "Italique", className: "is-italic", onClick: () => run("italic") }, { label: "S", title: "Souligné", className: "is-underline", onClick: () => run("underline") }, { sep: true }, { label: "A−", title: "Réduire", onClick: () => run("fontSize", "2") }, { label: "A+", title: "Agrandir", onClick: () => run("fontSize", "5") }, { label: "🔗", title: "Lien", onClick: addLink }, { label: "• Liste", title: "Liste à puces", onClick: () => run("insertUnorderedList") }, ]; return (
{tools.map((t, i) => t.sep ? (
); } const BLOG_COVER_HINT = "Paysage 16:9, idéalement 1600 × 900 px. JPEG, PNG ou WebP, 8 Mo max. Guy recadre au format blog."; const BLOG_COVER_W = 1600; const BLOG_COVER_H = 900; const BLOG_COVER_FILE_MAX = 8 * 1024 * 1024; async function resizeBlogCoverFile(file) { if (!file || !/^image\/(jpeg|jpg|png|webp)$/i.test(file.type)) { throw new Error("Formats acceptés : JPEG, PNG ou WebP."); } if (file.size > BLOG_COVER_FILE_MAX) { throw new Error("Image trop lourde (max 8 Mo). Réduisez-la avant l’envoi."); } const bitmap = await createImageBitmap(file); const canvas = document.createElement("canvas"); canvas.width = BLOG_COVER_W; canvas.height = BLOG_COVER_H; const ctx = canvas.getContext("2d"); if (!ctx) { bitmap.close(); throw new Error("Canvas indisponible."); } const targetRatio = BLOG_COVER_W / BLOG_COVER_H; const srcRatio = bitmap.width / bitmap.height; let sx = 0; let sy = 0; let sw = bitmap.width; let sh = bitmap.height; if (srcRatio > targetRatio) { sw = sh * targetRatio; sx = (bitmap.width - sw) / 2; } else { sh = sw / targetRatio; sy = (bitmap.height - sh) / 2; } ctx.drawImage(bitmap, sx, sy, sw, sh, 0, 0, BLOG_COVER_W, BLOG_COVER_H); bitmap.close(); let quality = 0.82; let dataUrl = canvas.toDataURL("image/jpeg", quality); while (dataUrl.length > 5_500_000 && quality > 0.5) { quality -= 0.08; dataUrl = canvas.toDataURL("image/jpeg", quality); } if (dataUrl.length > 5_500_000) { throw new Error("Image trop détaillée après compression. Choisissez un fichier plus léger."); } return dataUrl; } async function resizeProductRefFile(file) { if (!file || !/^image\/(jpeg|jpg|png|webp)$/i.test(file.type)) { throw new Error("Formats acceptés : JPEG, PNG ou WebP."); } if (file.size > BLOG_COVER_FILE_MAX) { throw new Error("Image trop lourde (max 8 Mo). Réduisez-la avant l’envoi."); } const bitmap = await createImageBitmap(file); const maxEdge = 1600; let w = bitmap.width; let h = bitmap.height; if (w > maxEdge || h > maxEdge) { const scale = maxEdge / Math.max(w, h); w = Math.round(w * scale); h = Math.round(h * scale); } const canvas = document.createElement("canvas"); canvas.width = w; canvas.height = h; const ctx = canvas.getContext("2d"); if (!ctx) { bitmap.close(); throw new Error("Canvas indisponible."); } ctx.drawImage(bitmap, 0, 0, w, h); bitmap.close(); let quality = 0.82; let dataUrl = canvas.toDataURL("image/jpeg", quality); while (dataUrl.length > 5_500_000 && quality > 0.5) { quality -= 0.08; dataUrl = canvas.toDataURL("image/jpeg", quality); } if (dataUrl.length > 5_500_000) { throw new Error("Image trop détaillée après compression. Choisissez un fichier plus léger."); } return dataUrl; } Object.assign(window, { Icon, StatusPill, Pill, ScoreRing, SeoScoreHumanNote, SEO_SCORE_HUMAN_NOTE, Sidebar, MobileTopBar, PageHeader, HeroBand, AlertList, KpiCard, HtmlEditor, NAV_MAIN, NAV_ADVANCED, resizeBlogCoverFile, BLOG_COVER_HINT, resizeProductRefFile, });