@accelerated-agency/visual-editor 0.5.2 → 0.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/vite.js CHANGED
@@ -3,6 +3,108 @@ import path from 'path';
3
3
  import { fileURLToPath } from 'url';
4
4
 
5
5
  // src/visualEditorProxyPlugin.ts
6
+
7
+ // src/croSections.ts
8
+ var CRO_SECTION_IMAGE_ROUTE = "/vvveb-editor-images/cro-sections/";
9
+ function croSectionImageFilename(key) {
10
+ return key.replace(/\//g, "-") + ".png";
11
+ }
12
+ var CRO_SECTIONS_BASE = [
13
+ {
14
+ key: "cro/hero",
15
+ name: "CRO Hero",
16
+ icon: "\u{1F3AF}",
17
+ desc: "High-converting hero section",
18
+ html: '<section style="background:linear-gradient(135deg,#667eea 0%,#764ba2 100%);color:white;padding:80px 20px;text-align:center"><div style="max-width:700px;margin:0 auto"><h1 style="font-size:2.8rem;font-weight:800;margin-bottom:16px;color:white">Your Compelling Headline Here</h1><p style="font-size:1.15rem;margin-bottom:28px;opacity:0.9">A clear, benefit-focused subheadline that explains exactly what visitors get from you.</p><a href="#" style="display:inline-block;background:#f59e0b;color:white;padding:16px 40px;border-radius:6px;font-size:1.1rem;font-weight:700;text-decoration:none">Get Started Free &rarr;</a><p style="margin-top:14px;font-size:13px;opacity:0.65">&#10003; No credit card required &nbsp;&middot;&nbsp; &#10003; 14-day free trial</p></div></section>'
19
+ },
20
+ {
21
+ key: "cro/social-proof",
22
+ name: "Social Proof",
23
+ icon: "\u2B50",
24
+ desc: "Star rating + 3-column testimonials",
25
+ html: '<section style="padding:60px 20px;background:#f9fafb"><div style="max-width:1000px;margin:0 auto"><div style="text-align:center;margin-bottom:36px"><div style="font-size:2rem;color:#f59e0b;letter-spacing:4px">&#9733;&#9733;&#9733;&#9733;&#9733;</div><p style="font-weight:700;font-size:1.2rem;margin-top:4px">4.9 / 5 from 2,400+ verified reviews</p></div><div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:20px"><div style="background:white;border-radius:8px;padding:24px;box-shadow:0 1px 4px rgba(0,0,0,0.08)"><div style="color:#f59e0b;font-size:1.1rem;margin-bottom:10px">&#9733;&#9733;&#9733;&#9733;&#9733;</div><p style="font-size:14px;color:#444;margin-bottom:16px">"This product completely transformed our results. 40% increase in conversions within the first month."</p><div style="display:flex;align-items:center;gap:10px"><div style="width:36px;height:36px;border-radius:50%;background:#e0e7ff;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:14px">SJ</div><div><div style="font-weight:600;font-size:13px">Sarah Johnson</div><div style="font-size:11px;color:#888">VP Marketing, TechCorp</div></div></div></div><div style="background:white;border-radius:8px;padding:24px;box-shadow:0 1px 4px rgba(0,0,0,0.08)"><div style="color:#f59e0b;font-size:1.1rem;margin-bottom:10px">&#9733;&#9733;&#9733;&#9733;&#9733;</div><p style="font-size:14px;color:#444;margin-bottom:16px">"The ROI was immediate. Best investment we have made this year. Highly recommend to any serious marketer."</p><div style="display:flex;align-items:center;gap:10px"><div style="width:36px;height:36px;border-radius:50%;background:#dcfce7;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:14px">MC</div><div><div style="font-weight:600;font-size:13px">Mark Chen</div><div style="font-size:11px;color:#888">Founder, GrowthLabs</div></div></div></div><div style="background:white;border-radius:8px;padding:24px;box-shadow:0 1px 4px rgba(0,0,0,0.08)"><div style="color:#f59e0b;font-size:1.1rem;margin-bottom:10px">&#9733;&#9733;&#9733;&#9733;&#9733;</div><p style="font-size:14px;color:#444;margin-bottom:16px">"Incredible support and the product delivers exactly what it promises. Our entire team loves it."</p><div style="display:flex;align-items:center;gap:10px"><div style="width:36px;height:36px;border-radius:50%;background:#fee2e2;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:14px">AT</div><div><div style="font-weight:600;font-size:13px">Amanda Torres</div><div style="font-size:11px;color:#888">CMO, ScaleUp Inc</div></div></div></div></div></div></section>'
26
+ },
27
+ {
28
+ key: "cro/urgency-banner",
29
+ name: "Urgency Banner",
30
+ icon: "\u23F0",
31
+ desc: "Countdown timer + limited-time offer",
32
+ html: '<div style="background:#ef4444;color:white;text-align:center;padding:14px;font-size:14px;font-weight:500">&#128293; Limited time offer: <strong>50% OFF</strong> ends in <span style="font-family:monospace;font-size:1rem;font-weight:700;background:rgba(0,0,0,0.2);padding:2px 10px;border-radius:4px;margin:0 6px">23:47:12</span>&mdash; <a href="#" style="color:white;font-weight:700;text-decoration:underline">Claim your discount &rarr;</a></div>'
33
+ },
34
+ {
35
+ key: "cro/trust-badges",
36
+ name: "Trust Badges",
37
+ icon: "\u{1F6E1}\uFE0F",
38
+ desc: "Security seals, payment icons, guarantees",
39
+ html: '<div style="padding:28px 20px;border-top:1px solid #e5e5e5;border-bottom:1px solid #e5e5e5;background:white"><div style="display:flex;flex-wrap:wrap;justify-content:center;align-items:center;gap:32px;max-width:700px;margin:0 auto"><div style="text-align:center;font-size:12px;color:#555"><div style="font-size:28px;margin-bottom:6px">&#128274;</div><div style="font-weight:600">SSL Secure</div><div style="font-size:10px;color:#888">256-bit encryption</div></div><div style="text-align:center;font-size:12px;color:#555"><div style="font-size:28px;margin-bottom:6px">&#8629;</div><div style="font-weight:600">30-Day Guarantee</div><div style="font-size:10px;color:#888">Money back, no questions</div></div><div style="text-align:center;font-size:12px;color:#555"><div style="font-size:28px;margin-bottom:6px">&#11088;</div><div style="font-weight:600">4.9 / 5 Rating</div><div style="font-size:10px;color:#888">From 2,400+ reviews</div></div><div style="text-align:center;font-size:12px;color:#555"><div style="font-size:28px;margin-bottom:6px">&#128179;</div><div style="font-weight:600">Secure Payment</div><div style="font-size:10px;color:#888">Visa &middot; MC &middot; PayPal</div></div></div></div>'
40
+ },
41
+ {
42
+ key: "cro/cta-block",
43
+ name: "CTA Block",
44
+ icon: "\u{1F4E3}",
45
+ desc: "Dark high-impact call-to-action section",
46
+ html: '<section style="background:#1a1a2e;color:white;padding:64px 20px;text-align:center"><div style="max-width:600px;margin:0 auto"><h2 style="font-size:2rem;font-weight:800;color:white;margin-bottom:10px">Ready to get started?</h2><p style="opacity:0.7;margin-bottom:32px">Join 10,000+ companies already seeing results with our platform.</p><div style="display:flex;gap:12px;justify-content:center;flex-wrap:wrap"><a href="#" style="display:inline-block;background:#f59e0b;color:white;padding:14px 36px;border-radius:6px;font-weight:700;text-decoration:none;font-size:1rem">Start Free Trial</a><a href="#" style="display:inline-block;border:2px solid rgba(255,255,255,0.4);color:white;padding:14px 36px;border-radius:6px;font-weight:600;text-decoration:none;font-size:1rem">Schedule Demo</a></div><p style="font-size:12px;margin-top:18px;opacity:0.45">No credit card required &middot; Cancel anytime</p></div></section>'
47
+ },
48
+ {
49
+ key: "cro/feature-grid",
50
+ name: "Feature Grid",
51
+ icon: "\u2728",
52
+ desc: "3-column key benefits grid",
53
+ html: '<section style="padding:60px 20px;background:white"><div style="max-width:900px;margin:0 auto"><h2 style="text-align:center;font-size:1.8rem;font-weight:800;margin-bottom:48px">Why choose us?</h2><div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:32px;text-align:center"><div><div style="font-size:2.5rem;margin-bottom:14px">&#128640;</div><h5 style="font-weight:700;margin-bottom:8px">Fast Results</h5><p style="color:#666;font-size:14px">See improvements in your conversion rate within the first 30 days or your money back.</p></div><div><div style="font-size:2.5rem;margin-bottom:14px">&#128202;</div><h5 style="font-weight:700;margin-bottom:8px">Data-Driven</h5><p style="color:#666;font-size:14px">Every decision backed by real user data and statistical significance testing.</p></div><div><div style="font-size:2.5rem;margin-bottom:14px">&#127919;</div><h5 style="font-weight:700;margin-bottom:8px">Precision Targeting</h5><p style="color:#666;font-size:14px">Target the right audience segments with personalized, high-converting experiences.</p></div></div></div></section>'
54
+ },
55
+ {
56
+ key: "cro/exit-intent-bar",
57
+ name: "Exit Intent Bar",
58
+ icon: "\u{1F6AA}",
59
+ desc: "Sticky bottom bar for exit-intent capture",
60
+ html: `<div style="position:fixed;bottom:0;left:0;right:0;background:#1e293b;color:white;padding:14px 20px;display:flex;align-items:center;justify-content:center;gap:16px;z-index:9999;box-shadow:0 -4px 20px rgba(0,0,0,0.2)"><span style="font-size:14px">&#128276; <strong>Wait!</strong> Don't leave empty-handed &mdash; get 20% off your first order.</span><a href="#" style="background:#f59e0b;color:white;padding:8px 20px;border-radius:4px;font-weight:700;text-decoration:none;font-size:13px;white-space:nowrap">Claim Offer</a><a href="#" style="color:rgba(255,255,255,0.5);font-size:12px;text-decoration:none">No thanks</a></div>`
61
+ },
62
+ {
63
+ key: "cro/pricing-table",
64
+ name: "Pricing Table",
65
+ icon: "\u{1F4B0}",
66
+ desc: "3-tier pricing with highlighted plan",
67
+ html: '<section style="padding:60px 20px;background:#f8fafc"><div style="max-width:900px;margin:0 auto"><h2 style="text-align:center;font-weight:800;font-size:1.8rem;margin-bottom:8px">Simple, transparent pricing</h2><p style="text-align:center;color:#666;margin-bottom:48px">No hidden fees. Cancel anytime.</p><div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:20px"><div style="background:white;border:1px solid #e5e5e5;border-radius:10px;padding:28px"><h3 style="font-weight:700;margin-bottom:6px">Starter</h3><div style="font-size:2rem;font-weight:800;margin-bottom:16px">$29<span style="font-size:1rem;font-weight:400;color:#888">/mo</span></div><ul style="list-style:none;padding:0;font-size:14px;color:#555;margin-bottom:24px"><li style="padding:6px 0;border-bottom:1px solid #f0f0f0">&#10003; Up to 10,000 visitors/mo</li><li style="padding:6px 0;border-bottom:1px solid #f0f0f0">&#10003; 5 A/B tests</li><li style="padding:6px 0">&#10003; Email support</li></ul><a href="#" style="display:block;text-align:center;border:2px solid #e5e5e5;padding:10px;border-radius:6px;font-weight:600;text-decoration:none;color:#333">Get started</a></div><div style="background:#3b82f6;border-radius:10px;padding:28px;position:relative;transform:scale(1.04);box-shadow:0 8px 32px rgba(59,130,246,0.3)"><div style="position:absolute;top:-12px;left:50%;transform:translateX(-50%);background:#f59e0b;color:white;font-size:11px;font-weight:700;padding:4px 12px;border-radius:20px">MOST POPULAR</div><h3 style="font-weight:700;margin-bottom:6px;color:white">Pro</h3><div style="font-size:2rem;font-weight:800;margin-bottom:16px;color:white">$79<span style="font-size:1rem;font-weight:400;opacity:0.7">/mo</span></div><ul style="list-style:none;padding:0;font-size:14px;color:rgba(255,255,255,0.85);margin-bottom:24px"><li style="padding:6px 0;border-bottom:1px solid rgba(255,255,255,0.15)">&#10003; Up to 100,000 visitors/mo</li><li style="padding:6px 0;border-bottom:1px solid rgba(255,255,255,0.15)">&#10003; Unlimited A/B tests</li><li style="padding:6px 0">&#10003; Priority support</li></ul><a href="#" style="display:block;text-align:center;background:white;color:#3b82f6;padding:10px;border-radius:6px;font-weight:700;text-decoration:none">Get started</a></div><div style="background:white;border:1px solid #e5e5e5;border-radius:10px;padding:28px"><h3 style="font-weight:700;margin-bottom:6px">Enterprise</h3><div style="font-size:2rem;font-weight:800;margin-bottom:16px">Custom</div><ul style="list-style:none;padding:0;font-size:14px;color:#555;margin-bottom:24px"><li style="padding:6px 0;border-bottom:1px solid #f0f0f0">&#10003; Unlimited visitors</li><li style="padding:6px 0;border-bottom:1px solid #f0f0f0">&#10003; White-label option</li><li style="padding:6px 0">&#10003; Dedicated support</li></ul><a href="#" style="display:block;text-align:center;border:2px solid #e5e5e5;padding:10px;border-radius:6px;font-weight:600;text-decoration:none;color:#333">Contact sales</a></div></div></div></section>'
68
+ }
69
+ ];
70
+ var CRO_SECTIONS = CRO_SECTIONS_BASE.map((section) => ({
71
+ ...section,
72
+ image: croSectionImageFilename(section.key)
73
+ }));
74
+
75
+ // src/visualEditorProxyPlugin.ts
76
+ var PACKAGE_ROOT = path.dirname(fileURLToPath(import.meta.url));
77
+ function resolveCroSectionImagePath(filename) {
78
+ const safe = path.basename(filename);
79
+ if (!safe || safe !== filename) return null;
80
+ const candidates = [
81
+ path.join(PACKAGE_ROOT, "images", "cro-sections", safe),
82
+ path.join(PACKAGE_ROOT, "..", "src", "images", "cro-sections", safe),
83
+ path.join(process.cwd(), "src", "images", "cro-sections", safe)
84
+ ];
85
+ for (const candidate of candidates) {
86
+ try {
87
+ if (fs.existsSync(candidate)) return candidate;
88
+ } catch (_) {
89
+ }
90
+ }
91
+ return null;
92
+ }
93
+ function listCroSectionImageFiles() {
94
+ const dirs = [
95
+ path.join(PACKAGE_ROOT, "images", "cro-sections"),
96
+ path.join(PACKAGE_ROOT, "..", "src", "images", "cro-sections"),
97
+ path.join(process.cwd(), "src", "images", "cro-sections")
98
+ ];
99
+ for (const dir of dirs) {
100
+ try {
101
+ if (!fs.existsSync(dir)) continue;
102
+ return fs.readdirSync(dir).filter((name) => name.endsWith(".png")).map((name) => path.join(dir, name));
103
+ } catch (_) {
104
+ }
105
+ }
106
+ return [];
107
+ }
6
108
  var DEFAULT_TRACKING_MARKERS = [
7
109
  "snowplow",
8
110
  "taboola",
@@ -118,7 +220,9 @@ var DEFAULT_TRACKING_MARKERS = [
118
220
  "getclicky",
119
221
  "clicky.com",
120
222
  "backend.shrinetheme.com",
121
- "backend.shrinetheme.com/api/analytics"
223
+ "backend.shrinetheme.com/api/analytics",
224
+ "backend.shrinetheme.com/api/analytics/v2/stop",
225
+ "https://backend.shrinetheme.com/api/analytics/v2/stop"
122
226
  ];
123
227
  function normalizeTrackingMarkers(input) {
124
228
  if (!Array.isArray(input)) return [];
@@ -252,64 +356,6 @@ catch(_){}})();</script>`;
252
356
  var AI_SYSTEM_PROMPT = `You are a CRO expert. Return JSON only with: hypothesis, mutations, summary. Keep 2-6 precise mutations and only valid selectors from snapshot.`;
253
357
  var BRIDGE_SCRIPT = `(function(){if(window.__CONVERSION_BRIDGE_LOADED__)return;window.__CONVERSION_BRIDGE_LOADED__=true;var CHANNEL='conversion-editor';var highlightEl=null;function send(payload){window.parent.postMessage({channel:CHANNEL,payload:payload},'*');}function qs(selector){try{return document.querySelector(selector);}catch(_){return null;}}function getSelector(el){if(!el||el.nodeType!==1)return'';if(el.id)return'#'+CSS.escape(el.id);var parts=[];var current=el;var depth=0;while(current&&current.nodeType===1&&depth<5){var part=current.tagName.toLowerCase();if(current.classList&&current.classList.length){part+='.'+Array.from(current.classList).slice(0,2).map(function(c){return CSS.escape(c);}).join('.');}var parent=current.parentElement;if(parent){var siblings=Array.from(parent.children).filter(function(s){return s.tagName===current.tagName;});if(siblings.length>1)part+=':nth-of-type('+(siblings.indexOf(current)+1)+')';}parts.unshift(part);current=parent;depth+=1;}return parts.join(' > ');}function payloadOf(el){var rect=el.getBoundingClientRect();var style=window.getComputedStyle(el);return{selector:getSelector(el),tagName:el.tagName.toLowerCase(),textContent:(el.textContent||'').trim().slice(0,500),computedStyles:{color:style.color,backgroundColor:style.backgroundColor,fontSize:style.fontSize,fontWeight:style.fontWeight,lineHeight:style.lineHeight,display:style.display},rect:{top:rect.top,left:rect.left,width:rect.width,height:rect.height}};}function applyMutation(m){var el=qs(m.selector);if(!el)return;switch(m.action){case'setStyle':if(m.property)el.style[m.property]=m.value;break;case'setText':el.textContent=m.value;break;case'setHTML':el.innerHTML=m.value;break;case'setAttribute':if(m.property)el.setAttribute(m.property,m.value);break;case'hide':el.style.display='none';break;case'show':el.style.display='';break;case'insertHTML':if(m.position)el.insertAdjacentHTML(m.position,m.value||'');break;case'insertSection':if(m.position)el.insertAdjacentHTML(m.position,m.sectionHtml||m.value||'');break;case'reorderElement':if(!m.targetSelector)break;var target=qs(m.targetSelector);if(!target||!target.parentNode||!el.parentNode)break;if(m.insertPosition==='before')target.parentNode.insertBefore(el,target);else target.parentNode.insertBefore(el,target.nextSibling);break;default:break;}}function revertMutation(m){var el=qs(m.selector);if(!el)return;switch(m.action){case'setStyle':if(m.property)el.style[m.property]=m.previous||'';break;case'setText':el.textContent=m.previous||'';break;case'setHTML':el.innerHTML=m.previous||'';break;case'setAttribute':if(m.property){if(m.previous!=null)el.setAttribute(m.property,m.previous);else el.removeAttribute(m.property);}break;case'hide':case'show':el.style.display=m.previous||'';break;default:break;}}function captureSnapshot(){var elements=Array.from(document.querySelectorAll('h1,h2,h3,p,button,a,input,select,img,section,div')).slice(0,700).map(function(el){var rect=el.getBoundingClientRect();var cs=window.getComputedStyle(el);return{selector:getSelector(el),parentSelector:el.parentElement?getSelector(el.parentElement):null,tagName:el.tagName.toLowerCase(),textContent:(el.textContent||'').trim().slice(0,300),attributes:Array.from(el.attributes||[]).reduce(function(acc,a){acc[a.name]=a.value;return acc;},{}),computedStyles:{color:cs.color,backgroundColor:cs.backgroundColor,fontSize:cs.fontSize,fontWeight:cs.fontWeight,display:cs.display},childrenCount:el.children?el.children.length:0,aboveFold:rect.top<window.innerHeight,depth:(function(){var d=0,p=el.parentElement;while(p&&d<25){d++;p=p.parentElement;}return d;})()};});send({type:'snapshotCaptured',snapshot:{url:window.location.href,title:document.title,viewport:{width:window.innerWidth,height:window.innerHeight},elements:elements}});}function captureTree(){function toNode(el,depth){var rect=el.getBoundingClientRect();var tag=el.tagName.toLowerCase();var type='generic';if(tag==='section')type='section';else if(tag==='img')type='image';else if(tag==='a')type='link';else if(tag==='button')type='button';else if(tag==='form')type='form';else if(['p','h1','h2','h3','h4','h5','h6','span'].indexOf(tag)>=0)type='text';else if(['div','main','article','aside','header','footer','nav'].indexOf(tag)>=0)type='container';var children=Array.from(el.children||[]).slice(0,50).map(function(c){return toNode(c,depth+1);});return{id:getSelector(el),selector:getSelector(el),tagName:tag,label:(el.getAttribute('aria-label')||el.getAttribute('id')||el.className||tag).toString().slice(0,80),type:type,children:children,depth:depth,isAboveFold:rect.top<window.innerHeight,rect:{top:rect.top,left:rect.left,width:rect.width,height:rect.height}};}send({type:'pageTreeCaptured',tree:[toNode(document.body,0)]});}window.addEventListener('message',function(e){var msg=e.data;if(!msg||msg.channel!==CHANNEL||!msg.payload)return;var p=msg.payload;switch(p.type){case'ping':send({type:'pong'});break;case'applyMutation':applyMutation(p.mutation);break;case'applyMutationBatch':(p.mutations||[]).forEach(applyMutation);break;case'revert':revertMutation(p.mutation);break;case'clearAllMutations':window.location.reload();break;case'captureSnapshot':captureSnapshot();break;case'validateSelectors':send({type:'selectorsValidated',results:(p.selectors||[]).map(function(s){var el=qs(s);return{selector:s,found:!!el,tagName:el?el.tagName.toLowerCase():null};})});break;case'scrollToElement':case'selectElement':var target=qs(p.selector);if(target){target.scrollIntoView({behavior:'smooth',block:'center'});send({type:'elementSelected',element:payloadOf(target)});}break;case'hoverElement':var h=qs(p.selector);if(h){if(highlightEl)highlightEl.style.outline='';h.style.outline='2px solid #3b82f6';highlightEl=h;}break;case'capturePageTree':captureTree();break;default:break;}});document.addEventListener('click',function(e){var el=e.target;if(!(el instanceof Element))return;send({type:'elementSelected',element:payloadOf(el)});},true);send({type:'bridgeReady'});})();`;
254
358
  function buildVvvebEditorHtml() {
255
- const CRO_SECTIONS = [
256
- {
257
- key: "cro/hero",
258
- name: "CRO Hero",
259
- icon: "\u{1F3AF}",
260
- desc: "High-converting hero section",
261
- html: '<section style="background:linear-gradient(135deg,#667eea 0%,#764ba2 100%);color:white;padding:80px 20px;text-align:center"><div style="max-width:700px;margin:0 auto"><h1 style="font-size:2.8rem;font-weight:800;margin-bottom:16px;color:white">Your Compelling Headline Here</h1><p style="font-size:1.15rem;margin-bottom:28px;opacity:0.9">A clear, benefit-focused subheadline that explains exactly what visitors get from you.</p><a href="#" style="display:inline-block;background:#f59e0b;color:white;padding:16px 40px;border-radius:6px;font-size:1.1rem;font-weight:700;text-decoration:none">Get Started Free &rarr;</a><p style="margin-top:14px;font-size:13px;opacity:0.65">&#10003; No credit card required &nbsp;&middot;&nbsp; &#10003; 14-day free trial</p></div></section>'
262
- },
263
- {
264
- key: "cro/social-proof",
265
- name: "Social Proof",
266
- icon: "\u2B50",
267
- desc: "Star rating + 3-column testimonials",
268
- html: '<section style="padding:60px 20px;background:#f9fafb"><div style="max-width:1000px;margin:0 auto"><div style="text-align:center;margin-bottom:36px"><div style="font-size:2rem;color:#f59e0b;letter-spacing:4px">&#9733;&#9733;&#9733;&#9733;&#9733;</div><p style="font-weight:700;font-size:1.2rem;margin-top:4px">4.9 / 5 from 2,400+ verified reviews</p></div><div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:20px"><div style="background:white;border-radius:8px;padding:24px;box-shadow:0 1px 4px rgba(0,0,0,0.08)"><div style="color:#f59e0b;font-size:1.1rem;margin-bottom:10px">&#9733;&#9733;&#9733;&#9733;&#9733;</div><p style="font-size:14px;color:#444;margin-bottom:16px">"This product completely transformed our results. 40% increase in conversions within the first month."</p><div style="display:flex;align-items:center;gap:10px"><div style="width:36px;height:36px;border-radius:50%;background:#e0e7ff;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:14px">SJ</div><div><div style="font-weight:600;font-size:13px">Sarah Johnson</div><div style="font-size:11px;color:#888">VP Marketing, TechCorp</div></div></div></div><div style="background:white;border-radius:8px;padding:24px;box-shadow:0 1px 4px rgba(0,0,0,0.08)"><div style="color:#f59e0b;font-size:1.1rem;margin-bottom:10px">&#9733;&#9733;&#9733;&#9733;&#9733;</div><p style="font-size:14px;color:#444;margin-bottom:16px">"The ROI was immediate. Best investment we have made this year. Highly recommend to any serious marketer."</p><div style="display:flex;align-items:center;gap:10px"><div style="width:36px;height:36px;border-radius:50%;background:#dcfce7;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:14px">MC</div><div><div style="font-weight:600;font-size:13px">Mark Chen</div><div style="font-size:11px;color:#888">Founder, GrowthLabs</div></div></div></div><div style="background:white;border-radius:8px;padding:24px;box-shadow:0 1px 4px rgba(0,0,0,0.08)"><div style="color:#f59e0b;font-size:1.1rem;margin-bottom:10px">&#9733;&#9733;&#9733;&#9733;&#9733;</div><p style="font-size:14px;color:#444;margin-bottom:16px">"Incredible support and the product delivers exactly what it promises. Our entire team loves it."</p><div style="display:flex;align-items:center;gap:10px"><div style="width:36px;height:36px;border-radius:50%;background:#fee2e2;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:14px">AT</div><div><div style="font-weight:600;font-size:13px">Amanda Torres</div><div style="font-size:11px;color:#888">CMO, ScaleUp Inc</div></div></div></div></div></div></section>'
269
- },
270
- {
271
- key: "cro/urgency-banner",
272
- name: "Urgency Banner",
273
- icon: "\u23F0",
274
- desc: "Countdown timer + limited-time offer",
275
- html: '<div style="background:#ef4444;color:white;text-align:center;padding:14px;font-size:14px;font-weight:500">&#128293; Limited time offer: <strong>50% OFF</strong> ends in <span style="font-family:monospace;font-size:1rem;font-weight:700;background:rgba(0,0,0,0.2);padding:2px 10px;border-radius:4px;margin:0 6px">23:47:12</span>&mdash; <a href="#" style="color:white;font-weight:700;text-decoration:underline">Claim your discount &rarr;</a></div>'
276
- },
277
- {
278
- key: "cro/trust-badges",
279
- name: "Trust Badges",
280
- icon: "\u{1F6E1}\uFE0F",
281
- desc: "Security seals, payment icons, guarantees",
282
- html: '<div style="padding:28px 20px;border-top:1px solid #e5e5e5;border-bottom:1px solid #e5e5e5;background:white"><div style="display:flex;flex-wrap:wrap;justify-content:center;align-items:center;gap:32px;max-width:700px;margin:0 auto"><div style="text-align:center;font-size:12px;color:#555"><div style="font-size:28px;margin-bottom:6px">&#128274;</div><div style="font-weight:600">SSL Secure</div><div style="font-size:10px;color:#888">256-bit encryption</div></div><div style="text-align:center;font-size:12px;color:#555"><div style="font-size:28px;margin-bottom:6px">&#8629;</div><div style="font-weight:600">30-Day Guarantee</div><div style="font-size:10px;color:#888">Money back, no questions</div></div><div style="text-align:center;font-size:12px;color:#555"><div style="font-size:28px;margin-bottom:6px">&#11088;</div><div style="font-weight:600">4.9 / 5 Rating</div><div style="font-size:10px;color:#888">From 2,400+ reviews</div></div><div style="text-align:center;font-size:12px;color:#555"><div style="font-size:28px;margin-bottom:6px">&#128179;</div><div style="font-weight:600">Secure Payment</div><div style="font-size:10px;color:#888">Visa &middot; MC &middot; PayPal</div></div></div></div>'
283
- },
284
- {
285
- key: "cro/cta-block",
286
- name: "CTA Block",
287
- icon: "\u{1F4E3}",
288
- desc: "Dark high-impact call-to-action section",
289
- html: '<section style="background:#1a1a2e;color:white;padding:64px 20px;text-align:center"><div style="max-width:600px;margin:0 auto"><h2 style="font-size:2rem;font-weight:800;color:white;margin-bottom:10px">Ready to get started?</h2><p style="opacity:0.7;margin-bottom:32px">Join 10,000+ companies already seeing results with our platform.</p><div style="display:flex;gap:12px;justify-content:center;flex-wrap:wrap"><a href="#" style="display:inline-block;background:#f59e0b;color:white;padding:14px 36px;border-radius:6px;font-weight:700;text-decoration:none;font-size:1rem">Start Free Trial</a><a href="#" style="display:inline-block;border:2px solid rgba(255,255,255,0.4);color:white;padding:14px 36px;border-radius:6px;font-weight:600;text-decoration:none;font-size:1rem">Schedule Demo</a></div><p style="font-size:12px;margin-top:18px;opacity:0.45">No credit card required &middot; Cancel anytime</p></div></section>'
290
- },
291
- {
292
- key: "cro/feature-grid",
293
- name: "Feature Grid",
294
- icon: "\u2728",
295
- desc: "3-column key benefits grid",
296
- html: '<section style="padding:60px 20px;background:white"><div style="max-width:900px;margin:0 auto"><h2 style="text-align:center;font-size:1.8rem;font-weight:800;margin-bottom:48px">Why choose us?</h2><div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:32px;text-align:center"><div><div style="font-size:2.5rem;margin-bottom:14px">&#128640;</div><h5 style="font-weight:700;margin-bottom:8px">Fast Results</h5><p style="color:#666;font-size:14px">See improvements in your conversion rate within the first 30 days or your money back.</p></div><div><div style="font-size:2.5rem;margin-bottom:14px">&#128202;</div><h5 style="font-weight:700;margin-bottom:8px">Data-Driven</h5><p style="color:#666;font-size:14px">Every decision backed by real user data and statistical significance testing.</p></div><div><div style="font-size:2.5rem;margin-bottom:14px">&#127919;</div><h5 style="font-weight:700;margin-bottom:8px">Precision Targeting</h5><p style="color:#666;font-size:14px">Target the right audience segments with personalized, high-converting experiences.</p></div></div></div></section>'
297
- },
298
- {
299
- key: "cro/exit-intent-bar",
300
- name: "Exit Intent Bar",
301
- icon: "\u{1F6AA}",
302
- desc: "Sticky bottom bar for exit-intent capture",
303
- html: `<div style="position:fixed;bottom:0;left:0;right:0;background:#1e293b;color:white;padding:14px 20px;display:flex;align-items:center;justify-content:center;gap:16px;z-index:9999;box-shadow:0 -4px 20px rgba(0,0,0,0.2)"><span style="font-size:14px">&#128276; <strong>Wait!</strong> Don't leave empty-handed &mdash; get 20% off your first order.</span><a href="#" style="background:#f59e0b;color:white;padding:8px 20px;border-radius:4px;font-weight:700;text-decoration:none;font-size:13px;white-space:nowrap">Claim Offer</a><a href="#" style="color:rgba(255,255,255,0.5);font-size:12px;text-decoration:none">No thanks</a></div>`
304
- },
305
- {
306
- key: "cro/pricing-table",
307
- name: "Pricing Table",
308
- icon: "\u{1F4B0}",
309
- desc: "3-tier pricing with highlighted plan",
310
- html: '<section style="padding:60px 20px;background:#f8fafc"><div style="max-width:900px;margin:0 auto"><h2 style="text-align:center;font-weight:800;font-size:1.8rem;margin-bottom:8px">Simple, transparent pricing</h2><p style="text-align:center;color:#666;margin-bottom:48px">No hidden fees. Cancel anytime.</p><div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:20px"><div style="background:white;border:1px solid #e5e5e5;border-radius:10px;padding:28px"><h3 style="font-weight:700;margin-bottom:6px">Starter</h3><div style="font-size:2rem;font-weight:800;margin-bottom:16px">$29<span style="font-size:1rem;font-weight:400;color:#888">/mo</span></div><ul style="list-style:none;padding:0;font-size:14px;color:#555;margin-bottom:24px"><li style="padding:6px 0;border-bottom:1px solid #f0f0f0">&#10003; Up to 10,000 visitors/mo</li><li style="padding:6px 0;border-bottom:1px solid #f0f0f0">&#10003; 5 A/B tests</li><li style="padding:6px 0">&#10003; Email support</li></ul><a href="#" style="display:block;text-align:center;border:2px solid #e5e5e5;padding:10px;border-radius:6px;font-weight:600;text-decoration:none;color:#333">Get started</a></div><div style="background:#3b82f6;border-radius:10px;padding:28px;position:relative;transform:scale(1.04);box-shadow:0 8px 32px rgba(59,130,246,0.3)"><div style="position:absolute;top:-12px;left:50%;transform:translateX(-50%);background:#f59e0b;color:white;font-size:11px;font-weight:700;padding:4px 12px;border-radius:20px">MOST POPULAR</div><h3 style="font-weight:700;margin-bottom:6px;color:white">Pro</h3><div style="font-size:2rem;font-weight:800;margin-bottom:16px;color:white">$79<span style="font-size:1rem;font-weight:400;opacity:0.7">/mo</span></div><ul style="list-style:none;padding:0;font-size:14px;color:rgba(255,255,255,0.85);margin-bottom:24px"><li style="padding:6px 0;border-bottom:1px solid rgba(255,255,255,0.15)">&#10003; Up to 100,000 visitors/mo</li><li style="padding:6px 0;border-bottom:1px solid rgba(255,255,255,0.15)">&#10003; Unlimited A/B tests</li><li style="padding:6px 0">&#10003; Priority support</li></ul><a href="#" style="display:block;text-align:center;background:white;color:#3b82f6;padding:10px;border-radius:6px;font-weight:700;text-decoration:none">Get started</a></div><div style="background:white;border:1px solid #e5e5e5;border-radius:10px;padding:28px"><h3 style="font-weight:700;margin-bottom:6px">Enterprise</h3><div style="font-size:2rem;font-weight:800;margin-bottom:16px">Custom</div><ul style="list-style:none;padding:0;font-size:14px;color:#555;margin-bottom:24px"><li style="padding:6px 0;border-bottom:1px solid #f0f0f0">&#10003; Unlimited visitors</li><li style="padding:6px 0;border-bottom:1px solid #f0f0f0">&#10003; White-label option</li><li style="padding:6px 0">&#10003; Dedicated support</li></ul><a href="#" style="display:block;text-align:center;border:2px solid #e5e5e5;padding:10px;border-radius:6px;font-weight:600;text-decoration:none;color:#333">Contact sales</a></div></div></div></section>'
311
- }
312
- ];
313
359
  const sectionsJson = JSON.stringify(CRO_SECTIONS);
