@accelerated-agency/visual-editor 0.5.7 → 0.5.9

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.
Files changed (3) hide show
  1. package/dist/vite.cjs +555 -83
  2. package/dist/vite.js +555 -83
  3. package/package.json +5 -1
package/dist/vite.js CHANGED
@@ -407,7 +407,7 @@ html,body{height:100%;overflow:hidden;font-family:var(--font-sans);font-size:13p
407
407
  overflow:visible
408
408
  }
409
409
  /* Toolbar layout sections */
410
- .tb-left{display:flex;align-items:center;gap:0;flex-shrink:0}
410
+ .tb-left{display:flex;align-items:center;gap:0;flex:1;min-width:0;max-width:min(560px,46vw);margin-right:8px}
411
411
  .tb-center{display:flex;align-items:center;gap:4px;flex:1;justify-content:center}
412
412
  .tb-right{display:flex;align-items:center;gap:6px;flex-shrink:0;margin-left:auto}
413
413
  /* Logo */
@@ -429,7 +429,7 @@ html,body{height:100%;overflow:hidden;font-family:var(--font-sans);font-size:13p
429
429
  /* Device toggle buttons */
430
430
  .tb-dev-wrap{position:relative;display:flex;align-items:center}
431
431
  .tb-dev-btns{display:flex;align-items:center;gap:1px;padding:8px 4px;background:#F0F0F0;border-radius:7px; height: 30px;width: 116px;}
432
- .tb-dev-3btns{display:flex;align-items:center;gap:2px;padding:8px 4px;background:#F0F0F0;border-radius:7px; height: 30px;width: 100px;}
432
+ .tb-dev-3btns{display:flex;align-items:center;gap:2px;padding:8px 4px;background:#F0F0F0;border-radius:7px; height: 30px;width: auto;}
433
433
  .tb-dev-menu{
434
434
  position:absolute;top:calc(100% + 8px);right:0;z-index:12040;display:none;
435
435
  width:264px;background:#fff;border:1px solid #e5e7eb;border-radius:8px;padding:4px 4px;
@@ -533,7 +533,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
533
533
  }
534
534
  .tb-dev-menu .vp-preset-btn:hover,.tb-dev-menu .vp-preset-btn.active{background:#f4f4f5}
535
535
  /* Dark icon buttons */
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}
536
+ .tb-dk-btn{width:28px;height:25px;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}
537
537
  .tb-dk-btn:hover{color:#e4e4e7;background:rgba(255,255,255,.07)}
538
538
  .tb-dk-btn.active{background:#FFFFFF}
539
539
  /* Dark separator */
@@ -700,6 +700,15 @@ box-shadow: 0 0 1px 0 rgba(0, 0, 0, 0.20), 0 1px 2px 0 rgba(0, 0, 0, 0.05), 0 1p
700
700
  background: var(--bg-primary-default, #262626)!important;
701
701
  color: var(--text-primary-default, #FFF)!important;
702
702
  }
703
+ #dom-tree-root .dt-row[draggable="true"]{cursor:grab}
704
+ #dom-tree-root .dt-row.dt-dragging{opacity:.55;cursor:grabbing}
705
+ #dom-tree-root .dt-row.dt-drop-before::after,
706
+ #dom-tree-root .dt-row.dt-drop-after::after{
707
+ content:'';position:absolute;left:8px;right:8px;height:2px;background:#6366f1;pointer-events:none;z-index:1
708
+ }
709
+ #dom-tree-root .dt-row.dt-drop-before::after{top:0}
710
+ #dom-tree-root .dt-row.dt-drop-after::after{bottom:0}
711
+ #dom-tree-root .dt-row.dt-drop-invalid{cursor:not-allowed;opacity:.45}
703
712
  .dt-chev{
704
713
  width:16px;height:16px;flex-shrink:0;border:none;background:transparent;
705
714
  cursor:pointer;color:var(--text-3);display:flex;align-items:center;justify-content:center;
@@ -748,6 +757,21 @@ flex: 1;
748
757
  }
749
758
  #no-url .nu-icon{font-size:48px;opacity:.3}
750
759
 
760
+ /* \u2500\u2500 Iframe load error overlay \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
761
+ #iframe-load-error{
762
+ display:none;position:absolute;inset:0;z-index:50;flex-direction:column;
763
+ align-items:center;justify-content:center;gap:12px;text-align:center;padding:40px;
764
+ background:rgba(232,236,240,.94);backdrop-filter:blur(4px)
765
+ }
766
+ #iframe-load-error .nu-icon{font-size:48px;color:#94a3b8}
767
+ #iframe-load-error .ile-title{font-weight:600;font-size:15px;color:var(--text-2)}
768
+ #iframe-load-error .ile-msg{font-size:12px;color:var(--text-3);max-width:360px;line-height:1.5;word-break:break-word}
769
+ #iframe-load-error .ile-reload-btn{
770
+ margin-top:4px;display:inline-flex;align-items:center;gap:6px;padding:8px 14px;border-radius:6px;
771
+ border:1px solid var(--border);background:#fff;color:var(--text);font-size:13px;font-weight:500;cursor:pointer
772
+ }
773
+ #iframe-load-error .ile-reload-btn:hover{background:var(--bg-hover);border-color:#cbd5e1}
774
+
751
775
  /* \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 */
752
776
  #device-frame{
753
777
  width: 100%;
@@ -802,12 +826,32 @@ flex: 1;
802
826
 
803
827
  /* \u2500\u2500 URL bar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
804
828
  #url-bar{
805
- flex:1;max-width:280px;background:var(--bg-sub);border:1px solid var(--border);border-radius:20px;
806
- color:var(--text-2);font-size:11px;padding:4px 12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap
829
+ flex: 1;
830
+ max-width: 100%;
831
+ width: 100%;
832
+ border: 1px solid var(--border);
833
+ border-radius: 4px;
834
+ color: var(--text-2);
835
+ font-size: 12px;
836
+ padding: 8px 8px;
837
+ outline: none;
838
+ font-family: inherit;
839
+ overflow: hidden;
840
+ text-overflow: ellipsis;
807
841
  }
842
+ #url-bar::placeholder{color:var(--text-3)}
843
+ #url-bar:focus{border-color:#94a3b8;color:var(--text);background:#fff}
844
+ #url-bar:disabled{opacity:.55;cursor:not-allowed}
808
845
 
809
846
  /* \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 */
810
- .lp-sec{border-bottom:1px solid var(--border);flex-shrink:0;padding: 24px 16px;}
847
+ .lp-sec{ border-bottom: 1px solid var(--border);
848
+ flex-shrink: 0;
849
+ padding: 18px 16px;}
850
+ .lp-sec-hd-url{
851
+ border-bottom: 1px solid var(--border);
852
+ flex-shrink: 0;
853
+ padding: 5px 16px;
854
+ }
811
855
  .lp-sec-no-border{border-bottom:none!important}
812
856
  .lp-sec-hd{
813
857
  margin-bottom: 12px;
@@ -1427,23 +1471,102 @@ select.pr-inp{cursor:pointer;background:#fff}
1427
1471
  max-width:420px;
1428
1472
  line-height:1.5;
1429
1473
  }
1430
- @media (max-width:1100px){
1431
- #app,
1432
- #custom-css-modal{
1433
- display:none!important;
1434
- }
1435
- #ve-min-screen-notice{
1436
- display:flex;
1437
- }
1474
+ html.ve-editor-gated #app,
1475
+ html.ve-editor-gated #custom-css-modal{
1476
+ display:none!important;
1477
+ }
1478
+ html.ve-editor-gated #ve-min-screen-notice{
1479
+ display:flex;
1438
1480
  }
1439
1481
  </style>
1440
1482
  </head>
1441
1483
  <body class="mode-editor">
1442
1484
  <div id="ve-min-screen-notice" aria-live="polite">
1443
1485
  <div class="ve-min-screen-icon"><i class="bi bi-display"></i></div>
1444
- <div class="ve-min-screen-title">Please switch to greater or bigger screen to use Visual Editor</div>
1486
+ <div class="ve-min-screen-title">Please switch to a larger screen to use the Visual Editor</div>
1445
1487
  <div class="ve-min-screen-desc">The Visual Editor requires a screen width of at least 1100px.</div>
1446
1488
  </div>
1489
+ <script>(function(){
1490
+ var MIN_EDITOR_WIDTH=1100;
1491
+ var ZOOM_IN_THRESHOLD=1.05;
1492
+ var ZOOM_MATCH_TOLERANCE=0.06;
1493
+ var STANDARD_BROWSER_ZOOMS=[1.25,1.33,1.5,1.67,1.75,2,2.5,3,4];
1494
+ function markUserZoomInteraction(){
1495
+ try{sessionStorage.setItem('ve-user-zoomed','1');}catch(_){}
1496
+ }
1497
+ function hasUserZoomInteraction(){
1498
+ try{return sessionStorage.getItem('ve-user-zoomed')==='1';}catch(_){return false;}
1499
+ }
1500
+ function matchesStandardBrowserZoom(iw,screenW){
1501
+ if(!iw||!screenW)return false;
1502
+ for(var i=0;i<STANDARD_BROWSER_ZOOMS.length;i++){
1503
+ var z=STANDARD_BROWSER_ZOOMS[i];
1504
+ var impliedFull=iw*z;
1505
+ if(Math.abs(impliedFull-screenW)/screenW<=ZOOM_MATCH_TOLERANCE)return true;
1506
+ }
1507
+ return false;
1508
+ }
1509
+ function isBrowserZoomedIn(iw,screenW){
1510
+ try{
1511
+ if(window.visualViewport&&window.visualViewport.scale>ZOOM_IN_THRESHOLD)return true;
1512
+ }catch(_){}
1513
+ if(!screenW||screenW<MIN_EDITOR_WIDTH||iw>=MIN_EDITOR_WIDTH)return false;
1514
+ if(hasUserZoomInteraction())return true;
1515
+ return matchesStandardBrowserZoom(iw,screenW);
1516
+ }
1517
+ function updateMinScreenGate(){
1518
+ var iw=window.innerWidth||0;
1519
+ var gated=iw<MIN_EDITOR_WIDTH;
1520
+ document.documentElement.classList.toggle('ve-editor-gated',gated);
1521
+ if(!gated){
1522
+ try{sessionStorage.removeItem('ve-user-zoomed');}catch(_){}
1523
+ return;
1524
+ }
1525
+ var notice=document.getElementById('ve-min-screen-notice');
1526
+ if(!notice)return;
1527
+ var titleEl=notice.querySelector('.ve-min-screen-title');
1528
+ var descEl=notice.querySelector('.ve-min-screen-desc');
1529
+ var iconEl=notice.querySelector('.ve-min-screen-icon i');
1530
+ if(!titleEl||!descEl||!iconEl)return;
1531
+ var screenW=(window.screen&&(window.screen.availWidth||window.screen.width))||0;
1532
+ var smallScreen=screenW>0&&screenW<MIN_EDITOR_WIDTH;
1533
+ var zoomedIn=isBrowserZoomedIn(iw,screenW);
1534
+ if(smallScreen){
1535
+ iconEl.className='bi bi-display';
1536
+ titleEl.textContent='Please switch to a larger screen to use the Visual Editor';
1537
+ descEl.textContent='The Visual Editor requires a screen width of at least '+MIN_EDITOR_WIDTH+'px.';
1538
+ }else if(zoomedIn){
1539
+ iconEl.className='bi bi-zoom-out';
1540
+ titleEl.textContent='You have zoomed in too much';
1541
+ descEl.textContent='Zoom out to use the Visual Editor \u2014 press Cmd + - on Mac or Ctrl + - on Windows.';
1542
+ }else{
1543
+ iconEl.className='bi bi-arrows-angle-expand';
1544
+ titleEl.textContent='Widen your browser window';
1545
+ descEl.textContent='The Visual Editor needs at least '+MIN_EDITOR_WIDTH+'px of width. Maximize this window or resize it wider.';
1546
+ }
1547
+ }
1548
+ function onZoomShortcut(e){
1549
+ if(!(e.ctrlKey||e.metaKey))return;
1550
+ var k=e.key||'';
1551
+ if(k==='+'||k==='-'||k==='='||k==='0'||k==='_'||e.code==='Equal'||e.code==='Minus'||e.code==='NumpadAdd'||e.code==='NumpadSubtract'){
1552
+ markUserZoomInteraction();
1553
+ setTimeout(updateMinScreenGate,0);
1554
+ }
1555
+ }
1556
+ function onZoomWheel(e){
1557
+ if(!e.ctrlKey)return;
1558
+ markUserZoomInteraction();
1559
+ setTimeout(updateMinScreenGate,0);
1560
+ }
1561
+ document.addEventListener('keydown',onZoomShortcut,true);
1562
+ window.addEventListener('wheel',onZoomWheel,{passive:true,capture:true});
1563
+ updateMinScreenGate();
1564
+ window.addEventListener('resize',updateMinScreenGate);
1565
+ if(window.visualViewport){
1566
+ window.visualViewport.addEventListener('resize',updateMinScreenGate);
1567
+ window.visualViewport.addEventListener('scroll',updateMinScreenGate);
1568
+ }
1569
+ })();</script>
1447
1570
  <div id="app">
1448
1571
  <!-- \u2500\u2500 Toolbar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->
1449
1572
  <div id="toolbar">
@@ -1540,16 +1663,13 @@ select.pr-inp{cursor:pointer;background:#fff}
1540
1663
  <div class="tb-dev-3btns">
1541
1664
  <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>
1542
1665
  <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>
1543
- <button class="tb-dk-btn" title="Comments"><i class="bi bi-chat-dots"></i></button>
1666
+ <button class="tb-dk-btn" style="display:none" title="Comments"><i class="bi bi-chat-dots"></i></button>
1544
1667
  </div>
1545
1668
  <button class="tb-sim-btn" id="btn-simulate" onclick="simulateExperiment()">See Preview</button>
1546
1669
 
1547
1670
  <!-- btn-close: kept for JS event listener -->
1548
1671
  <button class="tb-fin-btn" id="btn-close">Exit Editor</button>
1549
1672
  </div>
1550
-
1551
- <!-- url-bar: hidden, kept for JS compatibility -->
1552
- <div id="url-bar" style="display:none" title="">No page loaded</div>
1553
1673
  </div>
1554
1674
 
1555
1675
  <!-- \u2500\u2500 Main \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->
@@ -1557,7 +1677,10 @@ select.pr-inp{cursor:pointer;background:#fff}
1557
1677
 
1558
1678
  <!-- Left panel -->
1559
1679
  <div id="left-panel">
1560
-
1680
+ <div class="lp-sec-hd-url">
1681
+ <!-- Page URL -->
1682
+ <input type="text" id="url-bar" placeholder="Enter URL and press Enter" spellcheck="false" autocomplete="off" aria-label="Page URL" title="" />
1683
+ </div>
1561
1684
  <!-- Variations -->
1562
1685
  <div class="lp-sec">
1563
1686
  <div class="lp-sec-hd">
@@ -1600,7 +1723,7 @@ select.pr-inp{cursor:pointer;background:#fff}
1600
1723
  <!-- Tabs (hidden, kept for JS) -->
1601
1724
  <div class="lp-tabs-container">
1602
1725
  <div class="lp-tabs">
1603
- <div class="lp-tab active" onclick="switchLeftTab('elements')">Elements</div>
1726
+ <div class="lp-tab active" onclick="switchLeftTab('elements')">Added Nodes</div>
1604
1727
  <div class="lp-tab" onclick="switchLeftTab('dom-tree')">DOM Tree</div>
1605
1728
  </div>
1606
1729
  <button class="lp-add-btn" id="btn-add-element" title="Add element">+ Add</button>
@@ -1652,6 +1775,16 @@ select.pr-inp{cursor:pointer;background:#fff}
1652
1775
  <div style="font-size:12px;color:var(--text-3)">Waiting for experiment data&hellip;</div>
1653
1776
  </div>
1654
1777
 
1778
+ <!-- Load error overlay (bad URL, fetch failure, refused to connect) -->
1779
+ <div id="iframe-load-error" aria-live="polite">
1780
+ <div class="nu-icon"><i class="bi bi-wifi-off"></i></div>
1781
+ <div class="ile-title">Page failed to load</div>
1782
+ <div class="ile-msg" id="iframe-load-error-msg">Check the URL and try again.</div>
1783
+ <button type="button" class="ile-reload-btn" id="iframe-load-error-reload">
1784
+ <i class="bi bi-arrow-clockwise"></i> Reload page
1785
+ </button>
1786
+ </div>
1787
+
1655
1788
  <!-- Device frame containing the editing iframe -->
1656
1789
  <div id="device-frame" class="desktop">
1657
1790
  <iframe id="iframeId" name="iframeId" allowfullscreen></iframe>
@@ -1901,12 +2034,17 @@ function renderCroSectionThumb(sec) {
1901
2034
  }
1902
2035
 
1903
2036
  var BASE_COMPONENTS = [
1904
- { name:'Heading', icon:'bi-type-h1', defaultAccSection:'content', html:'<h2 style="margin:0 0 12px;font-size:28px;font-weight:700;line-height:1.2;color:#0f172a">Your Heading</h2>' },
2037
+ { name:'Heading', icon:'bi-type-h1', defaultAccSection:'content', html:'<h1 style="margin:0 0 12px;font-size:28px;font-weight:700;line-height:1.2;color:#0f172a">Your Heading</h1>' },
2038
+ { name:'Heading', icon:'bi-type-h2', defaultAccSection:'content', html:'<h2 style="margin:0 0 12px;font-size:24px;font-weight:700;line-height:1.25;color:#0f172a">Your Heading</h2>' },
2039
+ { name:'Heading', icon:'bi-type-h3', defaultAccSection:'content', html:'<h3 style="margin:0 0 12px;font-size:20px;font-weight:700;line-height:1.3;color:#0f172a">Your Heading</h3>' },
2040
+ { name:'Heading', icon:'bi-type-h4', defaultAccSection:'content', html:'<h4 style="margin:0 0 12px;font-size:18px;font-weight:600;line-height:1.35;color:#0f172a">Your Heading</h4>' },
2041
+ { name:'Heading', icon:'bi-type-h5', defaultAccSection:'content', html:'<h5 style="margin:0 0 12px;font-size:16px;font-weight:600;line-height:1.4;color:#0f172a">Your Heading</h5>' },
2042
+ { name:'Heading', icon:'bi-type-h6', defaultAccSection:'content', html:'<h6 style="margin:0 0 12px;font-size:14px;font-weight:600;line-height:1.45;color:#0f172a">Your Heading</h6>' },
1905
2043
  { name:'Paragraph', icon:'bi-paragraph', defaultAccSection:'content', html:'<p style="margin:0 0 12px;font-size:16px;line-height:1.6;color:#334155">Your text here. Click to edit.</p>' },
1906
2044
  { 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">' },
1907
2045
  { name:'Button', icon:'bi-hand-index', defaultAccSection:'content', 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>' },
1908
2046
  { name:'Link', icon:'bi-link-45deg', defaultAccSection:'content', html:'<a href="#" style="font-size:16px;font-weight:500;color:#2563eb;text-decoration:underline">Link text</a>' },
1909
- { name:'Divider', icon:'bi-dash-lg', defaultAccSection:'background', html:'<hr style="margin:16px 0;border:none;border-top:1px solid #e2e8f0">' },
2047
+ { name:'Divider', icon:'bi-dash-lg', defaultAccSection:'border', html:'<hr style="margin:16px 0;border:none;border-top:1px solid #e2e8f0">' },
1910
2048
  { name:'List', icon:'bi-list-ul', defaultAccSection:'content', 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>' },
1911
2049
  { name:'Video', icon:'bi-play-circle', defaultAccSection:'video', html:'<video controls style="display:block;width:100%;max-width:100%;border-radius:8px;background:#000"><source src="" type="video/mp4"></video>' },
1912
2050
  { name:'Youtube Video', icon:'bi-youtube', defaultAccSection:'youtube', 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>' },
@@ -2153,6 +2291,8 @@ var currentSectionComponentsTab = 'components';
2153
2291
  var dragHandleActive = false;
2154
2292
  var domTreeCollapsed = {};
2155
2293
  var domTreeRefreshTimer = null;
2294
+ var domTreeDragState = null;
2295
+ var iframeLoadErrorCheckTimer = null;
2156
2296
  var iframeSyncRetryTimer = null;
2157
2297
  var iframeSyncAttempts = 0;
2158
2298
  var selectionScrollWin = null;
@@ -2638,6 +2778,117 @@ function bindViewportControls() {
2638
2778
  applyViewportFrame();
2639
2779
  }
2640
2780
 
2781
+ function setUrlBarValue(pageUrl) {
2782
+ var urlBar = document.getElementById('url-bar');
2783
+ if (!urlBar) return;
2784
+ var val = pageUrl ? String(pageUrl) : '';
2785
+ urlBar.value = val;
2786
+ urlBar.title = val || 'Enter a page URL and press Enter';
2787
+ }
2788
+
2789
+ /** Keep experimentData.pageUrl and #url-bar in sync when the preview iframe navigates. */
2790
+ function applyUrlBarFromIframeUrl(pageUrl) {
2791
+ if (!pageUrl) return;
2792
+ var normalized = String(pageUrl);
2793
+ if (experimentData) {
2794
+ experimentData = Object.assign({}, experimentData, { pageUrl: normalized });
2795
+ } else {
2796
+ experimentData = { pageUrl: normalized };
2797
+ }
2798
+ setUrlBarValue(normalized);
2799
+ }
2800
+
2801
+ function normalizeUserPageUrl(raw) {
2802
+ var s = String(raw || '').trim();
2803
+ if (!s) return '';
2804
+ if (!/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(s)) s = 'https://' + s;
2805
+ try {
2806
+ return new URL(s).toString();
2807
+ } catch(_) {
2808
+ return '';
2809
+ }
2810
+ }
2811
+
2812
+ function buildProxyUrlForPageUrl(pageUrl, data) {
2813
+ data = data || experimentData || {};
2814
+ var extraTrackingMarkers = Array.isArray(data.trackingMarkers)
2815
+ ? data.trackingMarkers.filter(function(m) { return typeof m === 'string' && m.trim().length > 0; })
2816
+ : [];
2817
+ var conversionProxyBaseUrl = (typeof data.conversionProxyBaseUrl === 'string'
2818
+ ? data.conversionProxyBaseUrl
2819
+ : '').trim().replace(/\\/+$/, '');
2820
+ var proxyPath = '/api/conversion-proxy';
2821
+ var shellOrigin = '';
2822
+ try { shellOrigin = window.location.origin || ''; } catch(_) {}
2823
+ var onWorkerOrigin = !!(conversionProxyBaseUrl && shellOrigin === conversionProxyBaseUrl);
2824
+ var proxyRoot = proxyPath;
2825
+ var trackingMarkersParam = extraTrackingMarkers.length
2826
+ ? '&trackingMarkers=' + encodeURIComponent(JSON.stringify(extraTrackingMarkers))
2827
+ : '';
2828
+ var conversionProxyBaseUrlParam = (!onWorkerOrigin && conversionProxyBaseUrl)
2829
+ ? '&conversionProxyBaseUrl=' + encodeURIComponent(conversionProxyBaseUrl)
2830
+ : '';
2831
+ return proxyRoot + '?password=' + encodeURIComponent(data.editorPassword || '') +
2832
+ '&url=' + encodeURIComponent(pageUrl) +
2833
+ '&strictObserverFreeze=' + encodeURIComponent(data && data.strictObserverFreeze ? '1' : '0') +
2834
+ conversionProxyBaseUrlParam +
2835
+ trackingMarkersParam;
2836
+ }
2837
+
2838
+ function navigateToUserPageUrl(rawUrl) {
2839
+ var pageUrl = normalizeUserPageUrl(rawUrl);
2840
+ if (!pageUrl) {
2841
+ showEditorNotification('Enter a valid URL.', 'error', 2600);
2842
+ setUrlBarValue(experimentData && experimentData.pageUrl);
2843
+ return false;
2844
+ }
2845
+ var prevPageUrl = experimentData && experimentData.pageUrl ? String(experimentData.pageUrl) : '';
2846
+ if (prevPageUrl === pageUrl && lastLoadedProxyUrl) {
2847
+ setUrlBarValue(pageUrl);
2848
+ return true;
2849
+ }
2850
+
2851
+ commitStateChangesForActiveVariation();
2852
+ clearPendingGranularChangesets();
2853
+ varHtmlCache = {};
2854
+ sessionStructuralChainRowsByVarId = {};
2855
+ appliedChangesetSnapshots = {};
2856
+ appliedStructuralChangesetKeys = {};
2857
+ deselectElement();
2858
+
2859
+ experimentData = experimentData
2860
+ ? Object.assign({}, experimentData, { pageUrl: pageUrl })
2861
+ : { pageUrl: pageUrl };
2862
+
2863
+ setUrlBarValue(pageUrl);
2864
+ showNoUrl(false);
2865
+ loadPage(buildProxyUrlForPageUrl(pageUrl, experimentData));
2866
+ return true;
2867
+ }
2868
+
2869
+ function bindUrlBar() {
2870
+ var urlBar = document.getElementById('url-bar');
2871
+ if (!urlBar || urlBar._urlBarBound) return;
2872
+ urlBar._urlBarBound = true;
2873
+ urlBar.addEventListener('keydown', function(e) {
2874
+ if (e.key === 'Enter') {
2875
+ e.preventDefault();
2876
+ navigateToUserPageUrl(urlBar.value);
2877
+ urlBar.blur();
2878
+ } else if (e.key === 'Escape') {
2879
+ e.preventDefault();
2880
+ setUrlBarValue(experimentData && experimentData.pageUrl);
2881
+ urlBar.blur();
2882
+ }
2883
+ });
2884
+ urlBar.addEventListener('blur', function() {
2885
+ var canonical = experimentData && experimentData.pageUrl ? String(experimentData.pageUrl) : '';
2886
+ if (canonical && urlBar.value.trim() !== canonical) {
2887
+ setUrlBarValue(canonical);
2888
+ }
2889
+ });
2890
+ }
2891
+
2641
2892
  // \u2500\u2500 Left panel tab switch \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
2642
2893
  function switchLeftTab(tab) {
2643
2894
  if (tab !== 'elements' && tab !== 'dom-tree') return;
@@ -3872,34 +4123,11 @@ function handleLoadExperiment(data) {
3872
4123
  var pageUrl = data.pageUrl || '';
3873
4124
  if (!pageUrl) {
3874
4125
  showNoUrl(true);
4126
+ setUrlBarValue('');
3875
4127
  lastLoadedProxyUrl = '';
3876
4128
  return;
3877
4129
  }
3878
- var extraTrackingMarkers = Array.isArray(data && data.trackingMarkers)
3879
- ? data.trackingMarkers.filter(function(m){return typeof m === 'string' && m.trim().length > 0;})
3880
- : [];
3881
- var conversionProxyBaseUrl = (typeof (data && data.conversionProxyBaseUrl) === 'string'
3882
- ? data.conversionProxyBaseUrl
3883
- : '').trim().replace(/\\/+$/, '');
3884
- var proxyPath = '/api/conversion-proxy';
3885
- // Page iframe must stay same-origin as /vvveb-editor for DOM selection/editing.
3886
- // When the shell is served from the worker (conversionProxyBaseUrl origin),
3887
- // use same-origin relative proxy only. Otherwise delegate upstream via query param.
3888
- var shellOrigin = '';
3889
- try { shellOrigin = window.location.origin || ''; } catch(_) {}
3890
- var onWorkerOrigin = !!(conversionProxyBaseUrl && shellOrigin === conversionProxyBaseUrl);
3891
- var proxyRoot = proxyPath;
3892
- var trackingMarkersParam = extraTrackingMarkers.length
3893
- ? '&trackingMarkers=' + encodeURIComponent(JSON.stringify(extraTrackingMarkers))
3894
- : '';
3895
- var conversionProxyBaseUrlParam = (!onWorkerOrigin && conversionProxyBaseUrl)
3896
- ? '&conversionProxyBaseUrl=' + encodeURIComponent(conversionProxyBaseUrl)
3897
- : '';
3898
- var proxyUrl = proxyRoot + '?password=' + encodeURIComponent(data.editorPassword || '') +
3899
- '&url=' + encodeURIComponent(pageUrl) +
3900
- '&strictObserverFreeze=' + encodeURIComponent(data && data.strictObserverFreeze ? '1' : '0') +
3901
- conversionProxyBaseUrlParam +
3902
- trackingMarkersParam;
4130
+ var proxyUrl = buildProxyUrlForPageUrl(pageUrl, data);
3903
4131
 
3904
4132
  // Parent often re-posts load-experiment when React re-renders (new object identity) or
3905
4133
  // after mutations-changed. Reloading the iframe again wipes variant changesets mid-session.
@@ -3917,9 +4145,7 @@ function handleLoadExperiment(data) {
3917
4145
  loadStateChangesForActiveVariation();
3918
4146
  writePersistedActiveVariationId(activeVarId);
3919
4147
  renderVariationTabs();
3920
- var urlBarSkip = document.getElementById('url-bar');
3921
- urlBarSkip.textContent = pageUrl;
3922
- urlBarSkip.title = pageUrl;
4148
+ setUrlBarValue(pageUrl);
3923
4149
  try {
3924
4150
  applyActiveVariationHtml();
3925
4151
  syncIframeInteractions('load-experiment-skip-url');
@@ -3950,9 +4176,7 @@ function handleLoadExperiment(data) {
3950
4176
  writePersistedActiveVariationId(activeVarId);
3951
4177
  renderVariationTabs();
3952
4178
 
3953
- var urlBar = document.getElementById('url-bar');
3954
- urlBar.textContent = pageUrl;
3955
- urlBar.title = pageUrl;
4179
+ setUrlBarValue(pageUrl);
3956
4180
  if (currentMainTab === 'history') renderHistoryTab();
3957
4181
  captureBaselineFromVariations(variations);
3958
4182
  recomputeEditorDirty();
@@ -3961,12 +4185,128 @@ function handleLoadExperiment(data) {
3961
4185
 
3962
4186
  function showNoUrl(show) {
3963
4187
  document.getElementById('no-url').style.display = show ? 'flex' : 'none';
4188
+ var urlBar = document.getElementById('url-bar');
4189
+ if (urlBar) urlBar.disabled = !!show;
3964
4190
  if (show) {
4191
+ hideIframeLoadError();
3965
4192
  detachIframeLoadingListeners();
3966
4193
  setIframePageLoadingUi(false);
3967
4194
  }
3968
4195
  }
3969
4196
 
4197
+ function hideIframeLoadError() {
4198
+ if (iframeLoadErrorCheckTimer) {
4199
+ clearTimeout(iframeLoadErrorCheckTimer);
4200
+ iframeLoadErrorCheckTimer = null;
4201
+ }
4202
+ var el = document.getElementById('iframe-load-error');
4203
+ if (el) el.style.display = 'none';
4204
+ }
4205
+
4206
+ function showIframeLoadError(message) {
4207
+ if (iframeLoadErrorCheckTimer) {
4208
+ clearTimeout(iframeLoadErrorCheckTimer);
4209
+ iframeLoadErrorCheckTimer = null;
4210
+ }
4211
+ var el = document.getElementById('iframe-load-error');
4212
+ var msgEl = document.getElementById('iframe-load-error-msg');
4213
+ if (msgEl) msgEl.textContent = message || 'Check the URL and try again.';
4214
+ if (el) el.style.display = 'flex';
4215
+ setIframePageLoadingUi(false);
4216
+ detachIframeLoadingListeners();
4217
+ deselectElement({ preserveMainTab: true });
4218
+ }
4219
+
4220
+ function detectIframeLoadFailure(iframe, doc) {
4221
+ if (!iframe || !iframe.src || iframe.src === 'about:blank') return null;
4222
+
4223
+ if (!doc) {
4224
+ return 'Unable to load this page. The connection was refused or blocked.';
4225
+ }
4226
+
4227
+ var docUrl = '';
4228
+ try { docUrl = String(doc.URL || ''); } catch(_) {}
4229
+ if (docUrl === 'about:blank') {
4230
+ return 'This page could not be loaded. Check the URL and try again.';
4231
+ }
4232
+ if (
4233
+ docUrl.indexOf('chrome-error://') === 0 ||
4234
+ docUrl.indexOf('about:neterror') === 0
4235
+ ) {
4236
+ return 'This page could not be loaded. Check the URL and try again.';
4237
+ }
4238
+
4239
+ var bodyText = '';
4240
+ try {
4241
+ bodyText = doc.body && doc.body.innerText ? String(doc.body.innerText).trim() : '';
4242
+ } catch(_) {}
4243
+
4244
+ var bodyLower = bodyText.toLowerCase();
4245
+ if (
4246
+ bodyLower.indexOf('refused to connect') >= 0 ||
4247
+ bodyLower.indexOf('err_connection_refused') >= 0 ||
4248
+ bodyLower.indexOf('err_name_not_resolved') >= 0 ||
4249
+ bodyLower.indexOf('err_ssl') >= 0 ||
4250
+ bodyLower.indexOf('err_connection_timed_out') >= 0 ||
4251
+ bodyLower.indexOf("this site can't be reached") >= 0 ||
4252
+ bodyLower.indexOf('this site cant be reached') >= 0 ||
4253
+ bodyLower.indexOf('unable to connect') >= 0 ||
4254
+ bodyLower.indexOf("page isn't working") >= 0 ||
4255
+ bodyLower.indexOf('404 not found') >= 0 && bodyLower.length < 280
4256
+ ) {
4257
+ return 'This page could not be loaded. Check the URL and try again.';
4258
+ }
4259
+
4260
+ if (bodyText.charAt(0) === '{') {
4261
+ try {
4262
+ var parsed = JSON.parse(bodyText);
4263
+ if (parsed && parsed.error) return String(parsed.error);
4264
+ } catch(_) {}
4265
+ }
4266
+
4267
+ try {
4268
+ var ct = doc.contentType || '';
4269
+ if (String(ct).indexOf('application/json') >= 0 && bodyText.charAt(0) === '{') {
4270
+ var parsedCt = JSON.parse(bodyText);
4271
+ if (parsedCt && parsedCt.error) return String(parsedCt.error);
4272
+ }
4273
+ } catch(_) {}
4274
+
4275
+ return null;
4276
+ }
4277
+
4278
+ function scheduleIframeLoadErrorCheck(iframe) {
4279
+ if (iframeLoadErrorCheckTimer) clearTimeout(iframeLoadErrorCheckTimer);
4280
+ iframeLoadErrorCheckTimer = setTimeout(function() {
4281
+ iframeLoadErrorCheckTimer = null;
4282
+ if (!iframe || !iframe.src || iframe.src === 'about:blank') return;
4283
+ var doc = null;
4284
+ try { doc = iframe.contentDocument; } catch(_) {}
4285
+ var failMsg = detectIframeLoadFailure(iframe, doc);
4286
+ if (failMsg) showIframeLoadError(failMsg);
4287
+ }, 450);
4288
+ }
4289
+
4290
+ function reloadCurrentPage() {
4291
+ hideIframeLoadError();
4292
+ if (lastLoadedProxyUrl) {
4293
+ loadPage(lastLoadedProxyUrl);
4294
+ return;
4295
+ }
4296
+ var pageUrl = experimentData && experimentData.pageUrl;
4297
+ if (pageUrl) navigateToUserPageUrl(pageUrl);
4298
+ }
4299
+
4300
+ function bindIframeLoadErrorControls() {
4301
+ var btn = document.getElementById('iframe-load-error-reload');
4302
+ if (!btn || btn._iframeLoadErrorBound) return;
4303
+ btn._iframeLoadErrorBound = true;
4304
+ btn.addEventListener('click', function(e) {
4305
+ e.preventDefault();
4306
+ reloadCurrentPage();
4307
+ });
4308
+ }
4309
+
3970
4310
  function detachIframeLoadingListeners() {
3971
4311
  if (!iframeDocLoadingListeners) return;
3972
4312
  try {
@@ -4340,15 +4680,10 @@ function parseEditorUrlPayload(rawUrl) {
4340
4680
  var lastEditorUrlPayloadKey = '';
4341
4681
  function emitEditorUrlChangedPayload(payload) {
4342
4682
  if (!payload) return;
4683
+ if (payload.url) applyUrlBarFromIframeUrl(payload.url);
4343
4684
  var key = String(payload.url || '') + '|' + String(payload.password || '');
4344
4685
  if (key === lastEditorUrlPayloadKey) return;
4345
4686
  lastEditorUrlPayloadKey = key;
4346
- try {
4347
- console.info('[V2 iframe-url]', {
4348
- url: payload.url || '',
4349
- passwordPresent: !!payload.password,
4350
- });
4351
- } catch(_) {}
4352
4687
  send('editor-url-changed', payload);
4353
4688
  }
4354
4689
  function emitEditorUrlChanged(rawUrl) {
@@ -4357,6 +4692,7 @@ function emitEditorUrlChanged(rawUrl) {
4357
4692
  }
4358
4693
 
4359
4694
  function loadPage(proxyUrl) {
4695
+ hideIframeLoadError();
4360
4696
  showNoUrl(false);
4361
4697
  lastLoadedProxyUrl = proxyUrl;
4362
4698
  var navGen = nextIframeContentNavGen();
@@ -5521,6 +5857,149 @@ function setDomTreeStatus(mode) {
5521
5857
  }
5522
5858
  }
5523
5859
 
5860
+ function canDomTreeMoveElement(el) {
5861
+ if (!el || el.nodeType !== 1 || !el.parentElement) return false;
5862
+ var tag = (el.tagName || '').toLowerCase();
5863
+ if (tag === 'html' || tag === 'body') return false;
5864
+ try {
5865
+ var doc = el.ownerDocument;
5866
+ if (doc && (el === doc.documentElement || el === doc.body)) return false;
5867
+ } catch(_) {}
5868
+ return true;
5869
+ }
5870
+
5871
+ function canDomTreeDropRelativeTo(dragEl, targetEl) {
5872
+ if (!canDomTreeMoveElement(dragEl) || !targetEl || targetEl.nodeType !== 1) return false;
5873
+ if (dragEl === targetEl) return false;
5874
+ try {
5875
+ if (dragEl.contains(targetEl) || targetEl.contains(dragEl)) return false;
5876
+ } catch(_) {
5877
+ return false;
5878
+ }
5879
+ var parent = targetEl.parentElement;
5880
+ if (!parent) return false;
5881
+ if (isDomTreeSkippableTagName(parent.tagName)) return false;
5882
+ return true;
5883
+ }
5884
+
5885
+ function moveDomTreeElementRelativeTo(dragEl, targetEl, insertBefore) {
5886
+ if (!canDomTreeDropRelativeTo(dragEl, targetEl)) return false;
5887
+ var parent = targetEl.parentElement;
5888
+ if (!parent) return false;
5889
+ var ref = insertBefore ? targetEl : targetEl.nextSibling;
5890
+ if (dragEl.parentElement === parent && dragEl === ref) return false;
5891
+ if (!insertBefore && dragEl.parentElement === parent && dragEl === targetEl.nextSibling) return false;
5892
+ try {
5893
+ parent.insertBefore(dragEl, ref);
5894
+ return true;
5895
+ } catch(_) {
5896
+ return false;
5897
+ }
5898
+ }
5899
+
5900
+ function clearDomTreeDropIndicators() {
5901
+ var root = document.getElementById('dom-tree-root');
5902
+ if (!root) return;
5903
+ var rows = root.querySelectorAll('.dt-row');
5904
+ for (var i = 0; i < rows.length; i++) {
5905
+ rows[i].classList.remove('dt-drop-before', 'dt-drop-after', 'dt-drop-invalid', 'dt-dragging');
5906
+ }
5907
+ }
5908
+
5909
+ function cleanupDomTreeDrag() {
5910
+ clearDomTreeDropIndicators();
5911
+ domTreeDragState = null;
5912
+ }
5913
+
5914
+ function attachDomTreeDragHandlers() {
5915
+ var root = document.getElementById('dom-tree-root');
5916
+ if (!root || root._domTreeDragBound) return;
5917
+ root._domTreeDragBound = true;
5918
+
5919
+ root.addEventListener('dragstart', function(e) {
5920
+ if (currentMode !== 'editor') {
5921
+ e.preventDefault();
5922
+ return;
5923
+ }
5924
+ var row = e.target && e.target.closest ? e.target.closest('.dt-row') : null;
5925
+ if (!row || !row._dtEl || (e.target.closest && e.target.closest('.dt-chev'))) {
5926
+ e.preventDefault();
5927
+ return;
5928
+ }
5929
+ if (!canDomTreeMoveElement(row._dtEl)) {
5930
+ e.preventDefault();
5931
+ return;
5932
+ }
5933
+ domTreeDragState = { el: row._dtEl, row: row };
5934
+ row.classList.add('dt-dragging');
5935
+ try {
5936
+ e.dataTransfer.effectAllowed = 'move';
5937
+ e.dataTransfer.setData('text/plain', 'dom-tree');
5938
+ } catch(_) {}
5939
+ suppressClickUntil = Date.now() + 300;
5940
+ });
5941
+
5942
+ root.addEventListener('dragover', function(e) {
5943
+ if (!domTreeDragState) return;
5944
+ e.preventDefault();
5945
+ clearDomTreeDropIndicators();
5946
+ if (domTreeDragState.row) domTreeDragState.row.classList.add('dt-dragging');
5947
+
5948
+ var row = e.target && e.target.closest ? e.target.closest('.dt-row') : null;
5949
+ if (!row || !row._dtEl) {
5950
+ try { e.dataTransfer.dropEffect = 'none'; } catch(_) {}
5951
+ return;
5952
+ }
5953
+
5954
+ var dragEl = domTreeDragState.el;
5955
+ var targetEl = row._dtEl;
5956
+ if (!canDomTreeDropRelativeTo(dragEl, targetEl)) {
5957
+ row.classList.add('dt-drop-invalid');
5958
+ try { e.dataTransfer.dropEffect = 'none'; } catch(_) {}
5959
+ return;
5960
+ }
5961
+
5962
+ var rect = row.getBoundingClientRect();
5963
+ var insertBefore = e.clientY < rect.top + rect.height / 2;
5964
+ row.classList.add(insertBefore ? 'dt-drop-before' : 'dt-drop-after');
5965
+ domTreeDragState.dropRow = row;
5966
+ domTreeDragState.insertBefore = insertBefore;
5967
+ try { e.dataTransfer.dropEffect = 'move'; } catch(_) {}
5968
+ });
5969
+
5970
+ root.addEventListener('drop', function(e) {
5971
+ e.preventDefault();
5972
+ if (!domTreeDragState) return;
5973
+
5974
+ var dragEl = domTreeDragState.el;
5975
+ var row = domTreeDragState.dropRow || (e.target && e.target.closest ? e.target.closest('.dt-row') : null);
5976
+ var moved = false;
5977
+ if (row && row._dtEl && canDomTreeDropRelativeTo(dragEl, row._dtEl)) {
5978
+ var insertBefore = domTreeDragState.insertBefore;
5979
+ if (insertBefore === undefined) {
5980
+ var rect = row.getBoundingClientRect();
5981
+ insertBefore = e.clientY < rect.top + rect.height / 2;
5982
+ }
5983
+ if (moveDomTreeElementRelativeTo(dragEl, row._dtEl, insertBefore)) {
5984
+ moved = true;
5985
+ if (activeVarId) recordReorderAfterDrag(dragEl);
5986
+ saveCurrentVariationHtml();
5987
+ recomputeEditorDirty();
5988
+ selectElement(dragEl);
5989
+ scrollIframeElementIntoView(dragEl);
5990
+ updateSelectionToolbar();
5991
+ scheduleDomTreeRefresh();
5992
+ }
5993
+ }
5994
+ cleanupDomTreeDrag();
5995
+ if (moved) suppressClickUntil = Date.now() + 300;
5996
+ });
5997
+
5998
+ root.addEventListener('dragend', function() {
5999
+ cleanupDomTreeDrag();
6000
+ });
6001
+ }
6002
+
5524
6003
  function domTreePathSegment(el) {
5525
6004
  var tag = el.tagName.toLowerCase();
5526
6005
  var idx = 1;
@@ -5899,6 +6378,7 @@ function renderDomTree(filterRaw) {
5899
6378
  row.onmouseenter = function() {
5900
6379
  setTreeHoverHighlight(el);
5901
6380
  };
6381
+ if (canDomTreeMoveElement(el)) row.draggable = true;
5902
6382
  root.appendChild(row);
5903
6383
 
5904
6384
  if (!hasKids || collapsed) return;
@@ -5920,6 +6400,7 @@ function renderDomTree(filterRaw) {
5920
6400
  root.onmouseleave = function() {
5921
6401
  clearTreeHoverHighlight();
5922
6402
  };
6403
+ attachDomTreeDragHandlers();
5923
6404
  }
5924
6405
 
5925
6406
  // \u2500\u2500 Utility helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
@@ -6348,7 +6829,7 @@ function renderVideoSection(el) {
6348
6829
  }
6349
6830
 
6350
6831
  html += pr('Src', '<input class="pr-inp" id="pp-video-src" type="url" value="'+esc(src)+'" placeholder="https://example.com/video.mp4">');
6351
- html += pr('Poster', '<input class="pr-inp" id="pp-video-poster" type="url" value="'+esc(el.getAttribute('poster')||'')+'" placeholder="https://\u2026">');
6832
+ html += pr('Thumbnail', '<input class="pr-inp" id="pp-video-poster" type="url" value="'+esc(el.getAttribute('poster')||'')+'" placeholder="https://\u2026">');
6352
6833
 
6353
6834
  document.getElementById('acc-body-video').innerHTML = html;
6354
6835
 
@@ -6615,11 +7096,6 @@ function renderRightPanel(el, options) {
6615
7096
  // \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
6616
7097
  var contentHtml = '';
6617
7098
  if (isLinkElement(el)) contentHtml += buildAnchorContentFieldsHtml(el);
6618
- if (isFormControlElement(el)) {
6619
- contentHtml +=
6620
- subLbl('Value') +
6621
- '<textarea class="pr-inp" id="pp-value" style="width:100%;min-height:44px;margin-bottom:8px">'+esc(getFormControlValue(el))+'</textarea>';
6622
- }
6623
7099
  if (tag==='input' || tag==='textarea') {
6624
7100
  contentHtml +=
6625
7101
  subLbl('Placeholder') +
@@ -6732,17 +7208,13 @@ function renderRightPanel(el, options) {
6732
7208
  function wireContentFieldSync(el, sel) {
6733
7209
  var textInp = document.getElementById('pp-text');
6734
7210
  var htmlInp = document.getElementById('pp-html');
6735
- var valueInp = document.getElementById('pp-value');
6736
7211
  var syncing = false;
6737
7212
  function applyContentChange(changedId) {
6738
7213
  if (syncing) return;
6739
7214
  syncing = true;
6740
7215
  try {
6741
7216
  var orig = getOriginalValue(changedId, el);
6742
- if (changedId === 'pp-value') {
6743
- el.value = valueInp.value;
6744
- logChange(sel, 'pp-value', valueInp.value, el, orig);
6745
- } else if (changedId === 'pp-text') {
7217
+ if (changedId === 'pp-text') {
6746
7218
  el.innerText = textInp.value;
6747
7219
  if (htmlInp) htmlInp.value = el.innerHTML;
6748
7220
  logChange(sel, 'pp-text', textInp.value, el, orig);
@@ -6755,10 +7227,6 @@ function wireContentFieldSync(el, sel) {
6755
7227
  syncing = false;
6756
7228
  }
6757
7229
  }
6758
- if (valueInp && isFormControlElement(el)) {
6759
- valueInp.addEventListener('input', function() { applyContentChange('pp-value'); });
6760
- valueInp.addEventListener('change', function() { applyContentChange('pp-value'); });
6761
- }
6762
7230
  if (textInp) {
6763
7231
  textInp.addEventListener('input', function() { applyContentChange('pp-text'); });
6764
7232
  textInp.addEventListener('change', function() { applyContentChange('pp-text'); });
@@ -7719,6 +8187,8 @@ function registerCROSections() {
7719
8187
  window.addEventListener('load', function() {
7720
8188
  registerCROSections();
7721
8189
  bindViewportControls();
8190
+ bindUrlBar();
8191
+ bindIframeLoadErrorControls();
7722
8192
  // switchSectionComponentsTab(currentSectionComponentsTab);
7723
8193
  renderElementsTree(document.getElementById('comp-search').value);
7724
8194
  vvvebReady = true;
@@ -7733,14 +8203,8 @@ window.addEventListener('load', function() {
7733
8203
  try {
7734
8204
  var d = ev && ev.data;
7735
8205
  if (!d || d.channel !== 'vvveb-proxy-url' || d.type !== 'editor-url-changed') return;
7736
- if (!iframe || !iframe.contentWindow || ev.source !== iframe.contentWindow) return;
7737
8206
  var payload = d.payload || {};
7738
- try {
7739
- console.info('[V2 iframe-url bridge]', {
7740
- url: payload.url || '',
7741
- passwordPresent: !!payload.password,
7742
- });
7743
- } catch(_) {}
8207
+ if (payload.url) applyUrlBarFromIframeUrl(payload.url);
7744
8208
  emitEditorUrlChangedPayload({
7745
8209
  url: payload.url || undefined,
7746
8210
  password: payload.password || undefined,
@@ -7752,17 +8216,24 @@ window.addEventListener('load', function() {
7752
8216
  var doc = iframe.contentDocument;
7753
8217
  if (!doc) {
7754
8218
  resetIframeBindings();
7755
- syncIframeInteractions('iframe-load-no-doc');
8219
+ showIframeLoadError('Unable to load this page. The connection was refused or blocked.');
7756
8220
  return;
7757
8221
  }
7758
8222
  var docUrl = '';
7759
8223
  try { docUrl = String(doc.URL || ''); } catch(_) {}
8224
+ var immediateFail = detectIframeLoadFailure(iframe, doc);
8225
+ if (immediateFail) {
8226
+ showIframeLoadError(immediateFail);
8227
+ return;
8228
+ }
8229
+ hideIframeLoadError();
7760
8230
  emitEditorUrlChanged(iframe.src || docUrl);
7761
8231
  // Stale events: src may already be the proxy URL while the document is still
7762
8232
  // about:blank (e.g. src cleared then reset to force reload). Ask sync path to retry.
7763
8233
  if (docUrl === 'about:blank') {
7764
8234
  resetIframeBindings();
7765
8235
  syncIframeInteractions('iframe-load-about-blank');
8236
+ scheduleIframeLoadErrorCheck(iframe);
7766
8237
  return;
7767
8238
  }
7768
8239
  // If early-paint and final load DOM signatures match, avoid a second full apply/reset
@@ -7790,6 +8261,7 @@ window.addEventListener('load', function() {
7790
8261
  }
7791
8262
  // Always attempt sync; it has its own readiness checks + retry loop.
7792
8263
  syncIframeInteractions('iframe-load');
8264
+ scheduleIframeLoadErrorCheck(iframe);
7793
8265
  });
7794
8266
 
7795
8267
  var sfAdd = document.getElementById('sf-add');