// generator-color-patch.jsx — expands brand palette + injects color fields across ALL templates // runs AFTER templates*.jsx files, BEFORE generator-app.jsx (function () { if (!window.LIROU_TEMPLATES) return; // Paleta oficial Lirou Closet 65 — só as cores da marca const FULL_PALETTE = [ "#eadcbe", // cream (fundo principal) "#f4f1ec", // paper (fundo claro alternativo) "#ffffff", // white "#1b1b1a", // ink (texto principal) "#674689", // purple (cor de marca) "#df4b5a", // coral (destaque / promo) ]; // Defaults pra cada chave de cor reconhecida const COLOR_KEY_DEFAULTS = { bg: { label: "Cor de fundo", def: "#eadcbe" }, fg: { label: "Cor do texto", def: "#1b1b1a" }, accent: { label: "Cor de destaque", def: "#674689" }, glyph: { label: "Cor do glifo", def: "#674689" }, btn: { label: "Cor do botão", def: "#674689" }, btnText: { label: "Texto do botão", def: "#ffffff" }, hookColor: { label: "Cor do hook", def: "#df4b5a" }, textColor: { label: "Cor do texto (logo)", def: "" }, }; // 1) expande as options de TODA field do tipo "color" pra paleta completa // 2) injeta campos faltantes (fg, accent, btn, btnText, glyph) em todo template for (const t of window.LIROU_TEMPLATES) { if (!t.fields) continue; // Adesivos pra impressão: edição mínima — não injeta campos extras if (t.skipColorPatch || (t.group || "").startsWith("Adesivos")) { for (const f of t.fields) { if (f.type === "color") f.options = FULL_PALETTE.slice(); } continue; } const present = new Set(t.fields.filter(f => f.type === "color").map(f => f.k)); // expandir options das que já existem for (const f of t.fields) { if (f.type === "color") f.options = FULL_PALETTE.slice(); } // injetar campos faltantes — só se o template não tiver const inject = []; if (!present.has("bg")) inject.push({ k: "bg", label: COLOR_KEY_DEFAULTS.bg.label, type: "color", options: FULL_PALETTE.slice() }); if (!present.has("fg")) inject.push({ k: "fg", label: COLOR_KEY_DEFAULTS.fg.label, type: "color", options: FULL_PALETTE.slice() }); if (!present.has("accent")) inject.push({ k: "accent", label: COLOR_KEY_DEFAULTS.accent.label, type: "color", options: FULL_PALETTE.slice() }); if (!present.has("btn")) inject.push({ k: "btn", label: COLOR_KEY_DEFAULTS.btn.label, type: "color", options: FULL_PALETTE.slice() }); if (!present.has("btnText")) inject.push({ k: "btnText", label: COLOR_KEY_DEFAULTS.btnText.label, type: "color", options: FULL_PALETTE.slice() }); if (inject.length) t.fields = t.fields.concat(inject); // garantir defaults t.defaults = t.defaults || {}; for (const f of t.fields) { if (f.type === "color" && (t.defaults[f.k] === undefined || t.defaults[f.k] === "")) { const seed = COLOR_KEY_DEFAULTS[f.k]?.def; if (seed) t.defaults[f.k] = seed; else t.defaults[f.k] = FULL_PALETTE[0]; } } } // 3) Wrappa render() pra repassar btn/btnText pros componentes que usem, // sem precisar reescrever cada template — eles já leem d.btn / d.btnText // quando fornecidos via wrapper de estilo abaixo (CSS-vars no canvas). // TAMBÉM: passa por stripEmpty pra remover qualquer elemento de texto vazio. // Recursivamente remove elementos cujo único conteúdo é string vazia/whitespace. // Mantém imagens, SVGs, BR, HR e divs decorativas (que nunca tiveram filhos de texto). // Mantém elementos com dangerouslySetInnerHTML. function stripEmpty(el) { if (el == null || el === false || el === true) return null; if (typeof el === "string") return el.trim() === "" ? null : el; if (typeof el === "number") return el; if (Array.isArray(el)) { const arr = el.map(stripEmpty).filter(c => c !== null && c !== ""); return arr.length === 0 ? null : arr; } if (!React.isValidElement(el)) return el; // Imagens, SVGs, brs, hrs e elementos com innerHTML manual — preserva if (el.props && el.props.dangerouslySetInnerHTML) return el; if (el.type === "img" || el.type === "svg" || el.type === "br" || el.type === "hr") return el; const rawChildren = React.Children.toArray(el.props.children); if (rawChildren.length === 0) { // Sem filhos = elemento decorativo (círculo de fundo, barra, etc.) — preserva return el; } const newChildren = []; for (const child of rawChildren) { const stripped = stripEmpty(child); if (stripped === null || stripped === "") continue; newChildren.push(stripped); } // Tinha filhos mas todos eram vazios → elemento provavelmente é wrapper de texto vazio. Remove. if (newChildren.length === 0) return null; return React.cloneElement(el, el.props, ...newChildren); } for (const t of window.LIROU_TEMPLATES) { const orig = t.render; const skipColorVars = t.skipColorPatch || (t.group || "").startsWith("Adesivos"); t.render = function (d) { let wrapped = orig(d); // Sempre passa por stripEmpty (mesmo nos adesivos) try { wrapped = stripEmpty(wrapped); if (!wrapped) return null; } catch (e) { // se algo der errado, segue com o output original } if (skipColorVars) return wrapped; try { const style = (wrapped && wrapped.props && wrapped.props.style) || {}; const merged = { ...style, ["--lirou-btn"]: d.btn || "#674689", ["--lirou-btn-text"]: d.btnText || "#ffffff", ["--lirou-glyph"]: d.glyph || d.accent || "#674689", ["--lirou-fg"]: d.fg || "#1b1b1a", ["--lirou-bg"]: d.bg || "#eadcbe", ["--lirou-accent"]: d.accent || "#674689", }; return React.cloneElement(wrapped, { style: merged }); } catch { return wrapped; } }; } })();