314
360
  return `<!DOCTYPE html>
315
361
  <html lang="en">
@@ -317,27 +363,39 @@ function buildVvvebEditorHtml() {
317
363
  <meta charset="UTF-8">
318
364
  <meta name="viewport" content="width=device-width,initial-scale=1">
319
365
  <title>Visual Editor V2</title>
366
+ <link rel="preconnect" href="https://fonts.googleapis.com">
367
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
368
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">
320
369
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
321
370
  <style>
322
371
  /* \u2500\u2500 Reset \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
323
372
  *{box-sizing:border-box;margin:0;padding:0}
324
- html,body{height:100%;overflow:hidden;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Inter,Roboto,sans-serif;font-size:13px;color:#1e293b;background:#f1f5f9}
373
+ html,body{height:100%;overflow:hidden;font-family:var(--font-sans);font-size:13px;color:#1e293b;background:#f1f5f9}
325
374
 
326
375
  /* \u2500\u2500 CSS variables \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
327
376
  :root{
377
+ --font-inter:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
378
+ --font-roboto:'Roboto',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
379
+ --font-mono:'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;
380
+ --font-sans:var(--font-inter),var(--font-roboto),-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
328
381
  --bg: #ffffff;
329
382
  --bg-sub: #f8fafc;
330
- --bg-hover: #f1f5f9;
383
+ --bg-hover: #F5F5F5;
331
384
  --border: #e2e8f0;
332
385
  --border-sub: #f1f5f9;
333
386
  --text: #404040;
334
387
  --text-2: #475569;
335
388
  --text-3: #94a3b8;
336
- --accent: #6366f1;
337
- --accent-bg: #eef2ff;
389
+ --accent: #262626;
390
+ --accent-bg: #f5f5f5;
338
391
  --accent-txt: #404040;
339
392
  }
340
393
 
394
+ /* \u2500\u2500 Font utilities \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
395
+ .inter{font-family:var(--font-inter)}
396
+ .roboto{font-family:var(--font-roboto)}
397
+ .mono{font-family:var(--font-mono)}
398
+
341
399
  /* \u2500\u2500 Layout \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
342
400
  #app{display:flex;flex-direction:column;height:100vh}
343
401
  #main{display:flex;flex:1;overflow:hidden;min-height:0}
@@ -415,9 +473,32 @@ box-shadow: 0 0 1px 0 rgba(0, 0, 0, 0.20), 0 1px 2px 0 rgba(0, 0, 0, 0.05), 0 1p
415
473
  border-color:transparent;
416
474
  }
417
475
  .tb-dev-menu input#dev-zoom-level{
418
- max-width: fit-content;
419
- margin-left: auto;
476
+ max-width:72px;
477
+ margin-left:0;
420
478
  }
479
+ .tb-dev-menu .row-zoom .zoom-controls{
480
+ display:flex;
481
+ align-items:center;
482
+ gap:6px;
483
+ margin-left:auto;
484
+ }
485
+ .tb-dev-menu .dev-zoom-fit-btn{
486
+ width:30px;
487
+ height:30px;
488
+ flex-shrink:0;
489
+ border:none;
490
+ border-radius:6px;
491
+ background:#fff;
492
+ cursor:pointer;
493
+ display:flex;
494
+ align-items:center;
495
+ justify-content:center;
496
+ color:#404040;
497
+ box-shadow:0 0 1px 0 rgba(0,0,0,.20),0 1px 2px 0 rgba(0,0,0,.05),0 1px 1px 0 rgba(0,0,0,.01);
498
+ transition:background .12s,color .12s;
499
+ }
500
+ .tb-dev-menu .dev-zoom-fit-btn:hover{background:#f4f4f5;color:#171717}
501
+ .tb-dev-menu .dev-zoom-fit-btn svg{display:block;width:16px;height:16px}
421
502
  .tb-dev-menu input:focus{
422
503
  border-color:#1A1A1A;
423
504
  box-shadow:0 0 0 2px rgba(99,102,241,.14)
@@ -447,19 +528,10 @@ box-shadow: 0 0 1px 0 rgba(0, 0, 0, 0.20), 0 1px 2px 0 rgba(0, 0, 0, 0.05), 0 1p
447
528
  text-overflow: ellipsis;
448
529
  font-size: var(--font-size-sm, 14px);
449
530
  font-style: normal;
450
- font-weight: 500;
531
+ font-weight: 500;#
451
532
  line-height: var(--font-leading-4, 16px);
452
533
  }
453
534
  .tb-dev-menu .vp-preset-btn:hover,.tb-dev-menu .vp-preset-btn.active{background:#f4f4f5}
454
- .tb-dev-menu .ft{
455
- margin-top:10px;padding-top:8px;border-top:1px solid #ececf0;
456
- display:flex;justify-content:flex-end
457
- }
458
- .tb-dev-menu .apply-btn{
459
- border:1px solid #d4d4d8;background:#f8fafc;color:#111827;border-radius:6px;
460
- height:30px;padding:0 10px;font-size:12px;font-weight:600;cursor:pointer
461
- }
462
- .tb-dev-menu .apply-btn:hover{background:#eef2ff;border-color:#a5b4fc}
463
535
  /* Dark icon buttons */
464
536
  .tb-dk-btn{width:28px;height:28px;background:transparent;border:none;border-radius:5px;cursor:pointer;color:#71717a;display:flex;align-items:center;justify-content:center;font-size:13px;transition:all .12s;flex-shrink:0}
465
537
  .tb-dk-btn:hover{color:#e4e4e7;background:rgba(255,255,255,.07)}
@@ -490,9 +562,29 @@ box-shadow: 0 0 1px 0 rgba(0, 0, 0, 0.20), 0 1px 2px 0 rgba(0, 0, 0, 0.05), 0 1p
490
562
  .tb-sim-btn{background:transparent;border:1px solid #e5e7eb;border-radius:6px;color:#404040;cursor:pointer;font-size:14px;font-weight:500;padding:6px 10px;display:flex;align-items:center;gap:5px;transition:all .15s;white-space:nowrap;font-family:inherit}
491
563
  .tb-sim-btn:hover{background:rgba(255,255,255,.06);border-color:#52525b}
492
564
  .tb-sim-btn i{font-size:11px}
493
- .tb-fin-btn{background:#09090b;border:1px solid #3f3f46;border-radius:6px;color:#fafafa;cursor:pointer;font-size:14px;font-weight:600;padding:5px 14px;transition:all .15s;white-space:nowrap;font-family:inherit}
494
- .tb-fin-btn:hover{background:#000;border-color:#52525b}
495
-
565
+ .tb-fin-btn{ border: none;
566
+ cursor: pointer;
567
+ transition: all .15s;
568
+ white-space: nowrap;
569
+ border-radius: var(--radius-md, 6px);
570
+ background: var(--bg-primary-default, #262626);
571
+ padding: 7px 10px;
572
+ color: var(--content-on-color, #FFF);
573
+ text-align: center;
574
+ font-family: Inter;
575
+ font-size: var(--font-size-sm, 14px);
576
+ font-style: normal;
577
+ font-weight: 500;
578
+ line-height: var(--font-leading-4, 16px);
579
+ }
580
+ .tb-fin-btn:hover{opacity:0.8;}
581
+ .tb-fin-btn#btn-save{
582
+ background: var(--bg-interactive-default, #FFF);
583
+ box-shadow: 0 0 1px 0 var(--Black-20, rgba(0, 0, 0, 0.20)), 0 1px 2px 0 var(--Black-5, rgba(0, 0, 0, 0.05)), 0 1px 1px 0 rgba(0, 0, 0, 0.01);
584
+ border: none;
585
+ color: var(--content-success-default, #00C951);
586
+ font-weight: 500;
587
+ }
496
588
  /* \u2500\u2500 Page loading hint (toolbar + sidebar) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
497
589
  .tb-page-loading{
498
590
  display:none;flex-shrink:0;align-items:center;margin:0 10px 0 4px;
@@ -549,14 +641,17 @@ box-shadow: 0 0 1px 0 rgba(0, 0, 0, 0.20), 0 1px 2px 0 rgba(0, 0, 0, 0.05), 0 1p
549
641
 
550
642
  /* \u2500\u2500 Left panel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
551
643
  #left-panel{
552
- width:232px;background:#FFFFFF;border-right:1px solid var(--border);
553
- display:flex;flex-direction:column;flex-shrink:0;min-height:0;overflow:hidden
644
+ width:288px;
645
+ display:flex;flex-direction:column;flex-shrink:0;min-height:0;overflow:hidden;
646
+ border-right: 1px solid var(--border-default, #E5E5E5);
647
+ background: var(--cu-Background-Main, #FFF);
554
648
  }
555
649
 
556
650
  /* \u2500\u2500 Iframe panel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
557
651
  #iframe-panel{
558
652
  flex:1;background:#e8ecf0;display:flex;flex-direction:column;
559
- align-items:center;justify-content:flex-start;overflow:auto;position:relative
653
+ align-items:center;justify-content:flex-start;overflow:hidden;position:relative;
654
+ min-height:0;min-width:0
560
655
  }
561
656
 
562
657
  /* \u2500\u2500 Floating selection toolbar (above iframe selection) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
@@ -577,13 +672,34 @@ box-shadow: 0 0 1px 0 rgba(0, 0, 0, 0.20), 0 1px 2px 0 rgba(0, 0, 0, 0.05), 0 1p
577
672
  #selection-floater .sf-sep{width:1px;height:16px;background:var(--border);margin:0 2px;flex-shrink:0}
578
673
 
579
674
  /* \u2500\u2500 DOM tree (Elements tab) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
580
- .dt-tree{font-size:11px;padding:0px 0 0px 20px;user-select:none}
675
+ .dt-tree{user-select:none}
581
676
  .dt-row{
582
- width:fit-content;display:flex;align-items:center;gap:2px;min-height:26px;padding:2px 8px 2px 4px;
583
- cursor:pointer;color:var(--text-2);border-radius:4px;margin:0 4px
677
+ width: 100%;
678
+ display: flex;
679
+ align-items: center;
680
+ gap: 2px;
681
+ min-height: 26px;
682
+ padding: 10px 8px 10px 4px;
683
+ cursor: pointer;
684
+ color: var(--text-2);
685
+ position: relative;
584
686
  }
585
687
  .dt-row:hover{background:var(--bg-hover);color:var(--text)}
586
- .dt-row.dt-selected{background:#e0e7ff!important;color:var(--accent-txt)!important;outline:1px solid #a5b4fc}
688
+ .dt-row.dt-selected{position:relative;background:var(--bg-hover);}
689
+ .dt-row.dt-selected::before, .dt-row:hover::before{
690
+ display: block;
691
+ content: '';
692
+ position: absolute;
693
+ top: 0;
694
+ left: 0px;
695
+ height: 100%;
696
+ width: 2px;
697
+ background: #262626;
698
+ }
699
+ .dt-row.dt-selected .dt-lbl .dt-tag{
700
+ background: var(--bg-primary-default, #262626)!important;
701
+ color: var(--text-primary-default, #FFF)!important;
702
+ }
587
703
  .dt-chev{
588
704
  width:16px;height:16px;flex-shrink:0;border:none;background:transparent;
589
705
  cursor:pointer;color:var(--text-3);display:flex;align-items:center;justify-content:center;
@@ -592,22 +708,36 @@ box-shadow: 0 0 1px 0 rgba(0, 0, 0, 0.20), 0 1px 2px 0 rgba(0, 0, 0, 0.05), 0 1p
592
708
  .dt-chev:hover{color:var(--text)}
593
709
  .dt-chev.dt-spacer{visibility:hidden;pointer-events:none}
594
710
  .dt-ico{width:16px;flex-shrink:0;text-align:center;color:var(--text-3);font-size:12px}
595
- .dt-lbl{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:ui-monospace,SFMono-Regular,monospace;font-size:10px}
711
+ .dt-lbl{
712
+ flex: 1;
713
+ overflow: hidden;
714
+ text-overflow: ellipsis;
715
+ white-space: nowrap;
716
+ color: var(--content-strong, #171717);
717
+ font-family: "JetBrains Mono";
718
+ }
596
719
  .dt-muted{padding:16px 12px;text-align:center;color:var(--text-3);font-size:11px;line-height:1.4}
597
720
  #section-components-panel{
598
721
  max-height: 50%;
599
722
  overflow-y: auto;
723
+ flex-shrink: 0;
724
+ display: flex;
725
+ flex-direction: column;
600
726
  }
727
+ #section-components-panel .lp-body.collapsed{display:none}
601
728
  /* \u2500\u2500 Right panel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
602
729
  #right-panel{
603
- width:252px;background:var(--bg);border-left:1px solid var(--border);
730
+ width:288px;
731
+ border-left: 1px solid var(--border-default, #E5E5E5);
732
+ background: var(--cu-Background-Main, #FFF);
604
733
  display:flex;flex-direction:column;flex-shrink:0;overflow:hidden
605
734
  }
606
735
 
607
736
  /* \u2500\u2500 Breadcrumb \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
608
737
  #breadcrumb{
738
+ display:none;
609
739
  height:26px;background:var(--bg);border-top:1px solid var(--border);
610
- display:flex;align-items:center;padding:0 12px;font-size:11px;
740
+ align-items:center;padding:0 12px;font-size:11px;
611
741
  color:var(--text-3);flex-shrink:0;gap:5px;overflow:hidden
612
742
  }
613
743
 
@@ -620,9 +750,13 @@ box-shadow: 0 0 1px 0 rgba(0, 0, 0, 0.20), 0 1px 2px 0 rgba(0, 0, 0, 0.05), 0 1p
620
750
 
621
751
  /* \u2500\u2500 Device frame \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
622
752
  #device-frame{
623
- width:100%;min-height:100%;display:flex;align-items:stretch;
624
- justify-content:center;transform-origin:top center;
625
- transition:max-width .3s ease,width .2s ease,height .2s ease
753
+ width: 100%;
754
+ min-height: 100%;
755
+ display: flex;
756
+ align-items: center;
757
+ transform-origin: top left;
758
+ transition: max-width .3s ease,width .2s ease,height .2s ease;
759
+ flex-shrink: 0;
626
760
  }
627
761
  #device-frame.desktop{
628
762
  max-width:1440px;
@@ -673,33 +807,51 @@ box-shadow: 0 0 1px 0 rgba(0, 0, 0, 0.20), 0 1px 2px 0 rgba(0, 0, 0, 0.05), 0 1p
673
807
  }
674
808
 
675
809
  /* \u2500\u2500 Left panel sections \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
676
- .lp-sec{border-bottom:1px solid var(--border);flex-shrink:0;padding: 24px;}
810
+ .lp-sec{border-bottom:1px solid var(--border);flex-shrink:0;padding: 24px 16px;}
677
811
  .lp-sec-no-border{border-bottom:none!important}
678
812
  .lp-sec-hd{
679
813
  margin-bottom: 12px;
680
814
  font-size:14px;font-weight:600;
815
+ line-height: 20px;
681
816
  color:#404040;display:flex;align-items:center;justify-content:space-between;gap:5px
682
817
  }
683
818
  .lp-sec-hd-left{display:flex;align-items:center;gap:5px}
684
819
  #active-var-label{display:none;color:var(--accent-txt);font-size:10px;font-weight:500}
685
820
  .lp-info-icon{font-size:11px;color:var(--text-3);cursor:default;opacity:.7}
686
821
  .lp-add-btn{
687
- background:none;border:none;color:var(--text);font-size:14px;font-weight:500;
688
- cursor:pointer;padding:2px 5px;border-radius:4px;transition:all .12s;flex-shrink:0
822
+ background: none;
823
+ border: none;
824
+ cursor: pointer;
825
+ padding: 2px 5px;
826
+ border-radius: 4px;
827
+ transition: all .12s;
828
+ flex-shrink: 0;
829
+ color: var(--content-default, #404040);
830
+ text-align: center;
831
+ font-family: Inter;
832
+ font-size: 12px;
833
+ font-style: normal;
834
+ font-weight: 500;
835
+ line-height: 14px;
689
836
  }
690
- .lp-add-btn:hover{background:var(--bg-hover);color:var(--accent-txt)}
837
+ .lp-add-btn:hover{opacity:0.8}
691
838
 
692
839
  /* \u2500\u2500 Variation tabs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
693
840
  #variation-tabs{display:flex;flex-direction:column; gap:8px;}
694
841
  .var-tab{
695
- border-radius: 4px;
696
- border: 1px solid #e5e7eb;
697
- background:transparent;color:var(--text);
698
- cursor:pointer;font-size:12px;font-weight:500;padding:6px 12px;transition:background .12s,color .12s;
842
+ border-radius: var(--radius-md, 6px);
843
+ border:1px solid transparent;
844
+ background: var(--bg-interactive-default, #FFF);
845
+ box-shadow: 0 0 1px 0 var(--Black-20, rgba(0, 0, 0, 0.20)), 0 1px 2px 0 var(--Black-5, rgba(0, 0, 0, 0.05)), 0 1px 1px 0 rgba(0, 0, 0, 0.01);
846
+ cursor:pointer;font-size:15px;font-weight:500;padding:7px 10px;transition:background .12s,color .12s,border-color .12s;
699
847
  width:100%;text-align:left;display:flex;align-items:center;gap:8px;
848
+ line-height: 16px;
700
849
  }
701
850
  .var-tab:hover{background:var(--bg-hover);color:var(--text)}
702
- .var-tab.active{background:var(--accent-bg);color:var(--accent-txt);font-weight: 700;}
851
+ button.var-tab.active{
852
+ color:var(--accent-txt);
853
+ border-color:var(--var-tab-color, transparent);
854
+ }
703
855
  .var-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}
704
856
  .var-add-row{
705
857
  display:none!important;
@@ -713,51 +865,207 @@ box-shadow: 0 0 1px 0 rgba(0, 0, 0, 0.20), 0 1px 2px 0 rgba(0, 0, 0, 0.05), 0 1p
713
865
  .var-add-row:hover{color:var(--accent-txt)}
714
866
 
715
867
  /* \u2500\u2500 Search \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
868
+ .lp-search-container{margin-bottom:8px}
869
+ .lp-search-field{
870
+ position:relative;
871
+ display:flex;
872
+ align-items:center;
873
+ width:100%;
874
+ }
875
+ .lp-search-icon{
876
+ position:absolute;
877
+ left:12px;
878
+ top:50%;
879
+ transform:translateY(-50%);
880
+ display:flex;
881
+ align-items:center;
882
+ justify-content:center;
883
+ width:16px;
884
+ height:16px;
885
+ pointer-events:none;
886
+ color:var(--base-sub-2,#B5B5B5);
887
+ flex-shrink:0;
888
+ }
889
+ .lp-search-icon svg{display:block;width:16px;height:16px}
890
+ .lp-search-icon svg path{stroke:currentColor}
716
891
  #comp-search{
717
- background:#fff;border:1px solid var(--border);border-radius:6px;
718
- color:var(--text);font-size:12px;padding:6px 10px;width:100%;outline:none
892
+ border:1px solid var(--border);
893
+ color:var(--text);
894
+ font-size:12px;
895
+ padding:9px 12px 9px 36px;
896
+ width:100%;
897
+ outline:none;
898
+ border-radius:6px;
899
+ background:var(--Greyscale-0,#FFF);
900
+ box-shadow:0 0 1px 0 var(--Black-20,rgba(0,0,0,.20)),0 1px 2px 0 var(--Black-5,rgba(0,0,0,.05)),0 1px 1px 0 rgba(0,0,0,.01);
719
901
  }
720
- #comp-search::placeholder{color:var(--text-3)}
721
- #comp-search:focus{border-color:var(--accent);box-shadow:0 0 0 3px rgba(99,102,241,.12)}
722
-
902
+ #comp-search::placeholder{
903
+ color:var(--base-sub-2,#B5B5B5);
904
+ font-size:var(--font-size-sm,14px);
905
+ font-style:normal;
906
+ font-weight:500;
907
+ }
908
+ #comp-search:focus{border-color:#000;}
723
909
  /* \u2500\u2500 Left tabs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
724
- .lp-tabs, .section-components-tabs{display:flex;border-bottom:1px solid var(--border);flex-shrink:0;background:#fff}
725
- .lp-tab{
726
- flex:1;padding:8px 2px;text-align:center;font-size:10px;color:var(--text-3);
727
- cursor:pointer;border-bottom:2px solid transparent;transition:all .15s;font-weight:600;line-height:1.15
910
+ .lp-tabs-container{
911
+ display:flex;
912
+ align-items:center;
913
+ justify-content:space-between;
728
914
  }
729
- .lp-tab.close-section-components-panel{
730
- padding: 4px 10px;
731
- background: #000;
915
+ .lp-tabs{
916
+ width: 100%;
917
+ padding: 4px 10px;
918
+ flex: 1;
732
919
  display: flex;
733
- max-width: fit-content;
920
+ border-radius: 6px;
921
+ background: var(--base-soft-2,#F0F0F0);
922
+ padding: 4px 6px;
923
+ flex-shrink: 0;
924
+ gap: 6px;
925
+ align-items: center;
926
+ white-space: nowrap;
927
+ }
928
+ .lp-tab{
929
+ flex: 1;
930
+ text-align: center;
931
+ color: #404040;
932
+ white-space: nowrap;
933
+ cursor: pointer;
934
+ transition: all .15s;
935
+ line-height: 1.15;
936
+ font-size: var(--font-size-sm,14px);
937
+ font-weight: 500;
938
+ padding:4px 10px;
939
+ }
940
+ .section-components-tabs-container{
941
+ display: flex;
734
942
  align-items: center;
735
- justify-content: center;
736
- border-radius: 2px;
737
- margin: 2px;
738
- color: #fff;
943
+ justify-content: space-between;
944
+ flex-shrink: 0;
945
+ padding: 12px 16px;
946
+ border-bottom: 1px solid #F5F5F5;
947
+ }
948
+ .section-components-tabs-container .lp-tabs{
949
+ flex:1;
739
950
  }
951
+ .lp-tab.close-section-components-panel{
952
+ flex:0 0 auto;
953
+ flex-shrink:0;
954
+ display:flex;
955
+ align-items:center;
956
+ justify-content:center;
957
+ width:24px;
958
+ height:24px;
959
+ padding:0;
960
+ background:none;
961
+ border-radius:4px;
962
+ color:var(--content-default,#404040);
963
+ line-height:1;
964
+ white-space:nowrap;
965
+ }
966
+ .lp-tab.close-section-components-panel i{
967
+ font-size:14px;
968
+ line-height:1;
969
+ transition:transform .15s;
970
+ }
971
+ .lp-tab.close-section-components-panel.collapsed i{transform:rotate(-180deg)}
740
972
  .lp-tab:hover{color:var(--text-2)}
741
- .lp-tab.active{color:var(--accent-txt);border-bottom-color:var(--accent)}
973
+ .lp-tab.active{
974
+ border-radius:4px;
975
+ background:var(--bg-interactive-default,#FFF);
976
+ box-shadow:0 0 1px 0 var(--Black-20,rgba(0,0,0,.20)),0 1px 2px 0 var(--Black-5,rgba(0,0,0,.05)),0 1px 1px 0 rgba(0,0,0,.01);
977
+ }
978
+ .lp-tab.close-section-components-panel:hover{opacity:.8;color:var(--text-2)}
979
+ .lp-tab.close-section-components-panel.active{
980
+ background:none;
981
+ box-shadow:none;
982
+ border-radius:4px;
983
+ }
742
984
  .future-hidden{display:none!important}
743
985
 
744
986
  /* \u2500\u2500 Left panel body \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
987
+ .lp-body #tab-dom-tree, .lp-body #tab-elements{
988
+ padding-top:4px;
989
+ }
990
+ .lp-body #tab-elements .dt-lbl{
991
+ font-size: 12px;
992
+ font-style: normal;
993
+ font-weight: 500;
994
+ line-height: 14px;
995
+ letter-spacing: -0.1px;
996
+ }
997
+ .lp-body #tab-elements #elements-root > .dt-row{
998
+ padding-left: 16px!important;
999
+ }
1000
+ .lp-body #dom-tree-root .dt-lbl{
1001
+ color: var(--content-subtle, #737373);
1002
+ font-style: normal;
1003
+ font-weight: 400;
1004
+ line-height: var(--font-leading-3, 12px); /* 100% */
1005
+ letter-spacing: -0.1px;
1006
+ }
1007
+ .lp-body #dom-tree-root .dt-lbl .dt-tag{
1008
+ color: var(--content-strong, #171717);
1009
+ font-family: "JetBrains Mono";
1010
+ font-size: 12px;
1011
+ font-style: normal;
1012
+ font-weight: 500;
1013
+ line-height: 14px;
1014
+ letter-spacing: -0.1px;
1015
+ border-radius: 4px;
1016
+ background: var(--bg-surface-subtle, #F5F5F5);
1017
+ padding:3px 6px;
1018
+ display:inline-block;
1019
+ margin-right:4px;
1020
+ }
1021
+ .lp-body #tab-elements .dt-row .dt-chev.dt-spacer{display:none;}
1022
+ .lp-body #tab-dom-tree .dt-row .dt-ico{display:none;}
745
1023
  .lp-body, .section-components-body{flex:1;overflow-y:auto}
746
1024
  .lp-body::-webkit-scrollbar, .section-components-body::-webkit-scrollbar{width:3px}
747
1025
  .lp-body::-webkit-scrollbar-thumb, .section-components-body::-webkit-scrollbar-thumb{background:#cbd5e1;border-radius:2px}
748
1026
  .tab-pane, .section-components-tab-pane{display:none}.tab-pane.active, .section-components-tab-pane.active{display:block}
749
1027
 
750
1028
  /* \u2500\u2500 Component grid \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
751
- .cg-hdr{padding:8px 10px 4px;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--text-3)}
752
- .cg-grid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:4px;padding:0 8px 8px}
1029
+ .cg-hdr{ padding: 12px 12px;
1030
+ text-transform: uppercase;
1031
+ letter-spacing: .06em;
1032
+ color: var(--content-subtle, #737373);
1033
+ font-family: Inter;
1034
+ font-size: 12px;
1035
+ font-weight: 600;
1036
+ line-height: normal;
1037
+ }
1038
+ .cg-grid{ display: grid;
1039
+ grid-template-columns: 1fr 1fr 1fr;
1040
+ padding: 0 12px 12px;
1041
+ column-gap: 8px;
1042
+ row-gap: 12px;}
753
1043
  .cg-item{
754
- background:#fff;border:1px solid var(--border);border-radius:8px;cursor:pointer;
755
- padding:10px 4px 8px;text-align:center;transition:all .15s;color:var(--text-2);user-select:none
1044
+ background: #fff;
1045
+ cursor: pointer;
1046
+ padding: 15px 11px;
1047
+ text-align: center;
1048
+ transition: all .15s;
1049
+ color: var(--text-2);
1050
+ user-select: none;
1051
+ border-radius: 6px;
1052
+ border: 1px solid var(--border-default, #E5E5E5);
756
1053
  }
757
1054
  .cg-item:hover{background:var(--accent-bg);border-color:var(--accent);color:var(--accent-txt);transform:translateY(-1px);box-shadow:0 2px 8px rgba(99,102,241,.15)}
758
1055
  .cg-item:active{transform:translateY(0);box-shadow:none}
759
- .cg-icon{font-size:18px;margin-bottom:4px}
760
- .cg-name{font-size:10px;line-height:1.2}
1056
+ .cg-icon{ margin-bottom: 6px;
1057
+ color: var(--content-default, #404040);
1058
+ font-family: Inter;
1059
+ font-size: var(--font-size-sm, 14px);
1060
+ font-style: normal;
1061
+ font-weight: 500;
1062
+ line-height: var(--font-leading-4, 16px);}
1063
+ .cg-name{ color: var(--content-subtle, #737373);
1064
+ font-family: Inter;
1065
+ font-size: var(--font-size-xs, 12px);
1066
+ font-style: normal;
1067
+ font-weight: 400;
1068
+ line-height: var(--font-leading-3, 12px);}
761
1069
 
762
1070
  /* \u2500\u2500 Section list \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
763
1071
  .sec-item{
@@ -766,8 +1074,12 @@ box-shadow: 0 0 1px 0 rgba(0, 0, 0, 0.20), 0 1px 2px 0 rgba(0, 0, 0, 0.05), 0 1p
766
1074
  }
767
1075
  .sec-item:hover{border-color:var(--accent);transform:translateY(-1px);box-shadow:0 4px 12px rgba(99,102,241,.12)}
768
1076
  .sec-thumb{
769
- background:var(--bg-sub);height:52px;display:flex;align-items:center;
770
- justify-content:center;font-size:22px;border-bottom:1px solid var(--border)
1077
+ background:var(--bg-sub);height:72px;display:flex;align-items:center;
1078
+ justify-content:center;font-size:22px;border-bottom:1px solid var(--border);
1079
+ overflow:hidden;
1080
+ }
1081
+ .sec-thumb img{
1082
+ width:100%;height:100%;object-fit:cover;object-position:top center;display:block;
771
1083
  }
772
1084
  .sec-info{padding:7px 9px}
773
1085
  .sec-name{font-size:11px;font-weight:600;color:var(--text)}
@@ -783,9 +1095,9 @@ box-shadow: 0 0 1px 0 rgba(0, 0, 0, 0.20), 0 1px 2px 0 rgba(0, 0, 0, 0.05), 0 1p
783
1095
  #no-sel .ns-icon{font-size:32px;margin-bottom:10px;opacity:.4}
784
1096
 
785
1097
  /* \u2500\u2500 Element info header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
786
- #el-info{padding:8px 12px;border-bottom:1px solid var(--border);background:var(--bg-sub);flex-shrink:0}
787
- #el-info-tag{font-size:10px;font-weight:700;color:var(--text-3);text-transform:uppercase;letter-spacing:.05em;font-family:monospace}
788
- #el-info-sel{font-size:10px;color:var(--accent-txt);font-family:monospace;margin-top:2px;word-break:break-all;opacity:.8}
1098
+ #el-info{padding:8px 12px;border-bottom:1px solid var(--border);background:var(--bg-sub);flex-shrink:0;display:none;}
1099
+ #el-info-tag{font-size:10px;font-weight:700;color:var(--text-3);text-transform:uppercase;letter-spacing:.05em;font-family:var(--font-mono)}
1100
+ #el-info-sel{font-size:10px;color:var(--accent-txt);font-family:var(--font-mono);margin-top:2px;word-break:break-all;opacity:.8}
789
1101
 
790
1102
  /* \u2500\u2500 Accordion \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
791
1103
  .acc-section{border-bottom:1px solid var(--border-sub)}
@@ -840,7 +1152,7 @@ box-shadow: 0 0 1px 0 rgba(0, 0, 0, 0.20), 0 1px 2px 0 rgba(0, 0, 0, 0.05), 0 1p
840
1152
  .custom-css-close:hover{background:var(--bg-hover);color:var(--text)}
841
1153
  #custom-css-modal-textarea{
842
1154
  width:100%;min-height:360px;max-height:58vh;resize:vertical;border:none;outline:none;
843
- font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;
1155
+ font-family:var(--font-mono);
844
1156
  font-size:12px;line-height:1.5;padding:12px;background:#0b1220;color:#e2e8f0
845
1157
  }
846
1158
  .custom-css-actions{
@@ -883,7 +1195,7 @@ select.pr-inp{cursor:pointer;background:#fff}
883
1195
  .adv-section{padding:10px}
884
1196
  .adv-row{margin-bottom:8px}
885
1197
  .adv-key{font-size:10px;color:var(--text-3);margin-bottom:3px;font-weight:700;text-transform:uppercase;letter-spacing:.05em}
886
- .adv-val{font-size:11px;color:var(--text-2);word-break:break-all;background:var(--bg-sub);padding:5px 7px;border-radius:5px;font-family:monospace;border:1px solid var(--border)}
1198
+ .adv-val{font-size:11px;color:var(--text-2);word-break:break-all;background:var(--bg-sub);padding:5px 7px;border-radius:5px;font-family:var(--font-mono);border:1px solid var(--border)}
887
1199
 
888
1200
  /* \u2500\u2500 Selected element ring + drag affordance \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
889
1201
  /* Selection chrome is injected into the iframe (see injectIframeSelectionStyles); rules here are fallback only */
@@ -917,14 +1229,37 @@ select.pr-inp{cursor:pointer;background:#fff}
917
1229
  .img-add:hover{border-color:var(--accent);color:var(--accent-txt);background:var(--accent-bg)}
918
1230
 
919
1231
  /* \u2500\u2500 Right panel main tabs (Design / States / History) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
920
- #main-tabs{display:flex;border-bottom:1px solid var(--border);background:#fff;flex-shrink:0}
1232
+ #main-tabs{
1233
+ display:flex;
1234
+ align-items:center;
1235
+ gap:6px;
1236
+ margin:12px 16px 8px;
1237
+ padding:4px 6px;
1238
+ border-radius:6px;
1239
+ background:var(--base-soft-2,#F0F0F0);
1240
+ flex-shrink:0;
1241
+ }
921
1242
  .main-tab{
922
- flex:1;padding:10px 4px;text-align:center;font-size:12px;color:var(--text-3);
923
- cursor:pointer;border:none;background:transparent;border-bottom:2px solid transparent;
924
- transition:all .15s;font-weight:600;font-family:inherit
1243
+ flex:1;
1244
+ padding:4px 10px;
1245
+ text-align:center;
1246
+ font-size:var(--font-size-sm,14px);
1247
+ font-weight:500;
1248
+ line-height:1.15;
1249
+ color:#404040;
1250
+ white-space:nowrap;
1251
+ cursor:pointer;
1252
+ border:none;
1253
+ background:transparent;
1254
+ transition:all .15s;
1255
+ font-family:inherit;
925
1256
  }
926
1257
  .main-tab:hover{color:var(--text-2)}
927
- .main-tab.active{color:var(--accent-txt);border-bottom-color:var(--accent)}
1258
+ .main-tab.active{
1259
+ border-radius:4px;
1260
+ background:var(--bg-interactive-default,#FFF);
1261
+ box-shadow:0 0 1px 0 var(--Black-20,rgba(0,0,0,.20)),0 1px 2px 0 var(--Black-5,rgba(0,0,0,.05)),0 1px 1px 0 rgba(0,0,0,.01);
1262
+ }
928
1263
  .rp-pane{flex:1;overflow-y:auto;overflow-x:hidden;min-width:0;display:none}
929
1264
  .rp-pane.active{display:block}
930
1265
 
@@ -933,13 +1268,13 @@ select.pr-inp{cursor:pointer;background:#fff}
933
1268
  .states-empty i{font-size:30px;display:block;margin-bottom:10px;opacity:.3}
934
1269
  .state-group{border-bottom:1px solid var(--border-sub)}
935
1270
  .state-group-sel{
936
- padding:8px 12px 3px;font-size:10px;font-family:monospace;
1271
+ padding:8px 12px 3px;font-size:10px;font-family:var(--font-mono);
937
1272
  color:var(--text-3);font-weight:700;word-break:break-all;letter-spacing:-.01em
938
1273
  }
939
1274
  .state-item{display:flex;align-items:center;padding:4px 10px 4px 18px;gap:6px}
940
1275
  .state-item-label{flex:1;font-size:11px;color:var(--text-2)}
941
1276
  .state-item-val{
942
- font-size:10px;color:var(--accent-txt);font-family:monospace;
1277
+ font-size:10px;color:var(--accent-txt);font-family:var(--font-mono);
943
1278
  max-width:68px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
944
1279
  background:var(--accent-bg);padding:1px 5px;border-radius:3px
945
1280
  }
@@ -1067,7 +1402,17 @@ select.pr-inp{cursor:pointer;background:#fff}
1067
1402
  <div id="dev-more-menu" class="tb-dev-menu" aria-hidden="true">
1068
1403
  <div class="row row-zoom">
1069
1404
  <label for="dev-zoom-level">Zoom</label>
1070
- <input id="dev-zoom-level" type="number" min="25" max="200" step="5" value="100" />
1405
+ <div class="zoom-controls">
1406
+ <button type="button" id="dev-zoom-fit-btn" class="dev-zoom-fit-btn" title="Reset zoom to fit window" aria-label="Reset zoom to fit window">
1407
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
1408
+ <path d="M2.66699 5.33333V2.66667C2.66699 2.48986 2.73723 2.32029 2.86225 2.19526C2.98727 2.07024 3.15684 2 3.33366 2H6.00033" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
1409
+ <path d="M10 2H12.6667C12.8435 2 13.013 2.07024 13.1381 2.19526C13.2631 2.32029 13.3333 2.48986 13.3333 2.66667V5.33333" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
1410
+ <path d="M13.333 10.6667V13.3333C13.333 13.5101 13.2628 13.6797 13.1377 13.8047C13.0127 13.9298 12.8432 14 12.6663 14H10" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
1411
+ <path d="M6 14H3.33333C3.15652 14 2.98695 13.9298 2.86193 13.8047C2.73691 13.6797 2.66667 13.5101 2.66667 13.3333V10.6667" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
1412
+ </svg>
1413
+ </button>
1414
+ <input id="dev-zoom-level" type="number" min="25" max="200" step="5" value="100" />
1415
+ </div>
1071
1416
  </div>
1072
1417
  <div class="row row-split row-width height-width-row">
1073
1418
  <div class="row" style="margin:0">
@@ -1092,9 +1437,6 @@ select.pr-inp{cursor:pointer;background:#fff}
1092
1437
  <button type="button" class="vp-preset-btn" data-preset="iphone-7">iPhone 7</button>
1093
1438
  <button type="button" class="vp-preset-btn" data-preset="responsive">Responsive</button>
1094
1439
  </div>
1095
- <div class="ft">
1096
- <button type="button" class="apply-btn" id="dev-apply-btn">Apply</button>
1097
- </div>
1098
1440
  </div>
1099
1441
  </div>
1100
1442
  <button class="tb-dk-btn" id="btn-undo" title="Undo (\u2318Z)"><i class="bi bi-arrow-counterclockwise"></i></button>
@@ -1122,15 +1464,16 @@ select.pr-inp{cursor:pointer;background:#fff}
1122
1464
  <span class="tb-save-txt"></span>
1123
1465
  <span id="tb-save-time" class="tb-save-time"></span>
1124
1466
  </div>
1467
+ <button class="tb-fin-btn" id="btn-save">Save Changes</button>
1125
1468
  <div class="tb-dev-3btns">
1126
1469
  <button class="tb-dk-btn active" id="btn-mode-editor" onclick="setMode('editor')" title="Edit mode \u2014 click elements to edit"><i class="bi bi-pencil"></i></button>
1127
- <button class="tb-dk-btn" id="btn-mode-nav" onclick="setMode('navigate')" title="Navigate mode \u2014 interact with page normally"><i class="bi bi-magic"></i></button>
1470
+ <button class="tb-dk-btn" id="btn-mode-nav" onclick="setMode('navigate')" title="Navigate mode \u2014 interact with page normally"><i class="bi bi-cursor"></i></button>
1128
1471
  <button class="tb-dk-btn" title="Comments"><i class="bi bi-chat-dots"></i></button>
1129
1472
  </div>
1130
- <button class="tb-sim-btn" id="btn-simulate" onclick="simulateExperiment()"><i class="bi bi-lightning-charge-fill"></i> Simulate</button>
1131
- <button class="tb-fin-btn" id="btn-save">Save Changes</button>
1473
+ <button class="tb-sim-btn" id="btn-simulate" onclick="simulateExperiment()">See Preview</button>
1474
+
1132
1475
  <!-- btn-close: kept for JS event listener -->
1133
- <button class="tb-fin-btn" id="btn-close">Close</button>
1476
+ <button class="tb-fin-btn" id="btn-close">Exit Editor</button>
1134
1477
  </div>
1135
1478
 
1136
1479
  <!-- url-bar: hidden, kept for JS compatibility -->
@@ -1168,22 +1511,28 @@ select.pr-inp{cursor:pointer;background:#fff}
1168
1511
 
1169
1512
  <!-- Elements -->
1170
1513
  <div class="lp-sec lp-sec-no-border">
1171
- <div class="lp-sec-hd">
1172
- <span class="lp-sec-hd-left">Elements <i style="display:none" class="bi bi-info-circle lp-info-icon" title="Page elements"></i></span>
1173
- <button class="lp-add-btn" id="btn-add-element" title="Add element">+ Add</button>
1174
- </div>
1175
-
1176
1514
  <!-- Search (hidden, kept for JS) -->
1177
- <div>
1178
- <input type="search" id="comp-search" placeholder="Search layers\u2026" autocomplete="off">
1515
+ <div class="lp-search-container">
1516
+ <div class="lp-search-field">
1517
+ <span class="lp-search-icon" aria-hidden="true">
1518
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
1519
+ <path d="M11.333 11.3333L13.9997 13.9999" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
1520
+ <path d="M12.6667 7.33333C12.6667 4.38781 10.2789 2 7.33333 2C4.38781 2 2 4.38781 2 7.33333C2 10.2789 4.38781 12.6667 7.33333 12.6667C10.2789 12.6667 12.6667 10.2789 12.6667 7.33333Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
1521
+ </svg>
1522
+ </span>
1523
+ <input type="search" id="comp-search" placeholder="Search" autocomplete="off">
1524
+ </div>
1179
1525
  </div>
1180
1526
 
1181
1527
 
1182
1528
  <!-- Tabs (hidden, kept for JS) -->
1183
- <div class="lp-tabs" >
1184
- <div class="lp-tab active" onclick="switchLeftTab('elements')">Elements</div>
1529
+ <div class="lp-tabs-container">
1530
+ <div class="lp-tabs">
1531
+ <div class="lp-tab active" onclick="switchLeftTab('elements')">Elements</div>
1185
1532
  <div class="lp-tab" onclick="switchLeftTab('dom-tree')">DOM Tree</div>
1186
1533
  </div>
1534
+ <button class="lp-add-btn" id="btn-add-element" title="Add element">+ Add</button>
1535
+ </div>
1187
1536
 
1188
1537
  </div>
1189
1538
 
@@ -1212,11 +1561,11 @@ select.pr-inp{cursor:pointer;background:#fff}
1212
1561
 
1213
1562
  <!-- Floating toolbar for selected element (positioned over iframe) -->
1214
1563
  <div id="selection-floater" aria-label="Selection actions">
1564
+ <button type="button" class="sf-btn" id="sf-add" title="Add element"><i class="bi bi-plus-lg"></i></button>
1565
+ <span class="sf-sep"></span>
1215
1566
  <button type="button" class="sf-btn" id="sf-move-up" title="Move up"><i class="bi bi-arrow-up"></i></button>
1216
1567
  <button type="button" class="sf-btn" id="sf-move-down" title="Move down"><i class="bi bi-arrow-down"></i></button>
1217
1568
  <span class="sf-sep"></span>
1218
- <button type="button" class="sf-btn" id="sf-resize" disabled title="Resize (coming soon)"><i class="bi bi-arrows-angle-expand"></i></button>
1219
- <button type="button" class="sf-btn" id="sf-rotate" disabled title="Rotate (coming soon)"><i class="bi bi-arrow-repeat"></i></button>
1220
1569
  <button type="button" class="sf-btn" id="sf-dup" title="Duplicate"><i class="bi bi-files"></i></button>
1221
1570
  <button type="button" class="sf-btn" id="sf-hide" title="Hide"><i class="bi bi-eye-slash"></i></button>
1222
1571
  <button type="button" class="sf-btn" id="sf-del" title="Delete"><i class="bi bi-trash"></i></button>
@@ -1240,14 +1589,16 @@ select.pr-inp{cursor:pointer;background:#fff}
1240
1589
 
1241
1590
  <!-- Right panel -->
1242
1591
  <div id="right-panel">
1243
- <div id="section-components-panel" style="display:none">
1592
+ <div id="section-components-panel">
1244
1593
  <!-- Left-tab controls moved here -->
1245
- <div class="section-components-tabs">
1246
- <div class="lp-tab" onclick="switchSectionComponentsTab('components')">Components</div>
1247
- <div class="lp-tab" onclick="switchSectionComponentsTab('sections')">Sections</div>
1248
- <div class="lp-tab close-section-components-panel" onclick="toggleSectionComponentsPanel()"> Close</div>
1594
+ <div class="lp-tabs-container section-components-tabs-container">
1595
+ <div class="lp-tabs">
1596
+ <div class="lp-tab" onclick="switchSectionComponentsTab('components')">Components</div>
1597
+ <div class="lp-tab" onclick="switchSectionComponentsTab('sections')">Sections</div>
1598
+ </div>
1599
+ <div class="lp-tab close-section-components-panel collapsed" onclick="toggleSectionComponentsBody()" title="Toggle components list"><i class="bi bi-chevron-down"></i></div>
1249
1600
  </div>
1250
- <div class="lp-body">
1601
+ <div class="lp-body collapsed">
1251
1602
  <div id="tab-components" class="tab-pane"></div>
1252
1603
  <div id="tab-sections" class="tab-pane"></div>
1253
1604
  </div>
@@ -1260,7 +1611,7 @@ select.pr-inp{cursor:pointer;background:#fff}
1260
1611
  <!-- \u2500\u2500 3 main tabs \u2500\u2500 -->
1261
1612
  <div id="main-tabs">
1262
1613
  <button class="main-tab active" onclick="switchMainTab('design')">Design</button>
1263
- <button class="main-tab" onclick="switchMainTab('states')">States</button>
1614
+ <button class="main-tab" style="display:none" onclick="switchMainTab('states')">States</button>
1264
1615
  <button class="main-tab" onclick="switchMainTab('history')">History</button>
1265
1616
  </div>
1266
1617
 
@@ -1450,26 +1801,35 @@ if (window.Vvveb && window.Vvveb.Components) {
1450
1801
 
1451
1802
  // \u2500\u2500 CRO definitions + Base components \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
1452
1803
  var CRO_SECTIONS = ${sectionsJson};
1804
+ var CRO_SECTION_IMAGE_ROUTE = ${JSON.stringify(CRO_SECTION_IMAGE_ROUTE)};
1805
+
1806
+ function renderCroSectionThumb(sec) {
1807
+ if (sec && sec.image) {
1808
+ return '<img src="' + CRO_SECTION_IMAGE_ROUTE + sec.image + '" alt="' + esc(sec.name || '') + '" loading="lazy">';
1809
+ }
1810
+ return sec && sec.icon ? sec.icon : '\u{1F4E6}';
1811
+ }
1453
1812
 
1454
1813
  var BASE_COMPONENTS = [
1455
- { name:'Heading', icon:'bi-type-h1', html:'<h2>Your Heading</h2>' },
1456
- { name:'Paragraph', icon:'bi-paragraph', html:'<p>Your text here. Click to edit.</p>' },
1457
- { name:'Image', icon:'bi-image', html:'<img src="https://placehold.co/600x300/1a1a2e/a78bfa?text=Image" alt="Image" style="max-width:100%">' },
1458
- { name:'Button', icon:'bi-hand-index', html:'<button class="btn btn-primary">Click me</button>' },
1459
- { name:'Link', icon:'bi-link-45deg', html:'<a href="#">Link text</a>' },
1460
- { name:'Divider', icon:'bi-dash-lg', html:'<hr>' },
1461
- { name:'List', icon:'bi-list-ul', html:'<ul><li>Item 1</li><li>Item 2</li><li>Item 3</li></ul>' },
1462
- { name:'Video', icon:'bi-play-circle', html:'<video controls style="max-width:100%"><source src="" type="video/mp4"></video>' },
1463
- { name:'Form', icon:'bi-ui-radios', html:'<form class="p-3"><div class="mb-3"><label class="form-label">Name</label><input type="text" class="form-control"></div><div class="mb-3"><label class="form-label">Email</label><input type="email" class="form-control"></div><button type="submit" class="btn btn-primary">Submit</button></form>' },
1464
- { name:'Input', icon:'bi-input-cursor-text', html:'<input type="text" class="form-control" placeholder="Enter text">' },
1465
- { name:'Textarea', icon:'bi-textarea-resize', html:'<textarea class="form-control" rows="3" placeholder="Enter text"></textarea>' },
1466
- { name:'Select', icon:'bi-menu-button-wide', html:'<select class="form-select"><option>Option 1</option><option>Option 2</option></select>' },
1467
- { name:'Card', icon:'bi-card-text', html:'<div class="card"><div class="card-body"><h5 class="card-title">Card Title</h5><p class="card-text">Card content here.</p><a href="#" class="btn btn-primary">Action</a></div></div>' },
1468
- { name:'Alert', icon:'bi-exclamation-triangle', html:'<div class="alert alert-info" role="alert">This is an alert message.</div>' },
1469
- { name:'Badge', icon:'bi-tag', html:'<span class="badge bg-primary">New</span>' },
1470
- { name:'Container', icon:'bi-layout-three-columns', html:'<div class="container py-4"><div class="row g-3"><div class="col"><p>Column 1</p></div><div class="col"><p>Column 2</p></div><div class="col"><p>Column 3</p></div></div></div>' },
1471
- { name:'2 Cols', icon:'bi-layout-split', html:'<div class="row g-3"><div class="col-md-6"><p>Left column</p></div><div class="col-md-6"><p>Right column</p></div></div>' },
1472
- { name:'Navbar', icon:'bi-layout-navbar', html:'<nav class="navbar navbar-expand-lg navbar-dark bg-dark"><div class="container-fluid"><a class="navbar-brand" href="#">Brand</a></div></nav>' },
1814
+ { name:'Heading', icon:'bi-type-h1', defaultAccSection:'typography', html:'<h2 style="margin:0 0 12px;font-size:28px;font-weight:700;line-height:1.2;color:#0f172a">Your Heading</h2>' },
1815
+ { name:'Paragraph', icon:'bi-paragraph', defaultAccSection:'typography', html:'<p style="margin:0 0 12px;font-size:16px;line-height:1.6;color:#334155">Your text here. Click to edit.</p>' },
1816
+ { name:'Image', icon:'bi-image', defaultAccSection:'image', html:'<img src="https://placehold.co/600x300/1a1a2e/a78bfa?text=Image" alt="Image" style="display:block;max-width:100%;height:auto;border-radius:8px">' },
1817
+ { name:'Button', icon:'bi-hand-index', defaultAccSection:'background', html:'<button type="button" style="display:inline-block;padding:10px 20px;font-size:14px;font-weight:600;line-height:1.2;color:#fff;background:#2563eb;border:none;border-radius:6px;cursor:pointer">Click me</button>' },
1818
+ { name:'Link', icon:'bi-link-45deg', defaultAccSection:'typography', html:'<a href="#" style="font-size:16px;font-weight:500;color:#2563eb;text-decoration:underline">Link text</a>' },
1819
+ { name:'Divider', icon:'bi-dash-lg', defaultAccSection:'background', html:'<hr style="margin:16px 0;border:none;border-top:1px solid #e2e8f0">' },
1820
+ { name:'List', icon:'bi-list-ul', defaultAccSection:'typography', html:'<ul style="margin:0 0 12px;padding-left:20px;font-size:16px;line-height:1.6;color:#334155"><li>Item 1</li><li>Item 2</li><li>Item 3</li></ul>' },
1821
+ { name:'Video', icon:'bi-play-circle', defaultAccSection:'content', html:'<video controls style="display:block;width:100%;max-width:100%;border-radius:8px;background:#000"><source src="" type="video/mp4"></video>' },
1822
+ { name:'Youtube Video', icon:'bi-youtube', defaultAccSection:'content', html:'<iframe data-youtube-id="" src="about:blank" title="YouTube video" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen style="display:block;width:100%;max-width:100%;aspect-ratio:16/9;border:none;border-radius:8px;background:#000"></iframe>' },
1823
+ { name:'Vimeo Video', icon:'bi-vimeo', defaultAccSection:'content', html:'<iframe data-vimeo-id="" src="about:blank" title="Vimeo video" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen style="display:block;width:100%;max-width:100%;aspect-ratio:16/9;border:none;border-radius:8px;background:#000"></iframe>' },
1824
+ { name:'Form', icon:'bi-ui-radios', defaultAccSection:'content', html:'<form style="padding:16px;border:1px solid #e2e8f0;border-radius:8px;background:#fff;max-width:400px;box-sizing:border-box"><div style="margin-bottom:12px"><label style="display:block;margin-bottom:4px;font-size:14px;font-weight:500;color:#334155">Name</label><input type="text" style="width:100%;padding:8px 12px;font-size:14px;border:1px solid #cbd5e1;border-radius:6px;box-sizing:border-box"></div><div style="margin-bottom:12px"><label style="display:block;margin-bottom:4px;font-size:14px;font-weight:500;color:#334155">Email</label><input type="email" style="width:100%;padding:8px 12px;font-size:14px;border:1px solid #cbd5e1;border-radius:6px;box-sizing:border-box"></div><button type="submit" style="display:inline-block;padding:10px 20px;font-size:14px;font-weight:600;color:#fff;background:#2563eb;border:none;border-radius:6px;cursor:pointer">Submit</button></form>' },
1825
+ { name:'Input', icon:'bi-input-cursor-text', defaultAccSection:'size', html:'<input type="text" placeholder="Enter text" style="width:100%;max-width:320px;padding:8px 12px;font-size:14px;border:1px solid #cbd5e1;border-radius:6px;box-sizing:border-box">' },
1826
+ { name:'Textarea', icon:'bi-textarea-resize', defaultAccSection:'size', html:'<textarea rows="3" placeholder="Enter text" style="width:100%;max-width:320px;padding:8px 12px;font-size:14px;border:1px solid #cbd5e1;border-radius:6px;box-sizing:border-box;resize:vertical"></textarea>' },
1827
+ { name:'Select', icon:'bi-menu-button-wide', defaultAccSection:'size', html:'<select style="width:100%;max-width:320px;padding:8px 12px;font-size:14px;border:1px solid #cbd5e1;border-radius:6px;background:#fff;box-sizing:border-box"><option>Option 1</option><option>Option 2</option></select>' },
1828
+ { name:'Card', icon:'bi-card-text', defaultAccSection:'shadow', html:'<div style="border:1px solid #e2e8f0;border-radius:8px;background:#fff;overflow:hidden;max-width:360px;box-shadow:0 1px 3px rgba(0,0,0,.08)"><div style="padding:20px"><h5 style="margin:0 0 8px;font-size:18px;font-weight:700;color:#0f172a">Card Title</h5><p style="margin:0 0 16px;font-size:14px;line-height:1.5;color:#475569">Card content here.</p><a href="#" style="display:inline-block;padding:8px 16px;font-size:14px;font-weight:600;color:#fff;background:#2563eb;border-radius:6px;text-decoration:none">Action</a></div></div>' },
1829
+ { name:'Alert', icon:'bi-exclamation-triangle', defaultAccSection:'background', html:'<div role="alert" style="padding:12px 16px;font-size:14px;line-height:1.5;color:#0c4a6e;background:#e0f2fe;border:1px solid #7dd3fc;border-radius:6px">This is an alert message.</div>' },
1830
+ { name:'Badge', icon:'bi-tag', defaultAccSection:'typography', html:'<span style="display:inline-flex;align-items:center;padding:4px 10px;font-size:12px;font-weight:600;line-height:1;letter-spacing:.02em;color:#fff;background:#2563eb;border-radius:999px">New</span>' },
1831
+ { name:'Container', icon:'bi-layout-three-columns', defaultAccSection:'spacing', html:'<div style="width:100%;max-width:960px;margin:0 auto;padding:16px;box-sizing:border-box"><div style="display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px"><div style="padding:16px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px"><p style="margin:0">Column 1</p></div><div style="padding:16px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px"><p style="margin:0">Column 2</p></div><div style="padding:16px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px"><p style="margin:0">Column 3</p></div></div></div>' },
1832
+ { name:'2 Cols', icon:'bi-layout-split', defaultAccSection:'spacing', html:'<div style="display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px;width:100%;box-sizing:border-box"><div style="padding:16px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;min-height:80px;box-sizing:border-box"><p style="margin:0">Left column</p></div><div style="padding:16px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;min-height:80px;box-sizing:border-box"><p style="margin:0">Right column</p></div></div>' },
1473
1833
  ];
1474
1834
 
1475
1835
  // \u2500\u2500 PostMessage bridge \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
@@ -1623,7 +1983,7 @@ var leftPanelCollapsed = false;
1623
1983
  var viewportPreset = 'desktop';
1624
1984
  var viewportWidth = 1440;
1625
1985
  var viewportHeight = 1024;
1626
- var viewportZoom = 1;
1986
+ var viewportZoom = null;
1627
1987
  var selectedEl = null;
1628
1988
  /** Stable selector fingerprint for resilient selection recovery after DOM churn. */
1629
1989
  var selectedElFingerprint = '';
@@ -1643,6 +2003,7 @@ var iframeSyncRetryTimer = null;
1643
2003
  var iframeSyncAttempts = 0;
1644
2004
  var selectionScrollWin = null;
1645
2005
  var selectionResizeBound = false;
2006
+ var selectionPanelScrollBound = false;
1646
2007
  var clickAttachDoc = null;
1647
2008
  var hoverAttachDoc = null;
1648
2009
  var changeObserver = null;
@@ -1894,16 +2255,22 @@ function clampViewportNumber(v, fallback, min, max) {
1894
2255
 
1895
2256
  function getViewportFitZoom() {
1896
2257
  var panel = document.getElementById('iframe-panel');
1897
- var available = panel ? Math.max(260, panel.clientWidth - 24) : viewportWidth;
1898
- if (!viewportWidth || viewportWidth <= 0) return 1;
1899
- return Math.min(1, available / viewportWidth);
2258
+ if (!panel || !viewportWidth || viewportWidth <= 0 || !viewportHeight || viewportHeight <= 0) return 1;
2259
+ var availW = Math.max(260, panel.clientWidth);
2260
+ var availH = Math.max(200, panel.clientHeight);
2261
+ return Math.min(1, availW / viewportWidth, availH / viewportHeight);
1900
2262
  }
1901
2263
 
1902
2264
  function getAppliedViewportZoom() {
1903
- var z = Number(viewportZoom);
1904
- if (!Number.isFinite(z) || z <= 0) z = 1;
1905
- z = Math.max(0.25, Math.min(2, z));
1906
- return Math.min(z, getViewportFitZoom());
2265
+ var fit = getViewportFitZoom();
2266
+ var z = viewportZoom != null ? Number(viewportZoom) : fit;
2267
+ if (!Number.isFinite(z) || z <= 0) z = fit;
2268
+ return Math.max(0.25, Math.min(2, z));
2269
+ }
2270
+
2271
+ function resetViewportZoomToFit() {
2272
+ viewportZoom = null;
2273
+ applyViewportFrame();
1907
2274
  }
1908
2275
 
1909
2276
  function updateViewportLabel() {
@@ -1924,20 +2291,28 @@ function syncViewportMenuControls() {
1924
2291
  presetButtons[i].classList.toggle('active', key === viewportPreset);
1925
2292
  }
1926
2293
  }
1927
- if (widthInp) widthInp.value = String(viewportWidth);
1928
- if (heightInp) heightInp.value = String(viewportHeight);
2294
+ if (widthInp && document.activeElement !== widthInp) widthInp.value = String(viewportWidth);
2295
+ if (heightInp && document.activeElement !== heightInp) heightInp.value = String(viewportHeight);
1929
2296
  if (zoomInp) zoomInp.value = String(Math.round(getAppliedViewportZoom() * 100));
1930
2297
  }
1931
2298
 
1932
2299
  function applyViewportFrame() {
1933
2300
  var frame = document.getElementById('device-frame');
1934
2301
  var iframe = document.getElementById('iframeId');
2302
+ var panel = document.getElementById('iframe-panel');
1935
2303
  if (!frame) return;
1936
2304
  frame.className = currentDevice;
1937
2305
  frame.style.width = viewportWidth + 'px';
1938
2306
  frame.style.height = viewportHeight + 'px';
1939
2307
  frame.style.maxWidth = 'none';
1940
- frame.style.zoom = String(getAppliedViewportZoom());
2308
+ var zoom = getAppliedViewportZoom();
2309
+ var fit = getViewportFitZoom();
2310
+ frame.style.zoom = String(zoom);
2311
+ if (panel) {
2312
+ var zoomedIn = zoom > fit + 0.001;
2313
+ panel.style.overflow = zoomedIn ? 'auto' : 'hidden';
2314
+ panel.style.alignItems = zoomedIn ? 'flex-start' : 'center';
2315
+ }
1941
2316
  if (iframe) {
1942
2317
  iframe.style.height = viewportHeight + 'px';
1943
2318
  iframe.style.minHeight = viewportHeight + 'px';
@@ -1954,6 +2329,7 @@ function setViewportPreset(presetKey) {
1954
2329
  viewportWidth = preset.width;
1955
2330
  viewportHeight = preset.height;
1956
2331
  currentDevice = preset.device || 'desktop';
2332
+ viewportZoom = null;
1957
2333
  ['desktop','tablet','mobile'].forEach(function(d) {
1958
2334
  document.getElementById('dev-' + d).classList.toggle('active', d === currentDevice);
1959
2335
  });
@@ -1966,20 +2342,26 @@ function setDevice(device) {
1966
2342
  var preset = DEVICE_PRESETS[device] || DEVICE_PRESETS.desktop;
1967
2343
  viewportWidth = preset.width;
1968
2344
  viewportHeight = preset.height;
2345
+ viewportZoom = null;
1969
2346
  ['desktop','tablet','mobile'].forEach(function(d) {
1970
2347
  document.getElementById('dev-' + d).classList.toggle('active', d === device);
1971
2348
  });
1972
2349
  applyViewportFrame();
1973
2350
  }
1974
2351
 
1975
- function setViewportCustomFromInputs() {
2352
+ function setViewportCustomFromInputs(opts) {
2353
+ opts = opts || {};
1976
2354
  var widthInp = document.getElementById('dev-custom-width');
1977
2355
  var heightInp = document.getElementById('dev-custom-height');
1978
- var zoomInp = document.getElementById('dev-zoom-level');
1979
- viewportWidth = clampViewportNumber(widthInp ? widthInp.value : '', viewportWidth, 240, 3840);
1980
- viewportHeight = clampViewportNumber(heightInp ? heightInp.value : '', viewportHeight, 320, 3840);
1981
- var z = clampViewportNumber(zoomInp ? zoomInp.value : '', Math.round(getAppliedViewportZoom() * 100), 25, 200);
1982
- viewportZoom = z / 100;
2356
+ var rawW = widthInp ? widthInp.value : '';
2357
+ var rawH = heightInp ? heightInp.value : '';
2358
+ if (!opts.force) {
2359
+ if (widthInp && document.activeElement === widthInp && rawW === '') return;
2360
+ if (heightInp && document.activeElement === heightInp && rawH === '') return;
2361
+ }
2362
+ viewportWidth = clampViewportNumber(rawW, viewportWidth, 240, 3840);
2363
+ viewportHeight = clampViewportNumber(rawH, viewportHeight, 320, 3840);
2364
+ viewportZoom = null;
1983
2365
  viewportPreset = 'custom';
1984
2366
  currentDevice = viewportWidth <= 480 ? 'mobile' : (viewportWidth <= 1024 ? 'tablet' : 'desktop');
1985
2367
  ['desktop','tablet','mobile'].forEach(function(d) {
@@ -1988,6 +2370,22 @@ function setViewportCustomFromInputs() {
1988
2370
  applyViewportFrame();
1989
2371
  }
1990
2372
 
2373
+ var customViewportApplyTimer = null;
2374
+ function scheduleCustomViewportApply() {
2375
+ if (customViewportApplyTimer) clearTimeout(customViewportApplyTimer);
2376
+ customViewportApplyTimer = setTimeout(function() {
2377
+ customViewportApplyTimer = null;
2378
+ setViewportCustomFromInputs();
2379
+ }, 300);
2380
+ }
2381
+ function applyCustomViewportNow() {
2382
+ if (customViewportApplyTimer) {
2383
+ clearTimeout(customViewportApplyTimer);
2384
+ customViewportApplyTimer = null;
2385
+ }
2386
+ setViewportCustomFromInputs({ force: true });
2387
+ }
2388
+
1991
2389
  function closeViewportMenu() {
1992
2390
  var menu = document.getElementById('dev-more-menu');
1993
2391
  if (!menu) return;
@@ -2008,8 +2406,10 @@ function bindViewportControls() {
2008
2406
  var menu = document.getElementById('dev-more-menu');
2009
2407
  var leftPanelToggleLabel = document.getElementById('left-panel-toggle-label');
2010
2408
  var presetButtons = document.querySelectorAll('#dev-preset-list .vp-preset-btn');
2011
- var applyBtn = document.getElementById('dev-apply-btn');
2409
+ var widthInp = document.getElementById('dev-custom-width');
2410
+ var heightInp = document.getElementById('dev-custom-height');
2012
2411
  var zoomInp = document.getElementById('dev-zoom-level');
2412
+ var zoomFitBtn = document.getElementById('dev-zoom-fit-btn');
2013
2413
  var viewportBtn = document.querySelector('.tb-viewport');
2014
2414
  function updateLeftPanelToggleLabel() {
2015
2415
  if (!leftPanelToggleLabel) return;
@@ -2039,11 +2439,21 @@ function bindViewportControls() {
2039
2439
  e.stopPropagation();
2040
2440
  toggleViewportMenu();
2041
2441
  });
2042
- if (applyBtn) applyBtn.addEventListener('click', function(e) {
2043
- e.preventDefault();
2044
- setViewportCustomFromInputs();
2045
- closeViewportMenu();
2046
- });
2442
+ if (widthInp) {
2443
+ widthInp.addEventListener('input', scheduleCustomViewportApply);
2444
+ widthInp.addEventListener('change', applyCustomViewportNow);
2445
+ }
2446
+ if (heightInp) {
2447
+ heightInp.addEventListener('input', scheduleCustomViewportApply);
2448
+ heightInp.addEventListener('change', applyCustomViewportNow);
2449
+ }
2450
+ if (zoomFitBtn) {
2451
+ zoomFitBtn.addEventListener('click', function(e) {
2452
+ e.preventDefault();
2453
+ e.stopPropagation();
2454
+ resetViewportZoomToFit();
2455
+ });
2456
+ }
2047
2457
  if (zoomInp) {
2048
2458
  zoomInp.addEventListener('change', function() {
2049
2459
  var z = clampViewportNumber(zoomInp.value, Math.round(getAppliedViewportZoom() * 100), 25, 200);
@@ -2078,7 +2488,7 @@ function bindViewportControls() {
2078
2488
  function switchLeftTab(tab) {
2079
2489
  if (tab !== 'elements' && tab !== 'dom-tree') return;
2080
2490
  currentLeftTab = tab;
2081
- var tabs = document.querySelectorAll('.lp-tabs .lp-tab');
2491
+ var tabs = document.querySelectorAll('#left-panel .lp-tabs .lp-tab');
2082
2492
  for (var i = 0; i < tabs.length; i++) {
2083
2493
  var oc = tabs[i].getAttribute('onclick') || '';
2084
2494
  tabs[i].classList.toggle('active', oc.indexOf("switchLeftTab('" + tab + "')") >= 0);
@@ -2090,18 +2500,26 @@ function switchLeftTab(tab) {
2090
2500
  }
2091
2501
  var inp = document.getElementById('comp-search');
2092
2502
  if (tab === 'elements') {
2093
- inp.placeholder = 'Search elements\u2026';
2094
2503
  renderElementsTree(inp.value);
2095
2504
  } else if (tab === 'dom-tree') {
2096
- inp.placeholder = 'Search layers\u2026';
2097
2505
  renderDomTree(inp.value);
2098
2506
  }
2099
2507
  }
2100
2508
 
2509
+ function expandSectionComponentsBody() {
2510
+ var panel = document.getElementById('section-components-panel');
2511
+ if (!panel) return;
2512
+ var body = panel.querySelector('.lp-body');
2513
+ var toggle = panel.querySelector('.close-section-components-panel');
2514
+ if (body) body.classList.remove('collapsed');
2515
+ if (toggle) toggle.classList.remove('collapsed');
2516
+ }
2517
+
2101
2518
  function switchSectionComponentsTab(tab) {
2102
2519
  if (tab !== 'components' && tab !== 'sections') return;
2520
+ expandSectionComponentsBody();
2103
2521
  currentSectionComponentsTab = tab;
2104
- var tabs = document.querySelectorAll('.section-components-tabs .lp-tab');
2522
+ var tabs = document.querySelectorAll('#section-components-panel .lp-tabs .lp-tab');
2105
2523
  for (var i = 0; i < tabs.length; i++) {
2106
2524
  var oc = tabs[i].getAttribute('onclick') || '';
2107
2525
  tabs[i].classList.toggle('active', oc.indexOf("switchSectionComponentsTab('" + tab + "')") >= 0);
@@ -2111,29 +2529,67 @@ function switchSectionComponentsTab(tab) {
2111
2529
  if (compPane) compPane.classList.toggle('active', tab === 'components');
2112
2530
  if (secPane) secPane.classList.toggle('active', tab === 'sections');
2113
2531
  var inp = document.getElementById('comp-search');
2114
- if (inp) inp.placeholder = tab === 'sections' ? 'Search sections\u2026' : 'Search components\u2026';
2532
+ if (inp) inp.placeholder = tab === 'sections' ? 'Search' : 'Search';
2115
2533
  renderSidebar(inp ? inp.value : '');
2116
2534
  }
2117
2535
 
2536
+ function toggleSectionComponentsBody() {
2537
+ var panel = document.getElementById('section-components-panel');
2538
+ if (!panel) return;
2539
+ var body = panel.querySelector('.lp-body');
2540
+ var toggle = panel.querySelector('.close-section-components-panel');
2541
+ if (!body) return;
2542
+ var collapsed = body.classList.toggle('collapsed');
2543
+ if (toggle) toggle.classList.toggle('collapsed', collapsed);
2544
+ }
2545
+
2118
2546
  function toggleSectionComponentsPanel(forceVisible) {
2119
2547
  var panel = document.getElementById('section-components-panel');
2120
2548
  if (!panel) return;
2121
- var shouldShow =
2549
+ var body = panel.querySelector('.lp-body');
2550
+ var toggle = panel.querySelector('.close-section-components-panel');
2551
+ if (!body) return;
2552
+ var isCollapsed = body.classList.contains('collapsed');
2553
+ var shouldExpand =
2122
2554
  typeof forceVisible === 'boolean'
2123
2555
  ? forceVisible
2124
- : panel.style.display === 'none';
2125
- panel.style.display = shouldShow ? '' : 'none';
2126
- if (shouldShow) {
2556
+ : isCollapsed;
2557
+ body.classList.toggle('collapsed', !shouldExpand);
2558
+ if (toggle) toggle.classList.toggle('collapsed', !shouldExpand);
2559
+ if (shouldExpand) {
2127
2560
  switchSectionComponentsTab(currentSectionComponentsTab || 'components');
2128
2561
  }
2129
2562
  }
2130
2563
 
2564
+ function handleAddElementClick(e) {
2565
+ if (e) {
2566
+ e.preventDefault();
2567
+ e.stopPropagation();
2568
+ }
2569
+ toggleSectionComponentsPanel();
2570
+ }
2571
+
2131
2572
  // \u2500\u2500 Accordion toggle \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
2132
2573
  function toggleAcc(name) {
2133
2574
  var sec = document.getElementById('acc-' + name);
2134
2575
  if (sec) sec.classList.toggle('open');
2135
2576
  }
2136
2577
 
2578
+ function focusDesignAccordionSection(name) {
2579
+ if (!name) return;
2580
+ if (currentMainTab !== 'design') switchMainTab('design');
2581
+ var sec = document.getElementById('acc-' + name);
2582
+ if (!sec || sec.style.display === 'none') return;
2583
+ sec.classList.add('open');
2584
+ requestAnimationFrame(function() {
2585
+ try {
2586
+ sec.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
2587
+ } catch(_) {
2588
+ sec.scrollIntoView(true);
2589
+ }
2590
+ });
2591
+ }
2592
+
2137
2593
  // \u2500\u2500 Main tab switching (Design / States / History) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
2138
2594
  function switchMainTab(tab) {
2139
2595
  currentMainTab = tab;
@@ -2198,9 +2654,19 @@ var PROP_META = {
2198
2654
  'pp-id': {label:'ID', cssProp:null},
2199
2655
  'pp-href': {label:'Href', cssProp:null},
2200
2656
  'pp-target': {label:'Target', cssProp:null},
2657
+ 'pp-rel': {label:'Rel', cssProp:null},
2658
+ 'pp-download':{label:'Download', cssProp:null},
2659
+ 'pp-link-title': {label:'Title', cssProp:null},
2660
+ 'pp-hreflang':{label:'Hreflang', cssProp:null},
2661
+ 'pp-link-type': {label:'Link type', cssProp:null},
2662
+ 'pp-referrerpolicy': {label:'Referrer policy', cssProp:null},
2201
2663
  'pp-src': {label:'Src', cssProp:null},
2664
+ 'pp-video-src': {label:'Src', cssProp:null},
2665
+ 'pp-youtube-id': {label:'YouTube video ID', cssProp:null},
2666
+ 'pp-vimeo-id': {label:'Vimeo video ID', cssProp:null},
2202
2667
  'pp-alt': {label:'Alt', cssProp:null},
2203
2668
  'pp-ph': {label:'Placeholder', cssProp:null},
2669
+ 'pp-value': {label:'Value', cssProp:null},
2204
2670
  'pp-text': {label:'Inner text', cssProp:null},
2205
2671
  'pp-html': {label:'Inner HTML', cssProp:null},
2206
2672
  'pp-mob-css': {label:'Mobile CSS', cssProp:null},
@@ -2223,9 +2689,19 @@ function getOriginalValue(inputId, el) {
2223
2689
  case 'pp-id': return el.id || '';
2224
2690
  case 'pp-href': return el.getAttribute('href') || '';
2225
2691
  case 'pp-target': return el.getAttribute('target') || '';
2692
+ case 'pp-rel': return el.getAttribute('rel') || '';
2693
+ case 'pp-download': return el.getAttribute('download') || '';
2694
+ case 'pp-link-title': return el.getAttribute('title') || '';
2695
+ case 'pp-hreflang': return el.getAttribute('hreflang') || '';
2696
+ case 'pp-link-type': return el.getAttribute('type') || '';
2697
+ case 'pp-referrerpolicy': return el.getAttribute('referrerpolicy') || '';
2226
2698
  case 'pp-src': return el.getAttribute('src') || '';
2699
+ case 'pp-video-src': return getVideoSrc(el);
2700
+ case 'pp-youtube-id': return getYoutubeVideoId(el);
2701
+ case 'pp-vimeo-id': return getVimeoVideoId(el);
2227
2702
  case 'pp-alt': return el.getAttribute('alt') || '';
2228
2703
  case 'pp-ph': return el.getAttribute('placeholder') || '';
2704
+ case 'pp-value': return getFormControlValue(el);
2229
2705
  case 'pp-css': return el.getAttribute('style') || '';
2230
2706
  case 'pp-mob-css': return el.dataset.mobileCss || '';
2231
2707
  case 'pp-tab-css': return el.dataset.tabletCss || '';
@@ -2362,9 +2838,19 @@ function revertChangeOnDom(change) {
2362
2838
  case 'pp-css': orig ? el.setAttribute('style', orig) : el.removeAttribute('style'); break;
2363
2839
  case 'pp-href': orig ? el.setAttribute('href', orig) : el.removeAttribute('href'); break;
2364
2840
  case 'pp-target': orig ? el.setAttribute('target', orig) : el.removeAttribute('target'); break;
2841
+ case 'pp-rel': orig ? el.setAttribute('rel', orig) : el.removeAttribute('rel'); break;
2842
+ case 'pp-download': orig ? el.setAttribute('download', orig) : el.removeAttribute('download'); break;
2843
+ case 'pp-link-title': orig ? el.setAttribute('title', orig) : el.removeAttribute('title'); break;
2844
+ case 'pp-hreflang': orig ? el.setAttribute('hreflang', orig) : el.removeAttribute('hreflang'); break;
2845
+ case 'pp-link-type': orig ? el.setAttribute('type', orig) : el.removeAttribute('type'); break;
2846
+ case 'pp-referrerpolicy': orig ? el.setAttribute('referrerpolicy', orig) : el.removeAttribute('referrerpolicy'); break;
2365
2847
  case 'pp-src': orig ? el.setAttribute('src', orig) : el.removeAttribute('src'); break;
2848
+ case 'pp-video-src': orig ? setVideoSrc(el, orig) : setVideoSrc(el, ''); break;
2849
+ case 'pp-youtube-id': orig ? setYoutubeVideoId(el, orig) : setYoutubeVideoId(el, ''); break;
2850
+ case 'pp-vimeo-id': orig ? setVimeoVideoId(el, orig) : setVimeoVideoId(el, ''); break;
2366
2851
  case 'pp-alt': orig ? el.setAttribute('alt', orig) : el.removeAttribute('alt'); break;
2367
2852
  case 'pp-ph': orig ? el.setAttribute('placeholder', orig) : el.removeAttribute('placeholder'); break;
2853
+ case 'pp-value': el.value = orig; break;
2368
2854
  case 'pp-mob-css': el.dataset.mobileCss = orig; break;
2369
2855
  case 'pp-tab-css': el.dataset.tabletCss = orig; break;
2370
2856
  default: console.warn('[V2] revertChangeOnDom: no revert handler for', change.inputId);
@@ -2388,11 +2874,28 @@ function syncDesignInput(change) {
2388
2874
  function removeStateChange(idx) {
2389
2875
  var change = stateChanges[idx];
2390
2876
  if (!change) return;
2877
+ if (
2878
+ change.isStructuralLive &&
2879
+ normalizeChangesetType({ type: change.structuralType }) === 'insert'
2880
+ ) {
2881
+ var instId = historyInsertInstanceId(null, change);
2882
+ if (instId) {
2883
+ removeEditorInsertByInstanceId(instId);
2884
+ commitStateChangesForActiveVariation();
2885
+ renderStatesTab();
2886
+ if (currentMainTab === 'history') renderHistoryTab();
2887
+ try {
2888
+ delete varHtmlCache[activeVarId];
2889
+ } catch(_) {}
2890
+ saveCurrentVariationHtml();
2891
+ recomputeEditorDirty();
2892
+ scheduleDomTreeRefresh();
2893
+ return;
2894
+ }
2895
+ }
2391
2896
  if (change.isStructuralLive) {
2392
- removeSessionStructuralRowByTimestamp(
2393
- change.structuralVarId || activeVarId,
2394
- change.vveTs,
2395
- );
2897
+ var varId = change.structuralVarId || activeVarId;
2898
+ removeSessionStructuralRowByTimestamp(varId, change.vveTs);
2396
2899
  stateChanges.splice(idx, 1);
2397
2900
  commitStateChangesForActiveVariation();
2398
2901
  renderStatesTab();
@@ -2490,10 +2993,28 @@ function captureChangesetSnapshotBeforeApply(entry, el, iframeDoc) {
2490
2993
  break;
2491
2994
  case 'attribute':
2492
2995
  if (entry.attribute) {
2996
+ var attrVal = el.getAttribute(entry.attribute);
2997
+ if (
2998
+ String(entry.attribute).toLowerCase() === 'src' &&
2999
+ el.tagName &&
3000
+ el.tagName.toLowerCase() === 'video'
3001
+ ) {
3002
+ attrVal = getVideoSrc(el);
3003
+ } else if (
3004
+ String(entry.attribute).toLowerCase() === 'data-youtube-id' &&
3005
+ isYoutubeVideoElement(el)
3006
+ ) {
3007
+ attrVal = getYoutubeVideoId(el);
3008
+ } else if (
3009
+ String(entry.attribute).toLowerCase() === 'data-vimeo-id' &&
3010
+ isVimeoVideoElement(el)
3011
+ ) {
3012
+ attrVal = getVimeoVideoId(el);
3013
+ }
2493
3014
  appliedChangesetSnapshots[k] = {
2494
3015
  kind: 'attribute',
2495
3016
  name: entry.attribute,
2496
- v: el.getAttribute(entry.attribute),
3017
+ v: attrVal,
2497
3018
  };
2498
3019
  }
2499
3020
  break;
@@ -2542,6 +3063,13 @@ function revertChangesetEntryOnDom(entry) {
2542
3063
  var iframeDoc = document.getElementById('iframeId').contentDocument;
2543
3064
  if (!iframeDoc) return false;
2544
3065
  var k = entrySnapshotKey(entry);
3066
+ if (normalizeChangesetType(entry) === 'insert') {
3067
+ var instId = instanceIdFromInsertHtml(entry.html);
3068
+ delete appliedChangesetSnapshots[k];
3069
+ if (instId && removeEditorInsertByInstanceId(instId)) return false;
3070
+ softReloadEditorIframe();
3071
+ return true;
3072
+ }
2545
3073
  var snap = appliedChangesetSnapshots[k];
2546
3074
  var el = querySelectorResolved(iframeDoc, entry.selector);
2547
3075
  if (!snap || !el) {
@@ -2560,7 +3088,23 @@ function revertChangesetEntryOnDom(entry) {
2560
3088
  if (snap.v) el.setAttribute('style', snap.v);
2561
3089
  else el.removeAttribute('style');
2562
3090
  } else if (snap.kind === 'attribute' && snap.name) {
2563
- if (snap.v == null || snap.v === '') el.removeAttribute(snap.name);
3091
+ if (
3092
+ String(snap.name).toLowerCase() === 'src' &&
3093
+ el.tagName &&
3094
+ el.tagName.toLowerCase() === 'video'
3095
+ ) {
3096
+ setVideoSrc(el, snap.v == null ? '' : snap.v);
3097
+ } else if (
3098
+ String(snap.name).toLowerCase() === 'data-youtube-id' &&
3099
+ isYoutubeVideoElement(el)
3100
+ ) {
3101
+ setYoutubeVideoId(el, snap.v == null ? '' : snap.v);
3102
+ } else if (
3103
+ String(snap.name).toLowerCase() === 'data-vimeo-id' &&
3104
+ isVimeoVideoElement(el)
3105
+ ) {
3106
+ setVimeoVideoId(el, snap.v == null ? '' : snap.v);
3107
+ } else if (snap.v == null || snap.v === '') el.removeAttribute(snap.name);
2564
3108
  else el.setAttribute(snap.name, snap.v);
2565
3109
  } else if (snap.kind === 'display') el.style.display = snap.v;
2566
3110
  else {
@@ -2641,6 +3185,42 @@ function formatHistoryRelativeTime(ts) {
2641
3185
  return day + ' day' + (day === 1 ? '' : 's') + ' ago';
2642
3186
  }
2643
3187
 
3188
+ function findSessionInsertRowForChange(change) {
3189
+ if (!change) return null;
3190
+ var varId = change.structuralVarId || activeVarId;
3191
+ var arr = varId && sessionStructuralChainRowsByVarId[varId];
3192
+ if (!arr || !arr.length) return null;
3193
+ var instId = '';
3194
+ try {
3195
+ if (change.targetEl && change.targetEl.getAttribute) {
3196
+ instId = change.targetEl.getAttribute('data-vve-instance') || '';
3197
+ }
3198
+ } catch(_) {}
3199
+ for (var i = 0; i < arr.length; i++) {
3200
+ var row = arr[i];
3201
+ if (!row || normalizeChangesetType(row) !== 'insert') continue;
3202
+ if (change.vveTs && row.vveTs === change.vveTs) return row;
3203
+ if (instId && instanceIdFromInsertHtml(row.html) === instId) return row;
3204
+ }
3205
+ return null;
3206
+ }
3207
+
3208
+ function historyInsertInstanceId(entry, change) {
3209
+ if (entry && normalizeChangesetType(entry) === 'insert') {
3210
+ return instanceIdFromInsertHtml(entry.html);
3211
+ }
3212
+ if (change && change.isStructuralLive && normalizeChangesetType({ type: change.structuralType }) === 'insert') {
3213
+ var row = findSessionInsertRowForChange(change);
3214
+ if (row) return instanceIdFromInsertHtml(row.html);
3215
+ try {
3216
+ if (change.targetEl && change.targetEl.getAttribute) {
3217
+ return change.targetEl.getAttribute('data-vve-instance') || '';
3218
+ }
3219
+ } catch(_) {}
3220
+ }
3221
+ return '';
3222
+ }
3223
+
2644
3224
  function getUnifiedHistoryItems() {
2645
3225
  var out = [];
2646
3226
  var v = getActiveVariationForHistory();
@@ -2650,6 +3230,7 @@ function getUnifiedHistoryItems() {
2650
3230
  out.push({
2651
3231
  source: 'saved',
2652
3232
  idx: i,
3233
+ insertInstanceId: historyInsertInstanceId(e, null),
2653
3234
  selector: (e && e.selector) || '(unknown)',
2654
3235
  label: historyEntryTypeLabel(e),
2655
3236
  value: historyEntryValuePreview(e),
@@ -2666,6 +3247,7 @@ function getUnifiedHistoryItems() {
2666
3247
  out.push({
2667
3248
  source: 'live',
2668
3249
  idx: j,
3250
+ insertInstanceId: historyInsertInstanceId(null, c),
2669
3251
  selector: c.selector || '(unknown)',
2670
3252
  label: c.label || 'Live change',
2671
3253
  value: c.value != null ? String(c.value).slice(0, 120) : '',
@@ -2699,11 +3281,15 @@ function renderHistoryTab() {
2699
3281
  var title = 'Edit - ' + (it.label || 'Change');
2700
3282
  var avatarLabel = it.source === 'live' ? 'Y' : 'S';
2701
3283
  var timeText = formatHistoryRelativeTime(it.ts) || (it.tsLabel || '');
3284
+ var removeOnclick = it.insertInstanceId
3285
+ ? 'removeHistoryItem(&quot;' + esc(it.source) + '&quot;,' + it.idx + ', event, &quot;' + esc(it.insertInstanceId) + '&quot;)'
3286
+ : 'removeHistoryItem(&quot;' + esc(it.source) + '&quot;,' + it.idx + ', event)';
2702
3287
  html +=
2703
3288
  '<div class="history-item" role="button" tabindex="0" title="Jump to element in iframe" onclick="focusHistoryItem(&quot;' +
2704
3289
  esc(it.source) +
2705
3290
  '&quot;,' +
2706
3291
  it.idx +
3292
+ (it.insertInstanceId ? ', &quot;' + esc(it.insertInstanceId) + '&quot;' : '') +
2707
3293
  ')">' +
2708
3294
  '<span class="history-dot"></span>' +
2709
3295
  '<div class="history-card">' +
@@ -2714,18 +3300,16 @@ function renderHistoryTab() {
2714
3300
  '</div>' +
2715
3301
  '<div class="history-time">' + esc(timeText || 'n/a') + '</div>' +
2716
3302
  '</div>' +
2717
- '<button type="button" class="state-remove history-remove" title="Remove this change" onclick="removeHistoryItem(&quot;' +
2718
- esc(it.source) +
2719
- '&quot;,' +
2720
- it.idx +
2721
- ', event)">&#x2715;</button>' +
3303
+ '<button type="button" class="state-remove history-remove" title="Remove this change" onclick="' +
3304
+ removeOnclick +
3305
+ '">&#x2715;</button>' +
2722
3306
  '</div>';
2723
3307
  }
2724
3308
  html += '</div>';
2725
3309
  container.innerHTML = html;
2726
3310
  }
2727
3311
 
2728
- function focusHistoryItem(source, idx) {
3312
+ function focusHistoryItem(source, idx, insertInstanceId) {
2729
3313
  if (source === 'live') {
2730
3314
  var change = stateChanges[idx];
2731
3315
  if (!change || !change.selector) return;
@@ -2733,6 +3317,20 @@ function focusHistoryItem(source, idx) {
2733
3317
  var iframe = document.getElementById('iframeId');
2734
3318
  var iframeDoc = iframe && iframe.contentDocument;
2735
3319
  if (!iframeDoc) return;
3320
+ var instId = insertInstanceId || historyInsertInstanceId(null, change);
3321
+ if (instId) {
3322
+ var safeInst = String(instId).split('"').join('\\"');
3323
+ var insertedEl = iframeDoc.querySelector('[data-vve-instance="' + safeInst + '"]');
3324
+ if (insertedEl) {
3325
+ selectElement(insertedEl);
3326
+ try {
3327
+ insertedEl.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'nearest' });
3328
+ } catch(_) {
3329
+ insertedEl.scrollIntoView();
3330
+ }
3331
+ return;
3332
+ }
3333
+ }
2736
3334
  var el = querySelectorResolved(iframeDoc, change.selector);
2737
3335
  if (!el) return;
2738
3336
  selectElement(el);
@@ -2744,12 +3342,25 @@ function focusHistoryItem(source, idx) {
2744
3342
  } catch(_) {}
2745
3343
  return;
2746
3344
  }
2747
- focusHistoryChangeset(idx);
3345
+ focusHistoryChangeset(idx, insertInstanceId);
2748
3346
  }
2749
3347
 
2750
- function removeHistoryItem(source, idx, evt) {
3348
+ function removeHistoryItem(source, idx, evt, insertInstanceId) {
3349
+ if (evt && evt.stopPropagation) evt.stopPropagation();
3350
+ if (insertInstanceId) {
3351
+ removeEditorInsertByInstanceId(insertInstanceId);
3352
+ commitStateChangesForActiveVariation();
3353
+ try {
3354
+ delete varHtmlCache[activeVarId];
3355
+ } catch(_) {}
3356
+ saveCurrentVariationHtml();
3357
+ recomputeEditorDirty();
3358
+ if (currentMainTab === 'states') renderStatesTab();
3359
+ if (currentMainTab === 'history') renderHistoryTab();
3360
+ scheduleDomTreeRefresh();
3361
+ return;
3362
+ }
2751
3363
  if (source === 'live') {
2752
- if (evt && evt.stopPropagation) evt.stopPropagation();
2753
3364
  removeStateChange(idx);
2754
3365
  if (currentMainTab === 'history') renderHistoryTab();
2755
3366
  return;
@@ -2788,7 +3399,7 @@ function isStyleOnlyChangesetEntry(entry) {
2788
3399
  return false;
2789
3400
  }
2790
3401
 
2791
- function focusHistoryChangeset(idx) {
3402
+ function focusHistoryChangeset(idx, insertInstanceId) {
2792
3403
  var v = getActiveVariationForHistory();
2793
3404
  if (!v) return;
2794
3405
  var arr = parseVariationChangesets(v);
@@ -2799,6 +3410,20 @@ function focusHistoryChangeset(idx) {
2799
3410
  var iframe = document.getElementById('iframeId');
2800
3411
  var iframeDoc = iframe && iframe.contentDocument;
2801
3412
  if (!iframeDoc) return;
3413
+ var instId = insertInstanceId || historyInsertInstanceId(entry, null);
3414
+ if (instId) {
3415
+ var safeInst = String(instId).split('"').join('\\"');
3416
+ var insertedEl = iframeDoc.querySelector('[data-vve-instance="' + safeInst + '"]');
3417
+ if (insertedEl) {
3418
+ selectElement(insertedEl);
3419
+ try {
3420
+ insertedEl.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'nearest' });
3421
+ } catch(_) {
3422
+ insertedEl.scrollIntoView();
3423
+ }
3424
+ return;
3425
+ }
3426
+ }
2802
3427
  var el = querySelectorResolved(iframeDoc, entry.selector);
2803
3428
  if (!el) return;
2804
3429
  selectElement(el);
@@ -2840,6 +3465,10 @@ function removeHistoryChangeset(idx, evt) {
2840
3465
  var removedIsStructural = removedType === 'insert' || removedType === 'reorder';
2841
3466
  if (didReload) {
2842
3467
  /* revertChangesetEntryOnDom already kicked off iframe reload */
3468
+ } else if (removedType === 'insert') {
3469
+ try {
3470
+ saveCurrentVariationHtml();
3471
+ } catch(_) {}
2843
3472
  } else if (removedIsStructural) {
2844
3473
  softReloadEditorIframe();
2845
3474
  } else if (hasStructuralRemaining) {
@@ -3508,6 +4137,14 @@ function loadPage(proxyUrl) {
3508
4137
  // \u2500\u2500 Variation management \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
3509
4138
  var VAR_COLORS = ['#0069A8','#CA3500','#00786F','#ec4899','#14b8a6','#f59e0b','#8b5cf6','#ef4444'];
3510
4139
 
4140
+ function varColorAlpha(hex, alpha) {
4141
+ var h = hex.slice(1);
4142
+ return 'rgba(' +
4143
+ parseInt(h.slice(0, 2), 16) + ',' +
4144
+ parseInt(h.slice(2, 4), 16) + ',' +
4145
+ parseInt(h.slice(4, 6), 16) + ',' + alpha + ')';
4146
+ }
4147
+
3511
4148
  function renderVariationTabs() {
3512
4149
  var container = document.getElementById('variation-tabs');
3513
4150
  container.innerHTML = '';
@@ -3519,7 +4156,12 @@ function renderVariationTabs() {
3519
4156
  btn.onclick = function() { switchVariation(v._id); };
3520
4157
  var dot = document.createElement('span');
3521
4158
  dot.className = 'var-dot';
3522
- dot.style.background = VAR_COLORS[i % VAR_COLORS.length];
4159
+ var color = VAR_COLORS[i % VAR_COLORS.length];
4160
+ btn.setAttribute('data-color', color);
4161
+ btn.style.setProperty('--var-tab-color', color);
4162
+ dot.setAttribute('data-color', color);
4163
+ dot.style.background = color;
4164
+ dot.style.boxShadow = '0 0 0 3px ' + varColorAlpha(color, 0.5);
3523
4165
  btn.appendChild(dot);
3524
4166
  btn.appendChild(document.createTextNode(label));
3525
4167
  container.appendChild(btn);
@@ -3720,6 +4362,29 @@ function removeSessionStructuralRowByTimestamp(varId, ts) {
3720
4362
  }
3721
4363
  }
3722
4364
 
4365
+ function removeStructuralStateChangeByTimestamp(varId, ts) {
4366
+ if (!varId || !ts) return;
4367
+ for (var i = stateChanges.length - 1; i >= 0; i--) {
4368
+ var c = stateChanges[i];
4369
+ if (
4370
+ c &&
4371
+ c.isStructuralLive &&
4372
+ c.vveTs === ts &&
4373
+ (c.structuralVarId || activeVarId) === varId
4374
+ ) {
4375
+ stateChanges.splice(i, 1);
4376
+ }
4377
+ }
4378
+ }
4379
+
4380
+ /** Undo a session insert when its root node is deleted \u2014 avoids orphan remove rows that match re-inserts at the same slot. */
4381
+ function cancelSessionInsertForElement(varId, el) {
4382
+ if (!varId || !el) return false;
4383
+ var inst = el.getAttribute && el.getAttribute('data-vve-instance');
4384
+ if (!inst || !String(inst).trim()) return false;
4385
+ return removeEditorInsertByInstanceId(inst, { skipDom: true, varId: varId });
4386
+ }
4387
+
3723
4388
  /** One States-tab row -> Conversion.io chain-set shape (matches applyChangesetEntry). */
3724
4389
  function stateChangeToChainSet(c) {
3725
4390
  if (!c || !c.selector) return null;
@@ -3745,12 +4410,32 @@ function stateChangeToChainSet(c) {
3745
4410
  return { selector: c.selector, type: 'attribute', attribute: 'href', value: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
3746
4411
  case 'pp-target':
3747
4412
  return { selector: c.selector, type: 'attribute', attribute: 'target', value: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
4413
+ case 'pp-rel':
4414
+ return { selector: c.selector, type: 'attribute', attribute: 'rel', value: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
4415
+ case 'pp-download':
4416
+ return { selector: c.selector, type: 'attribute', attribute: 'download', value: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
4417
+ case 'pp-link-title':
4418
+ return { selector: c.selector, type: 'attribute', attribute: 'title', value: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
4419
+ case 'pp-hreflang':
4420
+ return { selector: c.selector, type: 'attribute', attribute: 'hreflang', value: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
4421
+ case 'pp-link-type':
4422
+ return { selector: c.selector, type: 'attribute', attribute: 'type', value: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
4423
+ case 'pp-referrerpolicy':
4424
+ return { selector: c.selector, type: 'attribute', attribute: 'referrerpolicy', value: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
3748
4425
  case 'pp-src':
3749
4426
  return { selector: c.selector, type: 'attribute', attribute: 'src', value: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
4427
+ case 'pp-video-src':
4428
+ return { selector: c.selector, type: 'attribute', attribute: 'src', value: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
4429
+ case 'pp-youtube-id':
4430
+ return { selector: c.selector, type: 'attribute', attribute: 'data-youtube-id', value: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
4431
+ case 'pp-vimeo-id':
4432
+ return { selector: c.selector, type: 'attribute', attribute: 'data-vimeo-id', value: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
3750
4433
  case 'pp-alt':
3751
4434
  return { selector: c.selector, type: 'attribute', attribute: 'alt', value: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
3752
4435
  case 'pp-ph':
3753
4436
  return { selector: c.selector, type: 'attribute', attribute: 'placeholder', value: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
4437
+ case 'pp-value':
4438
+ return { selector: c.selector, type: 'attribute', attribute: 'value', value: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
3754
4439
  case 'pp-css':
3755
4440
  return { selector: c.selector, type: 'attribute', attribute: 'style', value: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
3756
4441
  case 'pp-mob-css':
@@ -4010,7 +4695,25 @@ function applyChangesetEntry(entry, iframeDoc) {
4010
4695
  case 'attribute':
4011
4696
  if (entry.attribute && entry.value != null) {
4012
4697
  if (String(entry.attribute).toLowerCase() === 'style') break;
4013
- el.setAttribute(entry.attribute, entry.value);
4698
+ if (
4699
+ String(entry.attribute).toLowerCase() === 'src' &&
4700
+ el.tagName &&
4701
+ el.tagName.toLowerCase() === 'video'
4702
+ ) {
4703
+ setVideoSrc(el, entry.value);
4704
+ } else if (
4705
+ String(entry.attribute).toLowerCase() === 'data-youtube-id' &&
4706
+ isYoutubeVideoElement(el)
4707
+ ) {
4708
+ setYoutubeVideoId(el, entry.value);
4709
+ } else if (
4710
+ String(entry.attribute).toLowerCase() === 'data-vimeo-id' &&
4711
+ isVimeoVideoElement(el)
4712
+ ) {
4713
+ setVimeoVideoId(el, entry.value);
4714
+ } else {
4715
+ el.setAttribute(entry.attribute, entry.value);
4716
+ }
4014
4717
  }
4015
4718
  break;
4016
4719
  case 'insert': {
@@ -4019,9 +4722,12 @@ function applyChangesetEntry(entry, iframeDoc) {
4019
4722
  if (structuralDedupeKey) appliedStructuralChangesetKeys[structuralDedupeKey] = true;
4020
4723
  break;
4021
4724
  }
4022
- case 'remove':
4725
+ case 'remove': {
4726
+ var inst = el.getAttribute && el.getAttribute('data-vve-instance');
4727
+ if (inst && String(entry.selector || '').indexOf('data-vve-instance') < 0) break;
4023
4728
  el.style.display = 'none';
4024
4729
  break;
4730
+ }
4025
4731
  case 'reorder': {
4026
4732
  var target = entry.targetSelector ? querySelectorResolved(iframeDoc, entry.targetSelector) : null;
4027
4733
  if (!target || !el.parentNode || !target.parentNode) break;
@@ -4241,7 +4947,11 @@ function deselectElement(options) {
4241
4947
  document.getElementById('rp-accordion').style.display = 'none';
4242
4948
  document.getElementById('bc-path').textContent = 'No element selected';
4243
4949
  document.getElementById('bc-path').style.color = 'var(--text-3)';
4244
- switchMainTab('design');
4950
+ var preserveMainTab =
4951
+ !!(options && options.preserveMainTab) ||
4952
+ currentMainTab === 'history' ||
4953
+ currentMainTab === 'states';
4954
+ if (!preserveMainTab) switchMainTab('design');
4245
4955
  if (!skipToolbarUpdate) updateSelectionToolbar();
4246
4956
  syncDomTreeSelection();
4247
4957
  } finally {
@@ -4328,6 +5038,30 @@ function setDragHandleActive(on) {
4328
5038
  }
4329
5039
  }
4330
5040
 
5041
+ function getIframeElementVisualRect(el) {
5042
+ var iframe = document.getElementById('iframeId');
5043
+ if (!el || !iframe) return null;
5044
+ // Element rects from the iframe document are in iframe viewport space, not the shell viewport.
5045
+ var elR = el.getBoundingClientRect();
5046
+ var iframeR = iframe.getBoundingClientRect();
5047
+ var cw = iframe.clientWidth || iframe.offsetWidth || 1;
5048
+ var ch = iframe.clientHeight || iframe.offsetHeight || 1;
5049
+ var scaleX = iframeR.width / cw;
5050
+ var scaleY = iframeR.height / ch;
5051
+ var top = iframeR.top + elR.top * scaleY;
5052
+ var left = iframeR.left + elR.left * scaleX;
5053
+ var width = elR.width * scaleX;
5054
+ var height = elR.height * scaleY;
5055
+ return {
5056
+ left: left,
5057
+ top: top,
5058
+ width: width,
5059
+ height: height,
5060
+ right: left + width,
5061
+ bottom: top + height,
5062
+ };
5063
+ }
5064
+
4331
5065
  function positionSelectionToolbar() {
4332
5066
  var bar = document.getElementById('selection-floater');
4333
5067
  var iframe = document.getElementById('iframeId');
@@ -4339,16 +5073,20 @@ function positionSelectionToolbar() {
4339
5073
  renderRightPanel(liveSelected);
4340
5074
  syncDomTreeSelection();
4341
5075
  }
4342
- var elR = selectedEl.getBoundingClientRect();
4343
- var iframeR = iframe.getBoundingClientRect();
5076
+ var elR = getIframeElementVisualRect(selectedEl);
5077
+ if (!elR) return;
4344
5078
  var panelR = panel.getBoundingClientRect();
4345
- var left = iframeR.left - panelR.left + elR.left + elR.width / 2 - bar.offsetWidth / 2;
4346
- var top = iframeR.top - panelR.top + elR.top - bar.offsetHeight - 8;
4347
- if (top < 6) top = iframeR.top - panelR.top + elR.bottom + 8;
4348
- var maxL = Math.max(6, panel.clientWidth - bar.offsetWidth - 8);
4349
- left = Math.max(6, Math.min(left, maxL));
5079
+ var panelScrollLeft = panel.scrollLeft || 0;
5080
+ var panelScrollTop = panel.scrollTop || 0;
5081
+ var left = elR.left - panelR.left + panelScrollLeft + elR.width / 2 - bar.offsetWidth / 2;
5082
+ var top = elR.top - panelR.top + panelScrollTop - bar.offsetHeight - 8;
5083
+ if (top < panelScrollTop + 6) top = elR.bottom - panelR.top + panelScrollTop + 8;
5084
+ var minL = panelScrollLeft + 6;
5085
+ var maxL = panelScrollLeft + Math.max(6, panel.clientWidth - bar.offsetWidth - 8);
5086
+ left = Math.max(minL, Math.min(left, maxL));
4350
5087
  bar.style.left = Math.round(left) + 'px';
4351
5088
  bar.style.top = Math.round(top) + 'px';
5089
+ syncMoveFloaterButtons(selectedEl);
4352
5090
  }
4353
5091
 
4354
5092
  function updateSelectionToolbar() {
@@ -4366,6 +5104,7 @@ function updateSelectionToolbar() {
4366
5104
  if (selectedEl !== liveSelected) selectedEl = liveSelected;
4367
5105
  selectedElFingerprint = buildSelector(liveSelected);
4368
5106
  bar.style.display = 'flex';
5107
+ syncMoveFloaterButtons(liveSelected);
4369
5108
  requestAnimationFrame(function() { positionSelectionToolbar(); });
4370
5109
  }
4371
5110
 
@@ -4382,6 +5121,11 @@ function bindSelectionToolbarScroll() {
4382
5121
  }
4383
5122
  selectionScrollWin = w;
4384
5123
  w.addEventListener('scroll', onFloaterScroll, true);
5124
+ var panel = document.getElementById('iframe-panel');
5125
+ if (panel && !selectionPanelScrollBound) {
5126
+ selectionPanelScrollBound = true;
5127
+ panel.addEventListener('scroll', onFloaterScroll, { passive: true });
5128
+ }
4385
5129
  if (!selectionResizeBound) {
4386
5130
  selectionResizeBound = true;
4387
5131
  window.addEventListener('resize', onFloaterScroll);
@@ -4403,7 +5147,6 @@ function scrollIframeElementIntoView(el) {
4403
5147
  function selectElementFromTree(el) {
4404
5148
  selectElement(el);
4405
5149
  scrollIframeElementIntoView(el);
4406
- if (currentMainTab !== 'design') switchMainTab('design');
4407
5150
  }
4408
5151
 
4409
5152
  function duplicateSelectedEl() {
@@ -4476,12 +5219,26 @@ function toggleHideSelectedEl() {
4476
5219
 
4477
5220
  function deleteSelectedEl() {
4478
5221
  if (!selectedEl || !selectedEl.parentNode) return;
5222
+ var inst = selectedEl.getAttribute && selectedEl.getAttribute('data-vve-instance');
5223
+ if (inst && activeVarId && removeEditorInsertByInstanceId(inst)) {
5224
+ saveCurrentVariationHtml();
5225
+ recomputeEditorDirty();
5226
+ deselectElement();
5227
+ scheduleDomTreeRefresh();
5228
+ if (currentMainTab === 'history') renderHistoryTab();
5229
+ return;
5230
+ }
4479
5231
  var delSel = buildSelector(selectedEl);
5232
+ var cancelledInsert =
5233
+ activeVarId && cancelSessionInsertForElement(activeVarId, selectedEl);
4480
5234
  selectedEl.remove();
4481
- if (activeVarId) {
5235
+ if (activeVarId && !cancelledInsert) {
4482
5236
  var delRow = { selector: delSel, type: 'remove' };
4483
5237
  appendSessionStructuralChainRow(activeVarId, delRow);
4484
5238
  logStructuralStateChange(delRow, 'Deleted via toolbar', 'Element removed', null);
5239
+ } else if (cancelledInsert) {
5240
+ commitStateChangesForActiveVariation();
5241
+ if (currentMainTab === 'history') renderHistoryTab();
4485
5242
  }
4486
5243
  saveCurrentVariationHtml();
4487
5244
  recomputeEditorDirty();
@@ -4508,14 +5265,13 @@ function syncDomTreeSelection() {
4508
5265
  }
4509
5266
 
4510
5267
  function scheduleDomTreeRefresh() {
4511
- if (currentLeftTab !== 'elements' && currentLeftTab !== 'dom-tree') return;
4512
5268
  if (domTreeRefreshTimer) clearTimeout(domTreeRefreshTimer);
4513
5269
  domTreeRefreshTimer = setTimeout(function() {
4514
5270
  domTreeRefreshTimer = null;
4515
5271
  var inp = document.getElementById('comp-search');
4516
5272
  var q = inp ? inp.value : '';
4517
- if (currentLeftTab === 'elements') renderElementsTree(q);
4518
- else if (currentLeftTab === 'dom-tree') renderDomTree(q);
5273
+ renderElementsTree(q);
5274
+ if (currentLeftTab === 'dom-tree') renderDomTree(q);
4519
5275
  }, 150);
4520
5276
  }
4521
5277
 
@@ -4538,6 +5294,162 @@ function domTreePathSegment(el) {
4538
5294
  return tag + '[' + idx + ']';
4539
5295
  }
4540
5296
 
5297
+ function instanceIdFromInsertHtml(html) {
5298
+ var m = String(html || '').match(/data-vve-instance=["']([^"']+)["']/);
5299
+ return m ? m[1] : '';
5300
+ }
5301
+
5302
+ function resolveInsertedElementFromEntry(doc, entry) {
5303
+ if (!doc || !entry) return null;
5304
+ var instId = instanceIdFromInsertHtml(entry.html);
5305
+ if (!instId) return null;
5306
+ try {
5307
+ var safe = String(instId).split('"').join('\\"');
5308
+ return doc.querySelector('[data-vve-instance="' + safe + '"]');
5309
+ } catch(_) {
5310
+ return null;
5311
+ }
5312
+ }
5313
+
5314
+ function removeInsertFromDomByEntry(entry, iframeDoc) {
5315
+ if (!entry || !iframeDoc) return false;
5316
+ var insertedEl = resolveInsertedElementFromEntry(iframeDoc, entry);
5317
+ if (!insertedEl || !insertedEl.parentNode) return false;
5318
+ if (
5319
+ selectedEl &&
5320
+ (selectedEl === insertedEl || (insertedEl.contains && insertedEl.contains(selectedEl)))
5321
+ ) {
5322
+ deselectElement();
5323
+ }
5324
+ insertedEl.remove();
5325
+ return true;
5326
+ }
5327
+
5328
+ /** Remove an editor insert everywhere (DOM + session + saved changesets + live history), keyed by data-vve-instance. */
5329
+ function removeEditorInsertByInstanceId(instId, opts) {
5330
+ if (!instId || !String(instId).trim()) return false;
5331
+ opts = opts || {};
5332
+ var varId = opts.varId || activeVarId;
5333
+ if (!varId) return false;
5334
+ var needle = 'data-vve-instance="' + String(instId).split('"').join('\\"') + '"';
5335
+ var didWork = false;
5336
+
5337
+ if (!opts.skipDom) {
5338
+ var iframeDoc = document.getElementById('iframeId').contentDocument;
5339
+ if (iframeDoc) {
5340
+ try {
5341
+ var safe = String(instId).split('"').join('\\"');
5342
+ var el = iframeDoc.querySelector('[data-vve-instance="' + safe + '"]');
5343
+ if (el && el.parentNode) {
5344
+ if (
5345
+ selectedEl &&
5346
+ (selectedEl === el || (el.contains && el.contains(selectedEl)))
5347
+ ) {
5348
+ deselectElement();
5349
+ }
5350
+ el.remove();
5351
+ didWork = true;
5352
+ }
5353
+ } catch(_) {}
5354
+ }
5355
+ }
5356
+
5357
+ var sessionArr = sessionStructuralChainRowsByVarId[varId];
5358
+ if (sessionArr && sessionArr.length) {
5359
+ for (var i = sessionArr.length - 1; i >= 0; i--) {
5360
+ var row = sessionArr[i];
5361
+ if (!row || normalizeChangesetType(row) !== 'insert' || !row.html) continue;
5362
+ if (String(row.html).indexOf(needle) < 0) continue;
5363
+ var ts = row.vveTs;
5364
+ try {
5365
+ var k = structuralChangesetDedupKey(row);
5366
+ if (k) delete appliedStructuralChangesetKeys[k];
5367
+ } catch(_) {}
5368
+ sessionArr.splice(i, 1);
5369
+ removeStructuralStateChangeByTimestamp(varId, ts);
5370
+ didWork = true;
5371
+ }
5372
+ }
5373
+
5374
+ var v = variations.find(function(x) { return x._id === varId; });
5375
+ if (v) {
5376
+ var saved = parseVariationChangesets(v);
5377
+ var savedChanged = false;
5378
+ for (var si = saved.length - 1; si >= 0; si--) {
5379
+ var entry = saved[si];
5380
+ if (!entry || normalizeChangesetType(entry) !== 'insert' || !entry.html) continue;
5381
+ if (String(entry.html).indexOf(needle) < 0) continue;
5382
+ try {
5383
+ delete appliedChangesetSnapshots[entrySnapshotKey(entry)];
5384
+ } catch(_) {}
5385
+ saved.splice(si, 1);
5386
+ savedChanged = true;
5387
+ didWork = true;
5388
+ }
5389
+ if (savedChanged && varId === activeVarId) persistActiveVariationChangesets(saved);
5390
+ }
5391
+
5392
+ for (var j = stateChanges.length - 1; j >= 0; j--) {
5393
+ var c = stateChanges[j];
5394
+ if (!c || !c.isStructuralLive) continue;
5395
+ if (normalizeChangesetType({ type: c.structuralType }) !== 'insert') continue;
5396
+ var match = false;
5397
+ try {
5398
+ if (c.targetEl && c.targetEl.getAttribute && c.targetEl.getAttribute('data-vve-instance') === instId) {
5399
+ match = true;
5400
+ }
5401
+ } catch(_) {}
5402
+ if (match) {
5403
+ stateChanges.splice(j, 1);
5404
+ didWork = true;
5405
+ }
5406
+ }
5407
+
5408
+ return didWork;
5409
+ }
5410
+
5411
+ /** Live DOM nodes inserted via the visual editor, ordered by changeset/session insert order. */
5412
+ function collectEditorInsertedElements(doc) {
5413
+ if (!doc || !doc.body) return [];
5414
+ var byInst = {};
5415
+ var els;
5416
+ try {
5417
+ els = doc.querySelectorAll('[data-vve-instance]');
5418
+ } catch(_) {
5419
+ els = [];
5420
+ }
5421
+ for (var i = 0; i < els.length; i++) {
5422
+ var el = els[i];
5423
+ if (!el || el.nodeType !== 1) continue;
5424
+ var inst = el.getAttribute('data-vve-instance');
5425
+ if (inst && String(inst).trim()) byInst[String(inst)] = el;
5426
+ }
5427
+
5428
+ var ordered = [];
5429
+ var seen = {};
5430
+ function addEl(el) {
5431
+ if (!el || !el.isConnected) return;
5432
+ var k = el.getAttribute && el.getAttribute('data-vve-instance');
5433
+ if (!k || seen[k]) return;
5434
+ seen[k] = true;
5435
+ ordered.push(el);
5436
+ }
5437
+
5438
+ if (activeVarId) {
5439
+ var variation = variations.find(function(v) { return v._id === activeVarId; });
5440
+ if (variation) {
5441
+ var cs = buildPersistedChainSetsForVariation(variation);
5442
+ for (var j = 0; j < cs.length; j++) {
5443
+ var row = cs[j];
5444
+ if (normalizeChangesetType(row) !== 'insert') continue;
5445
+ var instId = instanceIdFromInsertHtml(row.html);
5446
+ if (instId && byInst[instId]) addEl(byInst[instId]);
5447
+ }
5448
+ }
5449
+ }
5450
+ return ordered;
5451
+ }
5452
+
4541
5453
  function renderElementsTree(filterRaw) {
4542
5454
  var filterText = (filterRaw || '').toLowerCase().trim();
4543
5455
  var root = document.getElementById('elements-root');
@@ -4545,14 +5457,10 @@ function renderElementsTree(filterRaw) {
4545
5457
  var iframe = document.getElementById('iframeId');
4546
5458
  var doc = iframe && iframe.contentDocument;
4547
5459
  if (!isIframeDomReady(iframe, doc)) {
4548
- root.innerHTML = '<div class="dt-muted">Load a page to see the elements.</div>';
5460
+ root.innerHTML = '<div class="dt-muted">Load a page to see added elements.</div>';
4549
5461
  return;
4550
5462
  }
4551
5463
 
4552
- function skippable(el) {
4553
- return isDomTreeSkippableTagName(el.tagName);
4554
- }
4555
-
4556
5464
  function nodeIcon(tag) {
4557
5465
  tag = (tag || '').toLowerCase();
4558
5466
  if (/^h[1-6]$/.test(tag)) return 'bi bi-type-h1';
@@ -4572,50 +5480,10 @@ function renderElementsTree(filterRaw) {
4572
5480
  return tag.toUpperCase();
4573
5481
  }
4574
5482
 
4575
- function isListableNode(el) {
4576
- if (!el || el.nodeType !== 1) return false;
4577
- if (skippable(el)) return false;
4578
- var tag = (el.tagName || '').toLowerCase();
4579
- if (tag !== 'svg') {
4580
- var p = el.parentElement;
4581
- while (p) {
4582
- if ((p.tagName || '').toLowerCase() === 'svg') return false;
4583
- p = p.parentElement;
4584
- }
4585
- }
4586
- return true;
4587
- }
4588
-
4589
- var nodes = [];
4590
- var i, cursor;
4591
- try {
4592
- cursor = doc.createTreeWalker(doc.body, NodeFilter.SHOW_ELEMENT, null);
4593
- } catch(_) {
4594
- cursor = null;
4595
- }
4596
- if (cursor) {
4597
- while (cursor.nextNode()) {
4598
- var node = cursor.currentNode;
4599
- if (isListableNode(node)) nodes.push(node);
4600
- if (nodes.length > 4000) break;
4601
- }
4602
- } else {
4603
- function collectFlat(el) {
4604
- if (!el || !el.children) return;
4605
- for (var j = 0; j < el.children.length; j++) {
4606
- var c = el.children[j];
4607
- if (!isListableNode(c)) continue;
4608
- nodes.push(c);
4609
- if (nodes.length > 4000) return;
4610
- collectFlat(c);
4611
- if (nodes.length > 4000) return;
4612
- }
4613
- }
4614
- collectFlat(doc.body);
4615
- }
5483
+ var nodes = collectEditorInsertedElements(doc);
4616
5484
 
4617
5485
  root.innerHTML = '';
4618
- for (i = 0; i < nodes.length; i++) {
5486
+ for (var i = 0; i < nodes.length; i++) {
4619
5487
  var el = nodes[i];
4620
5488
  var lblText = labelFor(el);
4621
5489
  if (filterText && lblText.toLowerCase().indexOf(filterText) < 0) continue;
@@ -4653,14 +5521,39 @@ function renderElementsTree(filterRaw) {
4653
5521
 
4654
5522
  if (!root.querySelector('.dt-row')) {
4655
5523
  root.innerHTML = filterText
4656
- ? '<div class="dt-muted">No elements match your search.</div>'
4657
- : '<div class="dt-muted">No elements found.</div>';
5524
+ ? '<div class="dt-muted">No added elements match your search.</div>'
5525
+ : '<div class="dt-muted">No elements added yet. Use Components or Sections to insert.</div>';
4658
5526
  }
4659
5527
  root.onmouseleave = function() {
4660
5528
  clearTreeHoverHighlight();
4661
5529
  };
4662
5530
  }
4663
5531
 
5532
+ function domTreeLabelSuffix(el) {
5533
+ if (el.id != null && el.id !== '') return '#' + String(el.id).slice(0, 40);
5534
+ var cn = el.className && typeof el.className === 'string' ? el.className.trim() : '';
5535
+ if (cn) {
5536
+ var parts = cn.split(/s+/).filter(function(x) { return x.indexOf('vve-') !== 0; }).slice(0, 2).join('.');
5537
+ if (parts) return '.' + parts.slice(0, 56);
5538
+ }
5539
+ return '';
5540
+ }
5541
+
5542
+ function domTreeLabelFor(el) {
5543
+ return (el.tagName || '').toLowerCase() + domTreeLabelSuffix(el);
5544
+ }
5545
+
5546
+ function setDomTreeLabelContent(lbl, el) {
5547
+ var tag = (el.tagName || '').toLowerCase();
5548
+ lbl.textContent = '';
5549
+ var tagSpan = document.createElement('span');
5550
+ tagSpan.className = 'dt-tag';
5551
+ tagSpan.textContent = tag;
5552
+ lbl.appendChild(tagSpan);
5553
+ var suffix = domTreeLabelSuffix(el);
5554
+ if (suffix) lbl.appendChild(document.createTextNode(suffix));
5555
+ }
5556
+
4664
5557
  function renderDomTree(filterRaw) {
4665
5558
  var filterText = (filterRaw || '').toLowerCase().trim();
4666
5559
  var root = document.getElementById('dom-tree-root');
@@ -4678,14 +5571,7 @@ function renderDomTree(filterRaw) {
4678
5571
  }
4679
5572
 
4680
5573
  function labelFor(el) {
4681
- var tag = el.tagName.toLowerCase();
4682
- if (el.id != null && el.id !== '') return tag + '#' + String(el.id).slice(0, 40);
4683
- var cn = el.className && typeof el.className === 'string' ? el.className.trim() : '';
4684
- if (cn) {
4685
- var parts = cn.split(/s+/).filter(function(x) { return x.indexOf('vve-') !== 0; }).slice(0, 2).join('.');
4686
- if (parts) return tag + '.' + parts.slice(0, 56);
4687
- }
4688
- return tag;
5574
+ return domTreeLabelFor(el);
4689
5575
  }
4690
5576
 
4691
5577
  function skippable(el) {
@@ -4762,7 +5648,7 @@ function renderDomTree(filterRaw) {
4762
5648
 
4763
5649
  var lbl = document.createElement('div');
4764
5650
  lbl.className = 'dt-lbl';
4765
- lbl.textContent = labelFor(el);
5651
+ setDomTreeLabelContent(lbl, el);
4766
5652
  lbl.title = buildSelector(el);
4767
5653
 
4768
5654
  row.appendChild(chev);
@@ -4813,6 +5699,189 @@ function pr2(l1, i1, l2, i2) {
4813
5699
  '<div class="pr2-item"><div class="pr2-lbl">'+l2+'</div>'+i2+'</div></div>';
4814
5700
  }
4815
5701
  function subLbl(text) { return '<div class="sub-lbl">'+text+'</div>'; }
5702
+
5703
+ function isLinkElement(el) {
5704
+ if (!el || el.nodeType !== 1) return false;
5705
+ var tag = (el.tagName || '').toLowerCase();
5706
+ if (tag === 'a') return true;
5707
+ try {
5708
+ return el.getAttribute && el.getAttribute('role') === 'link';
5709
+ } catch(_) {}
5710
+ return false;
5711
+ }
5712
+
5713
+ function isFormControlElement(el) {
5714
+ if (!el || el.nodeType !== 1) return false;
5715
+ var tag = (el.tagName || '').toLowerCase();
5716
+ return tag === 'input' || tag === 'textarea' || tag === 'select';
5717
+ }
5718
+
5719
+ function getFormControlValue(el) {
5720
+ if (!isFormControlElement(el)) return '';
5721
+ try {
5722
+ return el.value != null ? String(el.value) : '';
5723
+ } catch(_) {
5724
+ return '';
5725
+ }
5726
+ }
5727
+
5728
+ function getVideoSrc(el) {
5729
+ if (!el || el.nodeType !== 1) return '';
5730
+ try {
5731
+ var source = el.querySelector && el.querySelector('source');
5732
+ if (source && source.getAttribute('src')) return source.getAttribute('src') || '';
5733
+ return el.getAttribute('src') || '';
5734
+ } catch(_) {
5735
+ return '';
5736
+ }
5737
+ }
5738
+
5739
+ function setVideoSrc(el, value) {
5740
+ if (!el || el.nodeType !== 1) return;
5741
+ var v = value == null ? '' : String(value);
5742
+ try {
5743
+ var source = el.querySelector && el.querySelector('source');
5744
+ if (source) {
5745
+ if (v) source.setAttribute('src', v);
5746
+ else source.removeAttribute('src');
5747
+ } else if (v) {
5748
+ el.setAttribute('src', v);
5749
+ } else {
5750
+ el.removeAttribute('src');
5751
+ }
5752
+ try {
5753
+ el.load();
5754
+ } catch(_) {}
5755
+ } catch(_) {}
5756
+ }
5757
+
5758
+ function isYoutubeVideoElement(el) {
5759
+ if (!el || el.nodeType !== 1) return false;
5760
+ if ((el.tagName || '').toLowerCase() !== 'iframe') return false;
5761
+ if (el.hasAttribute && el.hasAttribute('data-youtube-id')) return true;
5762
+ var src = el.getAttribute('src') || '';
5763
+ return src.indexOf('youtube.com/embed/') !== -1 || src.indexOf('youtu.be/') !== -1;
5764
+ }
5765
+
5766
+ function normalizeYoutubeVideoId(input) {
5767
+ var raw = String(input || '').trim();
5768
+ if (!raw) return '';
5769
+ var m = raw.match(new RegExp('(?:youtube\\.com/(?:embed/|watch\\?(?:.*&)?v=|shorts/|live/)|youtu\\.be/)([A-Za-z0-9_-]{11})', 'i'));
5770
+ if (m) return m[1];
5771
+ var bare = raw.match(/^([A-Za-z0-9_-]{11})$/);
5772
+ return bare ? bare[1] : raw;
5773
+ }
5774
+
5775
+ function getYoutubeVideoId(el) {
5776
+ if (!isYoutubeVideoElement(el)) return '';
5777
+ try {
5778
+ var explicit = el.getAttribute('data-youtube-id');
5779
+ if (explicit != null && String(explicit).trim()) return String(explicit).trim();
5780
+ var src = el.getAttribute('src') || '';
5781
+ var m = src.match(new RegExp('(?:youtube\\.com/embed/|youtu\\.be/)([A-Za-z0-9_-]{11})', 'i'));
5782
+ return m ? m[1] : '';
5783
+ } catch(_) {
5784
+ return '';
5785
+ }
5786
+ }
5787
+
5788
+ function setYoutubeVideoId(el, value) {
5789
+ if (!el || el.nodeType !== 1) return;
5790
+ var id = normalizeYoutubeVideoId(value);
5791
+ try {
5792
+ if (id) {
5793
+ el.setAttribute('data-youtube-id', id);
5794
+ el.setAttribute('src', 'https://www.youtube.com/embed/' + id);
5795
+ } else {
5796
+ el.removeAttribute('data-youtube-id');
5797
+ el.setAttribute('src', 'about:blank');
5798
+ }
5799
+ } catch(_) {}
5800
+ }
5801
+
5802
+ function isVimeoVideoElement(el) {
5803
+ if (!el || el.nodeType !== 1) return false;
5804
+ if ((el.tagName || '').toLowerCase() !== 'iframe') return false;
5805
+ if (el.hasAttribute && el.hasAttribute('data-vimeo-id')) return true;
5806
+ var src = el.getAttribute('src') || '';
5807
+ return src.indexOf('player.vimeo.com/video/') !== -1 || src.indexOf('vimeo.com/video/') !== -1;
5808
+ }
5809
+
5810
+ function isEmbeddedVideoIframe(el) {
5811
+ return isYoutubeVideoElement(el) || isVimeoVideoElement(el);
5812
+ }
5813
+
5814
+ function normalizeVimeoVideoId(input) {
5815
+ var raw = String(input || '').trim();
5816
+ if (!raw) return '';
5817
+ var m = raw.match(new RegExp('(?:player\\.vimeo\\.com/video/|vimeo\\.com/(?:video/)?)([0-9]+)', 'i'));
5818
+ if (m) return m[1];
5819
+ var bare = raw.match(/^([0-9]+)$/);
5820
+ return bare ? bare[1] : raw.replace(/[^d]/g, '');
5821
+ }
5822
+
5823
+ function getVimeoVideoId(el) {
5824
+ if (!isVimeoVideoElement(el)) return '';
5825
+ try {
5826
+ var explicit = el.getAttribute('data-vimeo-id');
5827
+ if (explicit != null && String(explicit).trim()) return String(explicit).trim();
5828
+ var src = el.getAttribute('src') || '';
5829
+ var m = src.match(new RegExp('(?:player\\.vimeo\\.com/video/|vimeo\\.com/video/)([0-9]+)', 'i'));
5830
+ return m ? m[1] : '';
5831
+ } catch(_) {
5832
+ return '';
5833
+ }
5834
+ }
5835
+
5836
+ function setVimeoVideoId(el, value) {
5837
+ if (!el || el.nodeType !== 1) return;
5838
+ var id = normalizeVimeoVideoId(value);
5839
+ try {
5840
+ if (id) {
5841
+ el.setAttribute('data-vimeo-id', id);
5842
+ el.setAttribute('src', 'https://player.vimeo.com/video/' + id);
5843
+ } else {
5844
+ el.removeAttribute('data-vimeo-id');
5845
+ el.setAttribute('src', 'about:blank');
5846
+ }
5847
+ } catch(_) {}
5848
+ }
5849
+
5850
+ function buildAnchorContentFieldsHtml(el) {
5851
+ return (
5852
+ subLbl('Link attributes') +
5853
+ pr('Href', '<input class="pr-inp" id="pp-href" type="text" value="'+esc(el.getAttribute('href')||'')+'" placeholder="https:// or #section">') +
5854
+ pr2(
5855
+ 'Target',
5856
+ '<select class="pr-inp" id="pp-target">'+selOpts(['','_self','_blank','_parent','_top'],el.getAttribute('target')||'')+'</select>',
5857
+ 'Rel',
5858
+ '<input class="pr-inp" id="pp-rel" type="text" value="'+esc(el.getAttribute('rel')||'')+'" placeholder="noopener noreferrer">'
5859
+ ) +
5860
+ pr2(
5861
+ 'Download',
5862
+ '<input class="pr-inp" id="pp-download" type="text" value="'+esc(el.getAttribute('download')||'')+'" placeholder="filename">',
5863
+ 'Title',
5864
+ '<input class="pr-inp" id="pp-link-title" type="text" value="'+esc(el.getAttribute('title')||'')+'">'
5865
+ ) +
5866
+ pr2(
5867
+ 'Hreflang',
5868
+ '<input class="pr-inp" id="pp-hreflang" type="text" value="'+esc(el.getAttribute('hreflang')||'')+'" placeholder="en">',
5869
+ 'Type',
5870
+ '<input class="pr-inp" id="pp-link-type" type="text" value="'+esc(el.getAttribute('type')||'')+'" placeholder="MIME type">'
5871
+ ) +
5872
+ pr(
5873
+ 'Referrer policy',
5874
+ '<select class="pr-inp" id="pp-referrerpolicy">'+selOpts(['','no-referrer','no-referrer-when-downgrade','origin','origin-when-cross-origin','same-origin','strict-origin','strict-origin-when-cross-origin','unsafe-url'],el.getAttribute('referrerpolicy')||'')+'</select>'
5875
+ )
5876
+ );
5877
+ }
5878
+
5879
+ function setOptionalAnchorAttr(el, name, value) {
5880
+ if (!el || !name) return;
5881
+ var v = value == null ? '' : String(value).trim();
5882
+ if (v) el.setAttribute(name, v);
5883
+ else el.removeAttribute(name);
5884
+ }
4816
5885
  function openCustomCssModal() {
4817
5886
  var modal = document.getElementById('custom-css-modal');
4818
5887
  var ta = document.getElementById('custom-css-modal-textarea');
@@ -5038,7 +6107,7 @@ function renderRightPanel(el) {
5038
6107
 
5039
6108
  // \u2500\u2500 Typography \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
5040
6109
  document.getElementById('acc-body-typography').innerHTML =
5041
- pr('Color', '<input type="color" class="pr-color" id="pp-color" value="'+esc(rgbToHex(cs.color))+'"><span style="font-size:11px;color:var(--text-3);flex:1;font-family:monospace">'+esc(cs.color)+'</span>') +
6110
+ pr('Color', '<input type="color" class="pr-color" id="pp-color" value="'+esc(rgbToHex(cs.color))+'"><span style="font-size:11px;color:var(--text-3);flex:1;font-family:var(--font-mono)">'+esc(cs.color)+'</span>') +
5042
6111
  pr2('Font size', '<input class="pr-inp" type="number" id="pp-fs" min="8" max="200" value="'+parseInt(cs.fontSize||'16')+'">',
5043
6112
  'Font weight', '<select class="pr-inp" id="pp-fw">'+weightOpts(cs.fontWeight)+'</select>') +
5044
6113
  pr('Font family', '<input class="pr-inp" id="pp-ff" type="text" value="'+esc(el.style.fontFamily||'')+'" placeholder="inherit">') +
@@ -5050,7 +6119,7 @@ function renderRightPanel(el) {
5050
6119
  // \u2500\u2500 Background \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
5051
6120
  var bgiVal = (el.style.backgroundImage||'').replace(/url(['"]?([^'"]+)['"]?)/,'$1');
5052
6121
  document.getElementById('acc-body-background').innerHTML =
5053
- pr('Color', '<input type="color" class="pr-color" id="pp-bg" value="'+esc(rgbToHex(cs.backgroundColor))+'"><span style="font-size:11px;color:var(--text-3);flex:1;font-family:monospace">'+esc(cs.backgroundColor)+'</span>') +
6122
+ pr('Color', '<input type="color" class="pr-color" id="pp-bg" value="'+esc(rgbToHex(cs.backgroundColor))+'"><span style="font-size:11px;color:var(--text-3);flex:1;font-family:var(--font-mono)">'+esc(cs.backgroundColor)+'</span>') +
5054
6123
  pr('Image URL', '<input class="pr-inp" id="pp-bgi" type="url" value="'+esc(bgiVal)+'" placeholder="https://\u2026">') +
5055
6124
  pr2('Size', '<select class="pr-inp" id="pp-bgs">'+selOpts(['auto','cover','contain'],el.style.backgroundSize||'auto')+'</select>',
5056
6125
  'Repeat', '<select class="pr-inp" id="pp-bgr">'+selOpts(['repeat','no-repeat','repeat-x','repeat-y'],el.style.backgroundRepeat||'repeat')+'</select>');
@@ -5120,9 +6189,9 @@ function renderRightPanel(el) {
5120
6189
  // \u2500\u2500 Device styling rules \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
5121
6190
  document.getElementById('acc-body-device').innerHTML =
5122
6191
  subLbl('Mobile (\u2264768px)') +
5123
- '<textarea class="pr-inp" id="pp-mob-css" style="width:100%;min-height:60px;font-family:monospace;font-size:11px;margin-bottom:8px" placeholder="/* styles for mobile */">'+esc(el.dataset.mobileCss||'')+'</textarea>' +
6192
+ '<textarea class="pr-inp" id="pp-mob-css" style="width:100%;min-height:60px;font-family:var(--font-mono);font-size:11px;margin-bottom:8px" placeholder="/* styles for mobile */">'+esc(el.dataset.mobileCss||'')+'</textarea>' +
5124
6193
  subLbl('Tablet (\u22641024px)') +
5125
- '<textarea class="pr-inp" id="pp-tab-css" style="width:100%;min-height:60px;font-family:monospace;font-size:11px" placeholder="/* styles for tablet */">'+esc(el.dataset.tabletCss||'')+'</textarea>';
6194
+ '<textarea class="pr-inp" id="pp-tab-css" style="width:100%;min-height:60px;font-family:var(--font-mono);font-size:11px" placeholder="/* styles for tablet */">'+esc(el.dataset.tabletCss||'')+'</textarea>';
5126
6195
 
5127
6196
  // \u2500\u2500 CSS and Classes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
5128
6197
  document.getElementById('acc-body-css').innerHTML =
@@ -5133,27 +6202,54 @@ function renderRightPanel(el) {
5133
6202
  '<i class="bi bi-fullscreen"></i>' +
5134
6203
  '</button>' +
5135
6204
  '</div>' +
5136
- '<textarea class="pr-inp" id="pp-css" style="width:100%;min-height:80px;font-family:monospace;font-size:11px" placeholder="color: red; font-size: 16px;">'+esc(el.getAttribute('style')||'')+'</textarea>';
6205
+ '<textarea class="pr-inp" id="pp-css" style="width:100%;min-height:80px;font-family:var(--font-mono);font-size:11px" placeholder="color: red; font-size: 16px;">'+esc(el.getAttribute('style')||'')+'</textarea>';
5137
6206
 
5138
6207
  // \u2500\u2500 Attributes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
5139
6208
  var attrHtml =
5140
6209
  pr('ID', '<input class="pr-inp" id="pp-id" type="text" value="'+esc(el.id||'')+'" placeholder="element-id">');
5141
- if (tag==='a') attrHtml +=
5142
- pr('Href', '<input class="pr-inp" id="pp-href" type="url" value="'+esc(el.getAttribute('href')||'')+'" placeholder="https://">') +
5143
- pr('Target', '<select class="pr-inp" id="pp-target">'+selOpts(['','_blank','_self','_parent'],el.getAttribute('target')||'')+'</select>');
5144
6210
  if (tag==='img') attrHtml +=
5145
6211
  pr('Src', '<input class="pr-inp" id="pp-src" type="url" value="'+esc(el.getAttribute('src')||'')+'">') +
5146
6212
  pr('Alt', '<input class="pr-inp" id="pp-alt" type="text" value="'+esc(el.getAttribute('alt')||'')+'">');
5147
- if (tag==='input'||tag==='textarea') attrHtml +=
6213
+ if (tag==='textarea') attrHtml +=
5148
6214
  pr('Placeholder', '<input class="pr-inp" id="pp-ph" type="text" value="'+esc(el.getAttribute('placeholder')||'')+'">');
5149
6215
  document.getElementById('acc-body-attributes').innerHTML = attrHtml;
5150
6216
 
5151
6217
  // \u2500\u2500 HTML Content \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
5152
- document.getElementById('acc-body-content').innerHTML =
5153
- subLbl('Inner Text') +
5154
- '<textarea class="pr-inp" id="pp-text" style="width:100%;min-height:60px;margin-bottom:8px">'+esc(el.innerText||'')+'</textarea>' +
5155
- subLbl('Inner HTML') +
5156
- '<textarea class="pr-inp" id="pp-html" style="width:100%;min-height:70px;font-family:monospace;font-size:11px">'+esc(el.innerHTML||'')+'</textarea>';
6218
+ var contentHtml = '';
6219
+ if (isLinkElement(el)) contentHtml += buildAnchorContentFieldsHtml(el);
6220
+ if (isFormControlElement(el)) {
6221
+ contentHtml +=
6222
+ subLbl('Value') +
6223
+ '<textarea class="pr-inp" id="pp-value" style="width:100%;min-height:44px;margin-bottom:8px">'+esc(getFormControlValue(el))+'</textarea>';
6224
+ }
6225
+ if (tag==='input') {
6226
+ contentHtml +=
6227
+ subLbl('Placeholder') +
6228
+ '<input class="pr-inp" id="pp-ph" type="text" value="'+esc(el.getAttribute('placeholder')||'')+'" style="width:100%;margin-bottom:8px">';
6229
+ }
6230
+ if (tag==='video') {
6231
+ contentHtml +=
6232
+ subLbl('Src') +
6233
+ '<input class="pr-inp" id="pp-video-src" type="url" value="'+esc(getVideoSrc(el))+'" placeholder="https://example.com/video.mp4" style="width:100%;margin-bottom:8px">';
6234
+ }
6235
+ if (isYoutubeVideoElement(el)) {
6236
+ contentHtml +=
6237
+ subLbl('YouTube video ID') +
6238
+ '<input class="pr-inp" id="pp-youtube-id" type="text" value="'+esc(getYoutubeVideoId(el))+'" placeholder="dQw4w9WgXcQ" style="width:100%;margin-bottom:8px">';
6239
+ }
6240
+ if (isVimeoVideoElement(el)) {
6241
+ contentHtml +=
6242
+ subLbl('Vimeo video ID') +
6243
+ '<input class="pr-inp" id="pp-vimeo-id" type="text" value="'+esc(getVimeoVideoId(el))+'" placeholder="76979871" style="width:100%;margin-bottom:8px">';
6244
+ }
6245
+ if (tag!=='input' && tag!=='video' && !isEmbeddedVideoIframe(el)) {
6246
+ contentHtml +=
6247
+ subLbl('Inner Text') +
6248
+ '<textarea class="pr-inp" id="pp-text" style="width:100%;min-height:60px;margin-bottom:8px">'+esc(el.innerText||'')+'</textarea>' +
6249
+ subLbl('Inner HTML') +
6250
+ '<textarea class="pr-inp" id="pp-html" style="width:100%;min-height:70px;font-family:var(--font-mono);font-size:11px">'+esc(el.innerHTML||'')+'</textarea>';
6251
+ }
6252
+ document.getElementById('acc-body-content').innerHTML = contentHtml;
5157
6253
 
5158
6254
  // \u2500\u2500 Wire up all inputs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
5159
6255
  var bindings = [
@@ -5204,13 +6300,20 @@ function renderRightPanel(el) {
5204
6300
  ['pp-cls', function(v){el.className=v}],
5205
6301
  ['pp-css', function(v){el.setAttribute('style',v)}],
5206
6302
  ['pp-id', function(v){el.id=v}],
5207
- ['pp-href', function(v){el.setAttribute('href',v)}],
5208
- ['pp-target', function(v){el.setAttribute('target',v)}],
6303
+ ['pp-href', function(v){setOptionalAnchorAttr(el,'href',v)}],
6304
+ ['pp-target', function(v){setOptionalAnchorAttr(el,'target',v)}],
6305
+ ['pp-rel', function(v){setOptionalAnchorAttr(el,'rel',v)}],
6306
+ ['pp-download', function(v){setOptionalAnchorAttr(el,'download',v)}],
6307
+ ['pp-link-title', function(v){setOptionalAnchorAttr(el,'title',v)}],
6308
+ ['pp-hreflang', function(v){setOptionalAnchorAttr(el,'hreflang',v)}],
6309
+ ['pp-link-type', function(v){setOptionalAnchorAttr(el,'type',v)}],
6310
+ ['pp-referrerpolicy', function(v){setOptionalAnchorAttr(el,'referrerpolicy',v)}],
5209
6311
  ['pp-src', function(v){el.setAttribute('src',v)}],
6312
+ ['pp-video-src', function(v){setVideoSrc(el, v)}],
6313
+ ['pp-youtube-id', function(v){setYoutubeVideoId(el, v)}],
6314
+ ['pp-vimeo-id', function(v){setVimeoVideoId(el, v)}],
5210
6315
  ['pp-alt', function(v){el.setAttribute('alt',v)}],
5211
6316
  ['pp-ph', function(v){el.setAttribute('placeholder',v)}],
5212
- ['pp-text', function(v){el.innerText=v}],
5213
- ['pp-html', function(v){el.innerHTML=v}],
5214
6317
  ];
5215
6318
  var sel = buildSelector(el);
5216
6319
  bindings.forEach(function(b){
@@ -5235,6 +6338,45 @@ function renderRightPanel(el) {
5235
6338
  inp.addEventListener('change', onValueChange);
5236
6339
  }
5237
6340
  });
6341
+ wireContentFieldSync(el, sel);
6342
+ }
6343
+
6344
+ function wireContentFieldSync(el, sel) {
6345
+ var textInp = document.getElementById('pp-text');
6346
+ var htmlInp = document.getElementById('pp-html');
6347
+ var valueInp = document.getElementById('pp-value');
6348
+ var syncing = false;
6349
+ function applyContentChange(changedId) {
6350
+ if (syncing) return;
6351
+ syncing = true;
6352
+ try {
6353
+ var orig = getOriginalValue(changedId, el);
6354
+ if (changedId === 'pp-value') {
6355
+ el.value = valueInp.value;
6356
+ logChange(sel, 'pp-value', valueInp.value, el, orig);
6357
+ } else if (changedId === 'pp-text') {
6358
+ el.innerText = textInp.value;
6359
+ htmlInp.value = el.innerHTML;
6360
+ logChange(sel, 'pp-text', textInp.value, el, orig);
6361
+ } else {
6362
+ el.innerHTML = htmlInp.value;
6363
+ textInp.value = el.innerText;
6364
+ logChange(sel, 'pp-html', htmlInp.value, el, orig);
6365
+ }
6366
+ } finally {
6367
+ syncing = false;
6368
+ }
6369
+ }
6370
+ if (valueInp && isFormControlElement(el)) {
6371
+ valueInp.addEventListener('input', function() { applyContentChange('pp-value'); });
6372
+ valueInp.addEventListener('change', function() { applyContentChange('pp-value'); });
6373
+ }
6374
+ if (textInp && htmlInp) {
6375
+ textInp.addEventListener('input', function() { applyContentChange('pp-text'); });
6376
+ textInp.addEventListener('change', function() { applyContentChange('pp-text'); });
6377
+ htmlInp.addEventListener('input', function() { applyContentChange('pp-html'); });
6378
+ htmlInp.addEventListener('change', function() { applyContentChange('pp-html'); });
6379
+ }
5238
6380
  }
5239
6381
 
5240
6382
  // \u2500\u2500 Selector helper \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
@@ -5455,6 +6597,100 @@ function repositionDragSibling(dragEl, clientY) {
5455
6597
  }
5456
6598
  }
5457
6599
 
6600
+ function isMoveSwapSkippableSibling(el) {
6601
+ if (!el || el.nodeType !== 1) return true;
6602
+ if (isDomTreeSkippableTagName(el.tagName)) return true;
6603
+ try {
6604
+ if (el.hasAttribute && el.hasAttribute('hidden')) return true;
6605
+ if (el.getAttribute && el.getAttribute('data-vve-hidden') === '1') return true;
6606
+ } catch(_) {}
6607
+ try {
6608
+ if (el.style && el.style.display === 'none') return true;
6609
+ if (el.style && el.style.visibility === 'hidden') return true;
6610
+ } catch(_) {}
6611
+ try {
6612
+ var view = el.ownerDocument && el.ownerDocument.defaultView;
6613
+ var cs = view && view.getComputedStyle ? view.getComputedStyle(el) : null;
6614
+ if (cs && (cs.display === 'none' || cs.visibility === 'hidden')) return true;
6615
+ } catch(_) {}
6616
+ return false;
6617
+ }
6618
+
6619
+ function findMoveSwapSibling(el, direction) {
6620
+ if (!el) return null;
6621
+ var node = direction < 0 ? el.previousElementSibling : el.nextElementSibling;
6622
+ while (node && isMoveSwapSkippableSibling(node)) {
6623
+ node = direction < 0 ? node.previousElementSibling : node.nextElementSibling;
6624
+ }
6625
+ return node;
6626
+ }
6627
+
6628
+ function isMoveParentEscapeBlocked(parent) {
6629
+ if (!parent || parent.nodeType !== 1) return true;
6630
+ var tag = (parent.tagName || '').toLowerCase();
6631
+ if (tag === 'html' || tag === 'body') return true;
6632
+ try {
6633
+ var doc = parent.ownerDocument;
6634
+ if (doc && (parent === doc.documentElement || parent === doc.body)) return true;
6635
+ } catch(_) {}
6636
+ return false;
6637
+ }
6638
+
6639
+ /** When no visual sibling exists, lift the node to the grandparent (before/after its parent). */
6640
+ function canMoveEscapeToParent(el, direction) {
6641
+ if (!el || !el.parentElement) return false;
6642
+ var parent = el.parentElement;
6643
+ if (isMoveParentEscapeBlocked(parent) || !parent.parentElement) return false;
6644
+ if (findMoveSwapSibling(el, direction)) return false;
6645
+ return true;
6646
+ }
6647
+
6648
+ function canMoveElDirection(el, direction) {
6649
+ if (!el || !el.parentElement) return false;
6650
+ return !!findMoveSwapSibling(el, direction) || canMoveEscapeToParent(el, direction);
6651
+ }
6652
+
6653
+ function moveElByDirection(el, direction) {
6654
+ if (!el || !el.parentElement) return false;
6655
+ var parent = el.parentElement;
6656
+ var sibling = findMoveSwapSibling(el, direction);
6657
+ if (sibling) {
6658
+ if (direction < 0) parent.insertBefore(el, sibling);
6659
+ else parent.insertBefore(sibling, el);
6660
+ return true;
6661
+ }
6662
+ if (!canMoveEscapeToParent(el, direction)) return false;
6663
+ var grandparent = parent.parentElement;
6664
+ if (!grandparent) return false;
6665
+ if (direction < 0) grandparent.insertBefore(el, parent);
6666
+ else grandparent.insertBefore(el, parent.nextSibling);
6667
+ return true;
6668
+ }
6669
+
6670
+ function syncMoveFloaterButtons(el) {
6671
+ var upBtn = document.getElementById('sf-move-up');
6672
+ var downBtn = document.getElementById('sf-move-down');
6673
+ if (!upBtn || !downBtn) return;
6674
+ var canUp = canMoveElDirection(el, -1);
6675
+ var canDown = canMoveElDirection(el, 1);
6676
+ upBtn.disabled = !canUp;
6677
+ downBtn.disabled = !canDown;
6678
+ if (canUp) {
6679
+ upBtn.title = findMoveSwapSibling(el, -1)
6680
+ ? 'Move up'
6681
+ : 'Move up (out of container)';
6682
+ } else {
6683
+ upBtn.title = 'Cannot move up';
6684
+ }
6685
+ if (canDown) {
6686
+ downBtn.title = findMoveSwapSibling(el, 1)
6687
+ ? 'Move down'
6688
+ : 'Move down (out of container)';
6689
+ } else {
6690
+ downBtn.title = 'Cannot move down';
6691
+ }
6692
+ }
6693
+
5458
6694
  function recordReorderAfterDrag(movedEl) {
5459
6695
  if (!activeVarId || !movedEl || !movedEl.parentElement) return;
5460
6696
  var prev = movedEl.previousElementSibling;
@@ -5483,13 +6719,11 @@ function recordReorderAfterDrag(movedEl) {
5483
6719
  }
5484
6720
 
5485
6721
  function moveSelectedElByDirection(direction) {
5486
- if (!selectedEl || !selectedEl.parentElement) return;
5487
- var p = selectedEl.parentElement;
5488
- var sibling = direction < 0 ? selectedEl.previousElementSibling : selectedEl.nextElementSibling;
5489
- if (!sibling) return;
5490
- if (direction < 0) p.insertBefore(selectedEl, sibling);
5491
- else p.insertBefore(sibling, selectedEl);
5492
- recordReorderAfterDrag(selectedEl);
6722
+ var el = recoverSelectedElement(false) || selectedEl;
6723
+ if (!el || !el.parentElement) return;
6724
+ selectedEl = el;
6725
+ if (!moveElByDirection(el, direction)) return;
6726
+ recordReorderAfterDrag(el);
5493
6727
  saveCurrentVariationHtml();
5494
6728
  recomputeEditorDirty();
5495
6729
  scheduleDomTreeRefresh();
@@ -5589,6 +6823,7 @@ function attachClickHandler() {
5589
6823
  deselectElement(); return;
5590
6824
  }
5591
6825
  selectElement(target);
6826
+ if (currentMainTab !== 'design') switchMainTab('design');
5592
6827
  }, true);
5593
6828
  } catch(_) {}
5594
6829
  }
@@ -5693,15 +6928,18 @@ function syncIframeInteractions(reason) {
5693
6928
  // \u2500\u2500 HTML insertion (Components / Sections) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
5694
6929
  /** Full snippets for Vvveb keys whose html field is placeholder text, not markup. */
5695
6930
  var VVVEB_INSERT_HTML_OVERRIDES = {
5696
- 'html/gridrow': '<div class="row g-3"><div class="col-sm-4"><p>Column 1</p></div><div class="col-sm-4"><p>Column 2</p></div><div class="col-sm-4"><p>Column 3</p></div></div>',
5697
- 'html/gridcolumn': '<div class="col-sm-6"><p>New column</p></div>',
5698
- 'html/container': '<div class="container py-3"><p>Container</p></div>',
5699
- 'html/btn-link': '<a class="btn btn-primary" href="#">Primary button</a>',
5700
- 'html/btn': '<button type="button" class="btn btn-primary">Primary</button>',
5701
- 'html/pageitem': '<li class="page-item"><a class="page-link" href="#">1</a></li>',
5702
- 'html/breadcrumbitem': '<li class="breadcrumb-item"><a href="#">Item</a></li>',
5703
- 'html/listitem': '<li class="list-group-item">List item</li>',
5704
- 'html/tablebody': '<tbody><tr><td>Cell</td></tr></tbody>',
6931
+ 'html/gridrow': '<div style="display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px;width:100%;box-sizing:border-box"><div style="padding:16px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px"><p style="margin:0">Column 1</p></div><div style="padding:16px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px"><p style="margin:0">Column 2</p></div><div style="padding:16px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px"><p style="margin:0">Column 3</p></div></div>',
6932
+ 'html/gridcolumn': '<div style="padding:16px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;min-height:60px;box-sizing:border-box"><p style="margin:0">New column</p></div>',
6933
+ 'html/container': '<div style="width:100%;max-width:960px;margin:0 auto;padding:16px;box-sizing:border-box"><p style="margin:0">Container</p></div>',
6934
+ 'html/btn-link': '<a href="#" style="display:inline-block;padding:10px 20px;font-size:14px;font-weight:600;line-height:1.2;color:#fff;background:#2563eb;border-radius:6px;text-decoration:none">Primary button</a>',
6935
+ 'html/btn': '<button type="button" style="display:inline-block;padding:10px 20px;font-size:14px;font-weight:600;line-height:1.2;color:#fff;background:#2563eb;border:none;border-radius:6px;cursor:pointer">Primary</button>',
6936
+ 'html/badge': '<span style="display:inline-flex;align-items:center;padding:4px 10px;font-size:12px;font-weight:600;line-height:1;letter-spacing:.02em;color:#fff;background:#2563eb;border-radius:999px">Badge</span>',
6937
+ 'html/alert': '<div role="alert" style="padding:12px 16px;font-size:14px;line-height:1.5;color:#0c4a6e;background:#e0f2fe;border:1px solid #7dd3fc;border-radius:6px">Alert message</div>',
6938
+ 'html/card': '<div style="border:1px solid #e2e8f0;border-radius:8px;background:#fff;overflow:hidden;max-width:360px;box-shadow:0 1px 3px rgba(0,0,0,.08)"><div style="padding:20px"><h5 style="margin:0 0 8px;font-size:18px;font-weight:700;color:#0f172a">Card title</h5><p style="margin:0;font-size:14px;line-height:1.5;color:#475569">Card content</p></div></div>',
6939
+ 'html/pageitem': '<li style="display:inline-block;margin:0 4px"><a href="#" style="display:inline-block;padding:6px 12px;font-size:14px;color:#2563eb;border:1px solid #cbd5e1;border-radius:6px;text-decoration:none">1</a></li>',
6940
+ 'html/breadcrumbitem': '<li style="display:inline;font-size:14px;color:#64748b"><a href="#" style="color:#2563eb;text-decoration:none">Item</a></li>',
6941
+ 'html/listitem': '<li style="padding:10px 14px;font-size:14px;color:#334155;border:1px solid #e2e8f0;border-radius:6px;background:#fff">List item</li>',
6942
+ 'html/tablebody': '<tbody><tr><td style="padding:10px 12px;border:1px solid #e2e8f0;font-size:14px">Cell</td></tr></tbody>',
5705
6943
  };
5706
6944
 
5707
6945
  function buildHtmlFromVvvebComponent(comp, typeKey) {
@@ -5729,7 +6967,7 @@ function insertVvvebComponent(typeKey) {
5729
6967
  insertHtml(html);
5730
6968
  }
5731
6969
 
5732
- function insertHtml(html) {
6970
+ function insertHtml(html, options) {
5733
6971
  if (!html) return;
5734
6972
  try {
5735
6973
  var iframe = document.getElementById('iframeId');
@@ -5757,7 +6995,17 @@ function insertHtml(html) {
5757
6995
  } else {
5758
6996
  doc.body.appendChild(frag);
5759
6997
  }
6998
+ if (firstEl) {
6999
+ try {
7000
+ firstEl.setAttribute('data-vve-instance', generateVveInstanceId());
7001
+ } catch(_) {}
7002
+ htmlStr = firstEl.outerHTML;
7003
+ }
5760
7004
  if (firstEl) selectElement(firstEl);
7005
+ var accSection = options && options.defaultAccSection;
7006
+ if (accSection) {
7007
+ requestAnimationFrame(function() { focusDesignAccordionSection(accSection); });
7008
+ }
5761
7009
  if (activeVarId) {
5762
7010
  var insertRow = {
5763
7011
  selector: anchorSel,
@@ -5789,7 +7037,7 @@ function renderSidebar(filter) {
5789
7037
  baseFiltered.forEach(function(c) {
5790
7038
  var item = document.createElement('div'); item.className = 'cg-item'; item.title = 'Insert ' + c.name;
5791
7039
  item.innerHTML = '<div class="cg-icon"><i class="bi ' + c.icon + '"></i></div><div class="cg-name">' + c.name + '</div>';
5792
- item.onclick = function() { insertHtml(c.html); };
7040
+ item.onclick = function() { insertHtml(c.html, { defaultAccSection: c.defaultAccSection }); };
5793
7041
  g1.appendChild(item);
5794
7042
  });
5795
7043
  compTab.appendChild(g1);
@@ -5829,7 +7077,7 @@ function renderSidebar(filter) {
5829
7077
  var ch = document.createElement('div'); ch.className = 'cg-hdr'; ch.textContent = 'CRO Components'; secTab.appendChild(ch);
5830
7078
  croFiltered.forEach(function(sec) {
5831
7079
  var item = document.createElement('div'); item.className = 'sec-item';
5832
- item.innerHTML = '<div class="sec-thumb">'+(sec.icon||'\u{1F4E6}')+'</div><div class="sec-info"><div class="sec-name">'+sec.name+'</div>'+(sec.desc?'<div class="sec-desc">'+sec.desc+'</div>':'')+'</div>';
7080
+ item.innerHTML = '<div class="sec-thumb">'+renderCroSectionThumb(sec)+'</div><div class="sec-info"><div class="sec-name">'+sec.name+'</div>'+(sec.desc?'<div class="sec-desc">'+sec.desc+'</div>':'')+'</div>';
5833
7081
  item.onclick = function() { insertHtml(sec.html); };
5834
7082
  secTab.appendChild(item);
5835
7083
  });
@@ -5859,11 +7107,7 @@ document.getElementById('comp-search').addEventListener('input', function() {
5859
7107
  });
5860
7108
  var btnAddElement = document.getElementById('btn-add-element');
5861
7109
  if (btnAddElement) {
5862
- btnAddElement.addEventListener('click', function(e) {
5863
- e.preventDefault();
5864
- e.stopPropagation();
5865
- toggleSectionComponentsPanel();
5866
- });
7110
+ btnAddElement.addEventListener('click', handleAddElementClick);
5867
7111
  }
5868
7112
 
5869
7113
  // \u2500\u2500 Save / Close \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
@@ -6068,13 +7312,19 @@ function bindLoadingTooltipPositioning() {
6068
7312
  // \u2500\u2500 Init \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
6069
7313
  function registerCROSections() {
6070
7314
  if (typeof Vvveb === 'undefined' || !Vvveb.Sections) return;
6071
- CRO_SECTIONS.forEach(function(sec) { Vvveb.Sections.add(sec.key, { name: sec.name, image: '', html: sec.html }); });
7315
+ CRO_SECTIONS.forEach(function(sec) {
7316
+ Vvveb.Sections.add(sec.key, {
7317
+ name: sec.name,
7318
+ image: sec.image ? (CRO_SECTION_IMAGE_ROUTE + sec.image) : '',
7319
+ html: sec.html,
7320
+ });
7321
+ });
6072
7322
  }
6073
7323
 
6074
7324
  window.addEventListener('load', function() {
6075
7325
  registerCROSections();
6076
7326
  bindViewportControls();
6077
- switchSectionComponentsTab(currentSectionComponentsTab);
7327
+ // switchSectionComponentsTab(currentSectionComponentsTab);
6078
7328
  renderElementsTree(document.getElementById('comp-search').value);
6079
7329
  vvvebReady = true;
6080
7330
  bindLoadingTooltipPositioning();
@@ -6147,6 +7397,10 @@ window.addEventListener('load', function() {
6147
7397
  syncIframeInteractions('iframe-load');
6148
7398
  });
6149
7399
 
7400
+ var sfAdd = document.getElementById('sf-add');
7401
+ if (sfAdd) {
7402
+ sfAdd.addEventListener('click', handleAddElementClick);
7403
+ }
6150
7404
  var sfMoveUp = document.getElementById('sf-move-up');
6151
7405
  if (sfMoveUp) {
6152
7406
  sfMoveUp.addEventListener('click', function(e) {
@@ -6246,6 +7500,20 @@ function createVisualEditorMiddleware(options) {
6246
7500
  res.end(buildVvvebEditorHtml());
6247
7501
  return;
6248
7502
  }
7503
+ if (pathname.startsWith(CRO_SECTION_IMAGE_ROUTE)) {
7504
+ const filename = pathname.slice(CRO_SECTION_IMAGE_ROUTE.length);
7505
+ const filePath = resolveCroSectionImagePath(filename);
7506
+ if (!filePath) {
7507
+ res.statusCode = 404;
7508
+ res.end("Not found");
7509
+ return;
7510
+ }
7511
+ res.setHeader("Content-Type", "image/png");
7512
+ res.setHeader("Cache-Control", "public, max-age=86400");
7513
+ setFrameHeaders(req, res);
7514
+ res.end(fs.readFileSync(filePath));
7515
+ return;
7516
+ }
6249
7517
  if (pathname === "/api/generate-test" && enableGenerateTestApi) {
6250
7518
  if (req.method === "OPTIONS") {
6251
7519
  res.setHeader("Access-Control-Allow-Origin", "*");
@@ -6384,7 +7652,6 @@ function createVisualEditorMiddleware(options) {
6384
7652
  const trackingMarkersForRequest = mergeTrackingMarkers(
6385
7653
  extraTrackingMarkersForRequest
6386
7654
  );
6387
- console.log("trackingMarkersForRequest", trackingMarkersForRequest);
6388
7655
  const strictFreezeParam = (url.searchParams.get("strictObserverFreeze") || "").toLowerCase();
6389
7656
  const strictObserverFreezeForRequest = strictFreezeParam === "1" || strictFreezeParam === "true" || strictFreezeParam === "yes" ? true : strictFreezeParam === "0" || strictFreezeParam === "false" || strictFreezeParam === "no" ? false : strictObserverFreeze;
6390
7657
  if (!targetUrl) {
@@ -7085,6 +8352,13 @@ function visualEditorProxyPlugin(options) {
7085
8352
  generateBundle() {
7086
8353
  this.emitFile({ type: "asset", fileName: "bridge.js", source: BRIDGE_SCRIPT });
7087
8354
  this.emitFile({ type: "asset", fileName: "vvveb-editor/index.html", source: buildVvvebEditorHtml() });
8355
+ for (const imagePath of listCroSectionImageFiles()) {
8356
+ this.emitFile({
8357
+ type: "asset",
8358
+ fileName: path.join("images", "cro-sections", path.basename(imagePath)),
8359
+ source: fs.readFileSync(imagePath)
8360
+ });
8361
+ }
7088
8362
  },
7089
8363
  configureServer(server) {
7090
8364
  server.middlewares.use(mw);