feat(builder): add social design controls and per-block background/transitions

This commit is contained in:
komkida91
2026-02-24 16:48:49 +01:00
parent 93d046a24c
commit 208dca9f05

View File

@@ -56,6 +56,14 @@
body.dragging{user-select:none}
.block:hover{transform:translateY(-2px);box-shadow:0 20px 46px rgba(15,23,42,.14)}
.block.selected{border-color:#7aa7ff}
.block.anim-in-none{animation:none}
.block.anim-in-slide{animation:slideIn .28s ease}
.block.anim-in-zoom{animation:zoomIn .26s ease}
.block.hover-none:hover{transform:none;box-shadow:var(--site-block-shadow,0 12px 30px rgba(15,23,42,.08))}
.block.hover-glow:hover{transform:translateY(-2px);box-shadow:0 16px 34px rgba(37,99,235,.24),0 0 0 1px rgba(37,99,235,.28)}
.block.hover-tilt:hover{transform:translateY(-2px) rotate(-.3deg)}
.block.has-bg-media{background-size:cover;background-position:center;background-repeat:no-repeat}
.block.has-bg-media .editable,.block.has-bg-media h1,.block.has-bg-media h2,.block.has-bg-media h3,.block.has-bg-media p,.block.has-bg-media li,.block.has-bg-media a,.block.has-bg-media label,.block.has-bg-media strong,.block.has-bg-media span{position:relative;z-index:1}
.empty{padding:32px;border:1px dashed #cbd5e1;border-radius:12px;text-align:center;color:#64748b;background:#fff}
.drop{height:8px;border-radius:6px;background:rgba(122,167,255,.4);margin:6px 0}
.drag-handle{cursor:grab;touch-action:none}
@@ -67,6 +75,8 @@
}
.preview-mode .block-drag-handle{display:none !important}
@keyframes fadeUp{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}
@keyframes slideIn{from{opacity:0;transform:translateY(10px) translateX(-8px)}to{opacity:1;transform:translateY(0) translateX(0)}}
@keyframes zoomIn{from{opacity:0;transform:scale(.98)}to{opacity:1;transform:scale(1)}}
label{font-size:12px;color:var(--muted)}
input,textarea,select{width:100%;background:#0f172a;border:1px solid #1f2937;color:var(--text);padding:8px;border-radius:10px;font-family:inherit}
input[type="color"]{height:36px;padding:4px;background:#0f172a;border-radius:10px}
@@ -86,6 +96,14 @@
.social-style-minimal .social-btn i{background:transparent}
.social-style-solid .social-btn{background:var(--site-primary);border-color:var(--site-primary);color:#0b0f16}
.social-style-solid .social-btn i{color:#0b0f16 !important}
.social-surface-pro{position:relative;overflow:hidden}
.social-surface-pro::before{content:"";position:absolute;inset:-30% -10% auto auto;width:340px;height:340px;background:radial-gradient(circle,rgba(37,99,235,.16),transparent 62%);pointer-events:none}
.social-grid-pro{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:14px}
.social-card-pro{display:flex;align-items:center;gap:12px;min-height:84px;padding:16px;border-radius:16px;text-decoration:none;transition:transform .2s ease,box-shadow .2s ease,border-color .2s ease}
.social-card-pro:hover{transform:translateY(-3px);box-shadow:0 14px 28px rgba(15,23,42,.2)}
.social-card-meta{display:flex;flex-direction:column;min-width:0}
.social-card-title{font-size:17px;line-height:1.1;font-weight:750}
.social-card-value{font-size:12px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.inline-drop{background:#e2e8f0;border:1px dashed #cbd5e1;border-radius:12px;padding:18px;text-align:center;color:#64748b;cursor:pointer}
.image-wrap{position:relative;border-radius:12px;overflow:hidden}
.image-overlay{position:absolute;left:12px;right:12px;bottom:12px;padding:8px 10px;border-radius:10px;background:rgba(15,23,42,.55);backdrop-filter:blur(8px);color:#fff;font-size:13px;line-height:1.3}
@@ -657,6 +675,16 @@ const state = {
if (type === "menu") return 100;
return Math.max(30, Math.min(100, Number(raw || 100)));
}
function hexToRgba(hex, alpha){
const raw = String(hex || "#0f172a").replace("#", "").trim();
const full = raw.length === 3 ? raw.split("").map((c)=>c+c).join("") : raw;
const num = parseInt(full || "0f172a", 16);
const r = (num >> 16) & 255;
const g = (num >> 8) & 255;
const b = num & 255;
const a = Math.max(0, Math.min(1, Number(alpha || 0)));
return `rgba(${r},${g},${b},${a})`;
}
function getDefaultPos(){
const base = 20;
const gap = 120;
@@ -1259,9 +1287,32 @@ const state = {
threads:"fa-brands fa-threads"
};
const size = Math.max(14, Math.min(36, Number(block.data?.icon_size || 18)));
const iconColor = block.data?.icon_color || "var(--site-text)";
const iconColor = block.data?.icon_color || "#0f172a";
const showText = block.data?.show_text !== false;
const style = (block.data?.icon_style || "pill").toLowerCase();
const preset = String(block.data?.social_preset || "pro").toLowerCase();
const presets = {
pro: { surfaceBg: "#ffffff", surfaceBorder: "#e5e7eb", title: "#0f172a", subtitle: "#475569", cardBg: "#f8fafc", cardBorder: "#dbe3ee", label: "#0f172a", value: "#64748b", shadow: "0 18px 34px rgba(15,23,42,.12)" },
dark: { surfaceBg: "#0f172a", surfaceBorder: "#1e293b", title: "#f8fafc", subtitle: "#94a3b8", cardBg: "#111827", cardBorder: "#334155", label: "#e2e8f0", value: "#94a3b8", shadow: "0 18px 34px rgba(2,6,23,.45)" },
glass: { surfaceBg: "rgba(255,255,255,.70)", surfaceBorder: "rgba(255,255,255,.55)", title: "#0f172a", subtitle: "#475569", cardBg: "rgba(255,255,255,.55)", cardBorder: "rgba(148,163,184,.5)", label: "#0f172a", value: "#475569", shadow: "0 22px 42px rgba(15,23,42,.14)" }
};
const tone = presets[preset] || presets.pro;
const useBrandColors = block.data?.social_use_brand_colors !== false;
const surfaceBg = block.data?.social_surface_bg || tone.surfaceBg;
const surfaceBorder = block.data?.social_surface_border || tone.surfaceBorder;
const titleColor = block.data?.social_title_color || tone.title;
const subtitleColor = block.data?.social_subtitle_color || tone.subtitle;
const cardBg = block.data?.social_card_bg || tone.cardBg;
const cardBorder = block.data?.social_card_border || tone.cardBorder;
const labelColor = block.data?.social_label_color || tone.label;
const valueColor = block.data?.social_value_color || tone.value;
const brandStyles = {
whatsapp: { icon: "#25D366", bg: "#ecfdf3", border: "#bbf7d0" },
instagram: { icon: "#E1306C", bg: "#fff1f7", border: "#fecdd3" },
facebook: { icon: "#1877F2", bg: "#eff6ff", border: "#bfdbfe" },
tiktok: { icon: "#111111", bg: "#f8fafc", border: "#e2e8f0" },
youtube: { icon: "#FF0000", bg: "#fff1f2", border: "#fecdd3" }
};
const mainKeys = ["whatsapp","instagram","facebook","tiktok","youtube"];
const items = mainKeys.map(k=>[k, (block.data||{})[k]]).filter(([,v])=>v);
const linkFor = (k,v)=>{
@@ -1272,7 +1323,29 @@ const state = {
}
return normalizeLink(v);
};
return `<h3 style="margin:0 0 10px">Redes</h3><div class="social-icons social-style-${escapeHtml(style)}">${items.map(([k,v],idx)=>`<a class="social-btn" href="${escapeHtml(linkFor(k,v)||"#")}" target="_blank" rel="noreferrer" style="font-size:${size}px"><i class="${icons[k]||'fa-solid fa-circle'}" style="font-size:${size}px;color:${escapeHtml(iconColor)}"></i>${showText ? `<span class="editable" data-field="${escapeHtml(k)}" data-placeholder="${escapeHtml(k)}" contenteditable="true" style="font-size:12px">${escapeHtml(v)}</span>` : ""}</a>`).join("")}</div>`;
return `
<div class="social-surface-pro" style="width:100%;margin:0;background:${escapeHtml(surfaceBg)};border:1px solid ${escapeHtml(surfaceBorder)};border-radius:22px;padding:22px;box-shadow:${escapeHtml(tone.shadow)}">
<h3 style="margin:0;color:${escapeHtml(titleColor)};font-size:40px;line-height:1.02;letter-spacing:-.03em;font-weight:850">Redes Sociales</h3>
<p style="margin:10px 0 20px;color:${escapeHtml(subtitleColor)};font-size:16px">Conecta con nosotros en tus plataformas favoritas.</p>
<div class="social-grid-pro">
${items.map(([k,v])=>{
const brand = brandStyles[k] || { icon: iconColor, bg: cardBg, border: cardBorder };
const tileBg = useBrandColors ? brand.bg : cardBg;
const tileBorder = useBrandColors ? brand.border : cardBorder;
const tileIcon = useBrandColors ? brand.icon : iconColor;
const href = escapeHtml(linkFor(k,v) || "#");
const label = escapeHtml(k.charAt(0).toUpperCase() + k.slice(1));
return `<a class="social-btn social-style-${escapeHtml(style)} social-card-pro" href="${href}" target="_blank" rel="noreferrer" style="border:1px solid ${tileBorder};background:${tileBg}">
<i class="${icons[k]||'fa-solid fa-circle'}" style="font-size:${Math.max(28,size)}px;color:${tileIcon}"></i>
<div class="social-card-meta">
<strong class="social-card-title" style="color:${escapeHtml(labelColor)}">${label}</strong>
${showText ? `<span class="editable social-card-value" data-field="${escapeHtml(k)}" data-placeholder="${escapeHtml(k)}" contenteditable="true" style="color:${escapeHtml(valueColor)}">${escapeHtml(v)}</span>` : ""}
</div>
</a>`;
}).join("")}
</div>
</div>
`;
}
if (block.type==="map"){
const h = Math.max(220, Math.min(700, Number(block.data.height || 320)));
@@ -1454,6 +1527,33 @@ const state = {
el.style.height = "100%";
}
}
const blockData = block.data || {};
const animIn = ["fade","slide","zoom","none"].includes(String(blockData.anim_in || "").toLowerCase()) ? String(blockData.anim_in).toLowerCase() : "fade";
const animHover = ["lift","glow","tilt","none"].includes(String(blockData.anim_hover || "").toLowerCase()) ? String(blockData.anim_hover).toLowerCase() : "lift";
const animDuration = Math.max(120, Math.min(900, Number(blockData.anim_duration || 250)));
el.classList.add(`anim-in-${animIn}`);
el.classList.add(`hover-${animHover}`);
el.style.transitionDuration = `${animDuration}ms`;
const bgImageUrl = String(blockData.bg_image_url || "").trim();
if (bgImageUrl){
const overlayHex = String(blockData.bg_overlay_hex || "#0f172a");
const overlayOpacity = Math.max(0, Math.min(0.85, Number(blockData.bg_overlay_opacity || 0)));
const overlay = hexToRgba(overlayHex, overlayOpacity);
const safeBg = bgImageUrl.replace(/'/g, "%27");
el.classList.add("has-bg-media");
el.style.backgroundImage = `linear-gradient(${overlay}, ${overlay}), url('${safeBg}')`;
} else {
el.classList.remove("has-bg-media");
el.style.backgroundImage = "";
}
const blur = Math.max(0, Math.min(10, Number(blockData.bg_blur || 0)));
if (blur > 0){
el.style.backdropFilter = `blur(${blur}px)`;
el.style.webkitBackdropFilter = `blur(${blur}px)`;
} else {
el.style.backdropFilter = "";
el.style.webkitBackdropFilter = "";
}
el.innerHTML = renderBlockHtml(block);
if (block.type === "menu"){ wireMenuAccordionInteractions(el); }
if (block.type === "image"){ bindInlineImageDrop(el, block); }
@@ -1868,6 +1968,17 @@ const state = {
html+=`<div class="row"><label>Color iconos</label><input id="socialIconColor" type="color" value="${escapeHtml(data.icon_color || "#0b0c10")}"></div>`;
const styleVal = escapeHtml(data.icon_style || "pill");
html+=`<div class="row"><label>Estilo iconos</label><select id="socialIconStyle"><option value="pill" ${styleVal==="pill"?"selected":""}>Pill</option><option value="circle" ${styleVal==="circle"?"selected":""}>Circulo</option><option value="outline" ${styleVal==="outline"?"selected":""}>Outline</option><option value="minimal" ${styleVal==="minimal"?"selected":""}>Minimal</option><option value="solid" ${styleVal==="solid"?"selected":""}>Solid</option></select></div>`;
const presetVal = escapeHtml(String(data.social_preset || "pro").toLowerCase());
html+=`<div class="row"><label>Preset visual</label><select id="socialPreset"><option value="pro" ${presetVal==="pro"?"selected":""}>Pro</option><option value="dark" ${presetVal==="dark"?"selected":""}>Dark</option><option value="glass" ${presetVal==="glass"?"selected":""}>Glass</option></select></div>`;
html+=`<div class="row"><label>Usar colores de marca</label><input id="socialUseBrandColors" type="checkbox" ${data.social_use_brand_colors !== false ? "checked" : ""}></div>`;
html+=`<div class="row"><label>Fondo panel</label><input id="socialSurfaceBg" type="color" value="${escapeHtml(data.social_surface_bg || "#ffffff")}"></div>`;
html+=`<div class="row"><label>Borde panel</label><input id="socialSurfaceBorder" type="color" value="${escapeHtml(data.social_surface_border || "#e5e7eb")}"></div>`;
html+=`<div class="row"><label>Color titulo</label><input id="socialTitleColor" type="color" value="${escapeHtml(data.social_title_color || "#0f172a")}"></div>`;
html+=`<div class="row"><label>Color subtitulo</label><input id="socialSubtitleColor" type="color" value="${escapeHtml(data.social_subtitle_color || "#475569")}"></div>`;
html+=`<div class="row"><label>Fondo tarjetas</label><input id="socialCardBg" type="color" value="${escapeHtml(data.social_card_bg || "#f8fafc")}"></div>`;
html+=`<div class="row"><label>Borde tarjetas</label><input id="socialCardBorder" type="color" value="${escapeHtml(data.social_card_border || "#dbe3ee")}"></div>`;
html+=`<div class="row"><label>Color etiquetas</label><input id="socialLabelColor" type="color" value="${escapeHtml(data.social_label_color || "#0f172a")}"></div>`;
html+=`<div class="row"><label>Color texto enlace</label><input id="socialValueColor" type="color" value="${escapeHtml(data.social_value_color || "#64748b")}"></div>`;
html+=`<div class="row"><label>Mostrar texto</label><input id="socialShowText" type="checkbox" ${data.show_text !== false ? "checked" : ""}></div>`;
} else if (block.type==="video"){
html+=input("URL video","videoUrl",data.url);
@@ -1894,6 +2005,15 @@ const state = {
const w = snapBlockWidth(block.type, data.width);
html+=`<div class="row"><label>Ancho bloque (%)</label><input id="blockWidth" type="range" min="30" max="100" step="1" value="${w}"></div>`;
html+=`<div class="row"><input id="blockWidthNumber" type="number" min="30" max="100" step="1" value="${w}"></div>`;
html+=`<div class="row"><label>Fondo imagen (URL)</label><input id="blockBgImage" type="text" value="${escapeHtml(data.bg_image_url || "")}" placeholder="https://..."></div>`;
html+=`<div class="row"><label>Overlay color</label><input id="blockBgOverlayHex" type="color" value="${escapeHtml(data.bg_overlay_hex || "#0f172a")}"></div>`;
html+=`<div class="row"><label>Overlay opacidad</label><input id="blockBgOverlayOpacity" type="range" min="0" max="0.85" step="0.05" value="${Math.max(0, Math.min(0.85, Number(data.bg_overlay_opacity || 0)))}"></div>`;
html+=`<div class="row"><label>Blur bloque</label><input id="blockBgBlur" type="range" min="0" max="10" step="1" value="${Math.max(0, Math.min(10, Number(data.bg_blur || 0)))}"></div>`;
const animIn = escapeHtml(String(data.anim_in || "fade").toLowerCase());
html+=`<div class="row"><label>Animacion entrada</label><select id="blockAnimIn"><option value="fade" ${animIn==="fade"?"selected":""}>Fade</option><option value="slide" ${animIn==="slide"?"selected":""}>Slide</option><option value="zoom" ${animIn==="zoom"?"selected":""}>Zoom</option><option value="none" ${animIn==="none"?"selected":""}>None</option></select></div>`;
const hoverAnim = escapeHtml(String(data.anim_hover || "lift").toLowerCase());
html+=`<div class="row"><label>Animacion hover</label><select id="blockAnimHover"><option value="lift" ${hoverAnim==="lift"?"selected":""}>Lift</option><option value="glow" ${hoverAnim==="glow"?"selected":""}>Glow</option><option value="tilt" ${hoverAnim==="tilt"?"selected":""}>Tilt</option><option value="none" ${hoverAnim==="none"?"selected":""}>None</option></select></div>`;
html+=`<div class="row"><label>Duracion transicion (ms)</label><input id="blockAnimDuration" type="number" min="120" max="900" step="10" value="${Math.max(120, Math.min(900, Number(data.anim_duration || 250)))}"></div>`;
}
html+=`<button class="danger" id="deleteBlockBtn">Eliminar bloque</button>`;
panel.innerHTML=html;
@@ -1997,6 +2117,26 @@ const state = {
if (colorEl){ block.data.icon_color = colorEl.value || "#0b0c10"; }
const styleEl = document.getElementById("socialIconStyle");
if (styleEl){ block.data.icon_style = styleEl.value || "pill"; }
const presetEl = document.getElementById("socialPreset");
if (presetEl){ block.data.social_preset = presetEl.value || "pro"; }
const brandEl = document.getElementById("socialUseBrandColors");
if (brandEl){ block.data.social_use_brand_colors = !!brandEl.checked; }
const surfaceBgEl = document.getElementById("socialSurfaceBg");
if (surfaceBgEl){ block.data.social_surface_bg = surfaceBgEl.value || "#ffffff"; }
const surfaceBorderEl = document.getElementById("socialSurfaceBorder");
if (surfaceBorderEl){ block.data.social_surface_border = surfaceBorderEl.value || "#e5e7eb"; }
const titleColorEl = document.getElementById("socialTitleColor");
if (titleColorEl){ block.data.social_title_color = titleColorEl.value || "#0f172a"; }
const subtitleColorEl = document.getElementById("socialSubtitleColor");
if (subtitleColorEl){ block.data.social_subtitle_color = subtitleColorEl.value || "#475569"; }
const cardBgEl = document.getElementById("socialCardBg");
if (cardBgEl){ block.data.social_card_bg = cardBgEl.value || "#f8fafc"; }
const cardBorderEl = document.getElementById("socialCardBorder");
if (cardBorderEl){ block.data.social_card_border = cardBorderEl.value || "#dbe3ee"; }
const labelColorEl = document.getElementById("socialLabelColor");
if (labelColorEl){ block.data.social_label_color = labelColorEl.value || "#0f172a"; }
const valueColorEl = document.getElementById("socialValueColor");
if (valueColorEl){ block.data.social_value_color = valueColorEl.value || "#64748b"; }
const showEl = document.getElementById("socialShowText");
if (showEl){ block.data.show_text = !!showEl.checked; }
}
@@ -2029,6 +2169,20 @@ const state = {
if (widthEl && block.type !== "menu"){
block.data = block.data || {};
block.data.width = snapBlockWidth(block.type, Number(widthEl.value || 50));
const bgImgEl = document.getElementById("blockBgImage");
if (bgImgEl){ block.data.bg_image_url = bgImgEl.value || ""; }
const bgOverlayHexEl = document.getElementById("blockBgOverlayHex");
if (bgOverlayHexEl){ block.data.bg_overlay_hex = bgOverlayHexEl.value || "#0f172a"; }
const bgOverlayOpacityEl = document.getElementById("blockBgOverlayOpacity");
if (bgOverlayOpacityEl){ block.data.bg_overlay_opacity = Math.max(0, Math.min(0.85, Number(bgOverlayOpacityEl.value || 0))); }
const bgBlurEl = document.getElementById("blockBgBlur");
if (bgBlurEl){ block.data.bg_blur = Math.max(0, Math.min(10, Number(bgBlurEl.value || 0))); }
const animInEl = document.getElementById("blockAnimIn");
if (animInEl){ block.data.anim_in = animInEl.value || "fade"; }
const animHoverEl = document.getElementById("blockAnimHover");
if (animHoverEl){ block.data.anim_hover = animHoverEl.value || "lift"; }
const animDurationEl = document.getElementById("blockAnimDuration");
if (animDurationEl){ block.data.anim_duration = Math.max(120, Math.min(900, Number(animDurationEl.value || 250))); }
}
renderPreview();
}