@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.cjs CHANGED
@@ -415,7 +415,7 @@ html,body{height:100%;overflow:hidden;font-family:var(--font-sans);font-size:13p
415
415
  overflow:visible
416
416
  }
417
417
  /* Toolbar layout sections */
418
- .tb-left{display:flex;align-items:center;gap:0;flex-shrink:0}
418
+ .tb-left{display:flex;align-items:center;gap:0;flex:1;min-width:0;max-width:min(560px,46vw);margin-right:8px}
419
419
  .tb-center{display:flex;align-items:center;gap:4px;flex:1;justify-content:center}
420
420
  .tb-right{display:flex;align-items:center;gap:6px;flex-shrink:0;margin-left:auto}
421
421
  /* Logo */
@@ -437,7 +437,7 @@ html,body{height:100%;overflow:hidden;font-family:var(--font-sans);font-size:13p
437
437
  /* Device toggle buttons */
438
438
  .tb-dev-wrap{position:relative;display:flex;align-items:center}
439
439
  .tb-dev-btns{display:flex;align-items:center;gap:1px;padding:8px 4px;background:#F0F0F0;border-radius:7px; height: 30px;width: 116px;}
440
- .tb-dev-3btns{display:flex;align-items:center;gap:2px;padding:8px 4px;background:#F0F0F0;border-radius:7px; height: 30px;width: 100px;}
440
+ .tb-dev-3btns{display:flex;align-items:center;gap:2px;padding:8px 4px;background:#F0F0F0;border-radius:7px; height: 30px;width: auto;}
441
441
  .tb-dev-menu{
442
442
  position:absolute;top:calc(100% + 8px);right:0;z-index:12040;display:none;
443
443
  width:264px;background:#fff;border:1px solid #e5e7eb;border-radius:8px;padding:4px 4px;
@@ -541,7 +541,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
541
541
  }
542
542
  .tb-dev-menu .vp-preset-btn:hover,.tb-dev-menu .vp-preset-btn.active{background:#f4f4f5}
543
543
  /* Dark icon buttons */
544
- .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}
544
+ .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}
545
545
  .tb-dk-btn:hover{color:#e4e4e7;background:rgba(255,255,255,.07)}
546
546
  .tb-dk-btn.active{background:#FFFFFF}
547
547
  /* Dark separator */
@@ -708,6 +708,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
708
708
  background: var(--bg-primary-default, #262626)!important;
709
709
  color: var(--text-primary-default, #FFF)!important;
710
710
  }
711
+ #dom-tree-root .dt-row[draggable="true"]{cursor:grab}
712
+ #dom-tree-root .dt-row.dt-dragging{opacity:.55;cursor:grabbing}
713
+ #dom-tree-root .dt-row.dt-drop-before::after,
714
+ #dom-tree-root .dt-row.dt-drop-after::after{
715
+ content:'';position:absolute;left:8px;right:8px;height:2px;background:#6366f1;pointer-events:none;z-index:1
716
+ }
717
+ #dom-tree-root .dt-row.dt-drop-before::after{top:0}
718
+ #dom-tree-root .dt-row.dt-drop-after::after{bottom:0}
719
+ #dom-tree-root .dt-row.dt-drop-invalid{cursor:not-allowed;opacity:.45}
711
720
  .dt-chev{
712
721
  width:16px;height:16px;flex-shrink:0;border:none;background:transparent;
713
722
  cursor:pointer;color:var(--text-3);display:flex;align-items:center;justify-content:center;
@@ -756,6 +765,21 @@ flex: 1;
756
765
  }
757
766
  #no-url .nu-icon{font-size:48px;opacity:.3}
758
767
 
768
+ /* \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 */
769
+ #iframe-load-error{
770
+ display:none;position:absolute;inset:0;z-index:50;flex-direction:column;
771
+ align-items:center;justify-content:center;gap:12px;text-align:center;padding:40px;
772
+ background:rgba(232,236,240,.94);backdrop-filter:blur(4px)
773
+ }
774
+ #iframe-load-error .nu-icon{font-size:48px;color:#94a3b8}
775
+ #iframe-load-error .ile-title{font-weight:600;font-size:15px;color:var(--text-2)}
776
+ #iframe-load-error .ile-msg{font-size:12px;color:var(--text-3);max-width:360px;line-height:1.5;word-break:break-word}
777
+ #iframe-load-error .ile-reload-btn{
778
+ margin-top:4px;display:inline-flex;align-items:center;gap:6px;padding:8px 14px;border-radius:6px;
779
+ border:1px solid var(--border);background:#fff;color:var(--text);font-size:13px;font-weight:500;cursor:pointer
780
+ }
781
+ #iframe-load-error .ile-reload-btn:hover{background:var(--bg-hover);border-color:#cbd5e1}
782
+
759
783
  /* \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 */
760
784
  #device-frame{
761
785
  width: 100%;
@@ -810,12 +834,32 @@ flex: 1;
810
834
 
811
835
  /* \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 */
812
836
  #url-bar{
813
- flex:1;max-width:280px;background:var(--bg-sub);border:1px solid var(--border);border-radius:20px;
814
- color:var(--text-2);font-size:11px;padding:4px 12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap
837
+ flex: 1;
838
+ max-width: 100%;
839
+ width: 100%;
840
+ border: 1px solid var(--border);
841
+ border-radius: 4px;
842
+ color: var(--text-2);
843
+ font-size: 12px;
844
+ padding: 8px 8px;
845
+ outline: none;
846
+ font-family: inherit;
847
+ overflow: hidden;
848
+ text-overflow: ellipsis;
815
849
  }
850
+ #url-bar::placeholder{color:var(--text-3)}
851
+ #url-bar:focus{border-color:#94a3b8;color:var(--text);background:#fff}
852
+ #url-bar:disabled{opacity:.55;cursor:not-allowed}
816
853
 
817
854
  /* \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 */
818
- .lp-sec{border-bottom:1px solid var(--border);flex-shrink:0;padding: 24px 16px;}
855
+ .lp-sec{ border-bottom: 1px solid var(--border);
856
+ flex-shrink: 0;
857
+ padding: 18px 16px;}
858
+ .lp-sec-hd-url{
859
+ border-bottom: 1px solid var(--border);
860
+ flex-shrink: 0;
861
+ padding: 5px 16px;
862
+ }
819
863
  .lp-sec-no-border{border-bottom:none!important}
820
864
  .lp-sec-hd{
821
865
  margin-bottom: 12px;
@@ -1435,23 +1479,102 @@ select.pr-inp{cursor:pointer;background:#fff}
1435
1479
  max-width:420px;
1436
1480
  line-height:1.5;
1437
1481
  }
1438
- @media (max-width:1100px){
1439
- #app,
1440
- #custom-css-modal{
1441
- display:none!important;
1442
- }
1443
- #ve-min-screen-notice{
1444
- display:flex;
1445
- }
1482
+ html.ve-editor-gated #app,
1483
+ html.ve-editor-gated #custom-css-modal{
1484
+ display:none!important;
1485
+ }
1486
+ html.ve-editor-gated #ve-min-screen-notice{
1487
+ display:flex;
1446
1488
  }
1447
1489
  </style>
1448
1490
  </head>
1449
1491
  <body class="mode-editor">
1450
1492
  <div id="ve-min-screen-notice" aria-live="polite">
1451
1493
  <div class="ve-min-screen-icon"><i class="bi bi-display"></i></div>
1452
- <div class="ve-min-screen-title">Please switch to greater or bigger screen to use Visual Editor</div>
1494
+ <div class="ve-min-screen-title">Please switch to a larger screen to use the Visual Editor</div>
1453
1495
  <div class="ve-min-screen-desc">The Visual Editor requires a screen width of at least 1100px.</div>
1454
1496
  </div>
1497
+ <script>(function(){
1498
+ var MIN_EDITOR_WIDTH=1100;
1499
+ var ZOOM_IN_THRESHOLD=1.05;
1500
+ var ZOOM_MATCH_TOLERANCE=0.06;
1501
+ var STANDARD_BROWSER_ZOOMS=[1.25,1.33,1.5,1.67,1.75,2,2.5,3,4];
1502
+ function markUserZoomInteraction(){
1503
+ try{sessionStorage.setItem('ve-user-zoomed','1');}catch(_){}
1504
+ }
1505
+ function hasUserZoomInteraction(){
1506
+ try{return sessionStorage.getItem('ve-user-zoomed')==='1';}catch(_){return false;}
1507
+ }
1508
+ function matchesStandardBrowserZoom(iw,screenW){
1509
+ if(!iw||!screenW)return false;
1510
+ for(var i=0;i<STANDARD_BROWSER_ZOOMS.length;i++){
1511
+ var z=STANDARD_BROWSER_ZOOMS[i];
1512
+ var impliedFull=iw*z;
1513
+ if(Math.abs(impliedFull-screenW)/screenW<=ZOOM_MATCH_TOLERANCE)return true;
1514
+ }
1515
+ return false;
1516
+ }
1517
+ function isBrowserZoomedIn(iw,screenW){
1518
+ try{
1519
+ if(window.visualViewport&&window.visualViewport.scale>ZOOM_IN_THRESHOLD)return true;
1520
+ }catch(_){}
1521
+ if(!screenW||screenW<MIN_EDITOR_WIDTH||iw>=MIN_EDITOR_WIDTH)return false;
1522
+ if(hasUserZoomInteraction())return true;
1523
+ return matchesStandardBrowserZoom(iw,screenW);
1524
+ }
1525
+ function updateMinScreenGate(){
1526
+ var iw=window.innerWidth||0;
1527
+ var gated=iw<MIN_EDITOR_WIDTH;
1528
+ document.documentElement.classList.toggle('ve-editor-gated',gated);
1529
+ if(!gated){
1530
+ try{sessionStorage.removeItem('ve-user-zoomed');}catch(_){}
1531
+ return;
1532
+ }
1533
+ var notice=document.getElementById('ve-min-screen-notice');
1534
+ if(!notice)return;
1535
+ var titleEl=notice.querySelector('.ve-min-screen-title');
1536
+ var descEl=notice.querySelector('.ve-min-screen-desc');
1537
+ var iconEl=notice.querySelector('.ve-min-screen-icon i');
1538
+ if(!titleEl||!descEl||!iconEl)return;
1539
+ var screenW=(window.screen&&(window.screen.availWidth||window.screen.width))||0;
1540
+ var smallScreen=screenW>0&&screenW<MIN_EDITOR_WIDTH;
1541
+ var zoomedIn=isBrowserZoomedIn(iw,screenW);
1542
+ if(smallScreen){
1543
+ iconEl.className='bi bi-display';
1544
+ titleEl.textContent='Please switch to a larger screen to use the Visual Editor';
1545
+ descEl.textContent='The Visual Editor requires a screen width of at least '+MIN_EDITOR_WIDTH+'px.';
1546
+ }else if(zoomedIn){
1547
+ iconEl.className='bi bi-zoom-out';
1548
+ titleEl.textContent='You have zoomed in too much';
1549
+ descEl.textContent='Zoom out to use the Visual Editor \u2014 press Cmd + - on Mac or Ctrl + - on Windows.';
1550
+ }else{
1551
+ iconEl.className='bi bi-arrows-angle-expand';
1552
+ titleEl.textContent='Widen your browser window';
1553
+ descEl.textContent='The Visual Editor needs at least '+MIN_EDITOR_WIDTH+'px of width. Maximize this window or resize it wider.';
1554
+ }
1555
+ }
1556
+ function onZoomShortcut(e){
1557
+ if(!(e.ctrlKey||e.metaKey))return;
1558
+ var k=e.key||'';
1559
+ if(k==='+'||k==='-'||k==='='||k==='0'||k==='_'||e.code==='Equal'||e.code==='Minus'||e.code==='NumpadAdd'||e.code==='NumpadSubtract'){
1560
+ markUserZoomInteraction();
1561
+ setTimeout(updateMinScreenGate,0);
1562
+ }
1563
+ }
1564
+ function onZoomWheel(e){
1565
+ if(!e.ctrlKey)return;
1566
+ markUserZoomInteraction();
1567
+ setTimeout(updateMinScreenGate,0);
1568
+ }
1569
+ document.addEventListener('keydown',onZoomShortcut,true);
1570
+ window.addEventListener('wheel',onZoomWheel,{passive:true,capture:true});
1571
+ updateMinScreenGate();
1572
+ window.addEventListener('resize',updateMinScreenGate);
1573
+ if(window.visualViewport){
1574
+ window.visualViewport.addEventListener('resize',updateMinScreenGate);
1575
+ window.visualViewport.addEventListener('scroll',updateMinScreenGate);
1576
+ }
1577
+ })();</script>
1455
1578
  <div id="app">
1456
1579
  <!-- \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 -->
1457
1580
  <div id="toolbar">
@@ -1548,16 +1671,13 @@ select.pr-inp{cursor:pointer;background:#fff}
1548
1671
  <div class="tb-dev-3btns">
1549
1672
  <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>
1550
1673
  <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>
1551
- <button class="tb-dk-btn" title="Comments"><i class="bi bi-chat-dots"></i></button>
1674
+ <button class="tb-dk-btn" style="display:none" title="Comments"><i class="bi bi-chat-dots"></i></button>
1552
1675
  </div>
1553
1676
  <button class="tb-sim-btn" id="btn-simulate" onclick="simulateExperiment()">See Preview</button>
1554
1677
 
1555
1678
  <!-- btn-close: kept for JS event listener -->
1556
1679
  <button class="tb-fin-btn" id="btn-close">Exit Editor</button>
1557
1680
  </div>
1558
-
1559
- <!-- url-bar: hidden, kept for JS compatibility -->
1560
- <div id="url-bar" style="display:none" title="">No page loaded</div>
1561
1681
  </div>
1562
1682
 
1563
1683
  <!-- \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 -->
@@ -1565,7 +1685,10 @@ select.pr-inp{cursor:pointer;background:#fff}
1565
1685
 
1566
1686
  <!-- Left panel -->
1567
1687
  <div id="left-panel">
1568
-
1688
+ <div class="lp-sec-hd-url">
1689
+ <!-- Page URL -->
1690
+ <input type="text" id="url-bar" placeholder="Enter URL and press Enter" spellcheck="false" autocomplete="off" aria-label="Page URL" title="" />
1691
+ </div>
1569
1692
  <!-- Variations -->
1570
1693
  <div class="lp-sec">
1571
1694
  <div class="lp-sec-hd">
@@ -1608,7 +1731,7 @@ select.pr-inp{cursor:pointer;background:#fff}
1608
1731
  <!-- Tabs (hidden, kept for JS) -->
1609
1732
  <div class="lp-tabs-container">
1610
1733
  <div class="lp-tabs">
1611
- <div class="lp-tab active" onclick="switchLeftTab('elements')">Elements</div>
1734
+ <div class="lp-tab active" onclick="switchLeftTab('elements')">Added Nodes</div>
1612
1735
  <div class="lp-tab" onclick="switchLeftTab('dom-tree')">DOM Tree</div>
1613
1736
  </div>
1614
1737
  <button class="lp-add-btn" id="btn-add-element" title="Add element">+ Add</button>
@@ -1660,6 +1783,16 @@ select.pr-inp{cursor:pointer;background:#fff}
1660
1783
  <div style="font-size:12px;color:var(--text-3)">Waiting for experiment data&hellip;</div>
1661
1784
  </div>
1662
1785
 
1786
+ <!-- Load error overlay (bad URL, fetch failure, refused to connect) -->
1787
+ <div id="iframe-load-error" aria-live="polite">
1788
+ <div class="nu-icon"><i class="bi bi-wifi-off"></i></div>
1789
+ <div class="ile-title">Page failed to load</div>
1790
+ <div class="ile-msg" id="iframe-load-error-msg">Check the URL and try again.</div>
1791
+ <button type="button" class="ile-reload-btn" id="iframe-load-error-reload">
1792
+ <i class="bi bi-arrow-clockwise"></i> Reload page
1793
+ </button>
1794
+ </div>
1795
+
1663
1796
  <!-- Device frame containing the editing iframe -->
1664
1797
  <div id="device-frame" class="desktop">
1665
1798
  <iframe id="iframeId" name="iframeId" allowfullscreen></iframe>
@@ -1909,12 +2042,17 @@ function renderCroSectionThumb(sec) {
1909
2042
  }
1910
2043
 
1911
2044
  var BASE_COMPONENTS = [
1912
- { 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>' },
2045
+ { 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>' },
2046
+ { 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>' },
2047
+ { 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>' },
2048
+ { 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>' },
2049
+ { 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>' },
2050
+ { 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>' },
1913
2051
  { 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>' },
1914
2052
  { 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">' },
1915
2053
  { 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>' },
1916
2054
  { 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>' },
1917
- { name:'Divider', icon:'bi-dash-lg', defaultAccSection:'background', html:'<hr style="margin:16px 0;border:none;border-top:1px solid #e2e8f0">' },
2055
+ { name:'Divider', icon:'bi-dash-lg', defaultAccSection:'border', html:'<hr style="margin:16px 0;border:none;border-top:1px solid #e2e8f0">' },
1918
2056
  { 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>' },
1919
2057
  { 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>' },
1920
2058
  { 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>' },
@@ -2161,6 +2299,8 @@ var currentSectionComponentsTab = 'components';
2161
2299
  var dragHandleActive = false;
2162
2300
  var domTreeCollapsed = {};
2163
2301
  var domTreeRefreshTimer = null;
2302
+ var domTreeDragState = null;
2303
+ var iframeLoadErrorCheckTimer = null;
2164
2304
  var iframeSyncRetryTimer = null;
2165
2305
  var iframeSyncAttempts = 0;
2166
2306
  var selectionScrollWin = null;
@@ -2646,6 +2786,117 @@ function bindViewportControls() {
2646
2786
  applyViewportFrame();
2647
2787
  }
2648
2788
 
2789
+ function setUrlBarValue(pageUrl) {
2790
+ var urlBar = document.getElementById('url-bar');
2791
+ if (!urlBar) return;
2792
+ var val = pageUrl ? String(pageUrl) : '';
2793
+ urlBar.value = val;
2794
+ urlBar.title = val || 'Enter a page URL and press Enter';
2795
+ }
2796
+
2797
+ /** Keep experimentData.pageUrl and #url-bar in sync when the preview iframe navigates. */
2798
+ function applyUrlBarFromIframeUrl(pageUrl) {
2799
+ if (!pageUrl) return;
2800
+ var normalized = String(pageUrl);
2801
+ if (experimentData) {
2802
+ experimentData = Object.assign({}, experimentData, { pageUrl: normalized });
2803
+ } else {
2804
+ experimentData = { pageUrl: normalized };
2805
+ }
2806
+ setUrlBarValue(normalized);
2807
+ }
2808
+
2809
+ function normalizeUserPageUrl(raw) {
2810
+ var s = String(raw || '').trim();
2811
+ if (!s) return '';
2812
+ if (!/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(s)) s = 'https://' + s;
2813
+ try {
2814
+ return new URL(s).toString();
2815
+ } catch(_) {
2816
+ return '';
2817
+ }
2818
+ }
2819
+
2820
+ function buildProxyUrlForPageUrl(pageUrl, data) {
2821
+ data = data || experimentData || {};
2822
+ var extraTrackingMarkers = Array.isArray(data.trackingMarkers)
2823
+ ? data.trackingMarkers.filter(function(m) { return typeof m === 'string' && m.trim().length > 0; })
2824
+ : [];
2825
+ var conversionProxyBaseUrl = (typeof data.conversionProxyBaseUrl === 'string'
2826
+ ? data.conversionProxyBaseUrl
2827
+ : '').trim().replace(/\\/+$/, '');
2828
+ var proxyPath = '/api/conversion-proxy';
2829
+ var shellOrigin = '';
2830
+ try { shellOrigin = window.location.origin || ''; } catch(_) {}
2831
+ var onWorkerOrigin = !!(conversionProxyBaseUrl && shellOrigin === conversionProxyBaseUrl);
2832
+ var proxyRoot = proxyPath;
2833
+ var trackingMarkersParam = extraTrackingMarkers.length
2834
+ ? '&trackingMarkers=' + encodeURIComponent(JSON.stringify(extraTrackingMarkers))
2835
+ : '';
2836
+ var conversionProxyBaseUrlParam = (!onWorkerOrigin && conversionProxyBaseUrl)
2837
+ ? '&conversionProxyBaseUrl=' + encodeURIComponent(conversionProxyBaseUrl)
2838
+ : '';
2839
+ return proxyRoot + '?password=' + encodeURIComponent(data.editorPassword || '') +
2840
+ '&url=' + encodeURIComponent(pageUrl) +
2841
+ '&strictObserverFreeze=' + encodeURIComponent(data && data.strictObserverFreeze ? '1' : '0') +
2842
+ conversionProxyBaseUrlParam +
2843
+ trackingMarkersParam;
2844
+ }
2845
+
2846
+ function navigateToUserPageUrl(rawUrl) {
2847
+ var pageUrl = normalizeUserPageUrl(rawUrl);
2848
+ if (!pageUrl) {
2849
+ showEditorNotification('Enter a valid URL.', 'error', 2600);
2850
+ setUrlBarValue(experimentData && experimentData.pageUrl);
2851
+ return false;
2852
+ }
2853
+ var prevPageUrl = experimentData && experimentData.pageUrl ? String(experimentData.pageUrl) : '';
2854
+ if (prevPageUrl === pageUrl && lastLoadedProxyUrl) {
2855
+ setUrlBarValue(pageUrl);
2856
+ return true;
2857
+ }
2858
+
2859
+ commitStateChangesForActiveVariation();
2860
+ clearPendingGranularChangesets();
2861
+ varHtmlCache = {};
2862
+ sessionStructuralChainRowsByVarId = {};
2863
+ appliedChangesetSnapshots = {};
2864
+ appliedStructuralChangesetKeys = {};
2865
+ deselectElement();
2866
+
2867
+ experimentData = experimentData
2868
+ ? Object.assign({}, experimentData, { pageUrl: pageUrl })
2869
+ : { pageUrl: pageUrl };
2870
+
2871
+ setUrlBarValue(pageUrl);
2872
+ showNoUrl(false);
2873
+ loadPage(buildProxyUrlForPageUrl(pageUrl, experimentData));
2874
+ return true;
2875
+ }
2876
+
2877
+ function bindUrlBar() {
2878
+ var urlBar = document.getElementById('url-bar');
2879
+ if (!urlBar || urlBar._urlBarBound) return;
2880
+ urlBar._urlBarBound = true;
2881
+ urlBar.addEventListener('keydown', function(e) {
2882
+ if (e.key === 'Enter') {
2883
+ e.preventDefault();
2884
+ navigateToUserPageUrl(urlBar.value);
2885
+ urlBar.blur();
2886
+ } else if (e.key === 'Escape') {
2887
+ e.preventDefault();
2888
+ setUrlBarValue(experimentData && experimentData.pageUrl);
2889
+ urlBar.blur();
2890
+ }
2891
+ });
2892
+ urlBar.addEventListener('blur', function() {
2893
+ var canonical = experimentData && experimentData.pageUrl ? String(experimentData.pageUrl) : '';
2894
+ if (canonical && urlBar.value.trim() !== canonical) {
2895
+ setUrlBarValue(canonical);
2896
+ }
2897
+ });
2898
+ }
2899
+
2649
2900
  // \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
2650
2901
  function switchLeftTab(tab) {
2651
2902
  if (tab !== 'elements' && tab !== 'dom-tree') return;
@@ -3880,34 +4131,11 @@ function handleLoadExperiment(data) {
3880
4131
  var pageUrl = data.pageUrl || '';
3881
4132
  if (!pageUrl) {
3882
4133
  showNoUrl(true);
4134
+ setUrlBarValue('');
3883
4135
  lastLoadedProxyUrl = '';
3884
4136
  return;
3885
4137
  }
3886
- var extraTrackingMarkers = Array.isArray(data && data.trackingMarkers)
3887
- ? data.trackingMarkers.filter(function(m){return typeof m === 'string' && m.trim().length > 0;})
3888
- : [];
3889
- var conversionProxyBaseUrl = (typeof (data && data.conversionProxyBaseUrl) === 'string'
3890
- ? data.conversionProxyBaseUrl
3891
- : '').trim().replace(/\\/+$/, '');
3892
- var proxyPath = '/api/conversion-proxy';
3893
- // Page iframe must stay same-origin as /vvveb-editor for DOM selection/editing.
3894
- // When the shell is served from the worker (conversionProxyBaseUrl origin),
3895
- // use same-origin relative proxy only. Otherwise delegate upstream via query param.
3896
- var shellOrigin = '';
3897
- try { shellOrigin = window.location.origin || ''; } catch(_) {}
3898
- var onWorkerOrigin = !!(conversionProxyBaseUrl && shellOrigin === conversionProxyBaseUrl);
3899
- var proxyRoot = proxyPath;
3900
- var trackingMarkersParam = extraTrackingMarkers.length
3901
- ? '&trackingMarkers=' + encodeURIComponent(JSON.stringify(extraTrackingMarkers))
3902
- : '';
3903
- var conversionProxyBaseUrlParam = (!onWorkerOrigin && conversionProxyBaseUrl)
3904
- ? '&conversionProxyBaseUrl=' + encodeURIComponent(conversionProxyBaseUrl)
3905
- : '';
3906
- var proxyUrl = proxyRoot + '?password=' + encodeURIComponent(data.editorPassword || '') +
3907
- '&url=' + encodeURIComponent(pageUrl) +
3908
- '&strictObserverFreeze=' + encodeURIComponent(data && data.strictObserverFreeze ? '1' : '0') +
3909
- conversionProxyBaseUrlParam +
3910
- trackingMarkersParam;
4138
+ var proxyUrl = buildProxyUrlForPageUrl(pageUrl, data);
3911
4139
 
3912
4140
  // Parent often re-posts load-experiment when React re-renders (new object identity) or
3913
4141
  // after mutations-changed. Reloading the iframe again wipes variant changesets mid-session.
@@ -3925,9 +4153,7 @@ function handleLoadExperiment(data) {
3925
4153
  loadStateChangesForActiveVariation();
3926
4154
  writePersistedActiveVariationId(activeVarId);
3927
4155
  renderVariationTabs();
3928
- var urlBarSkip = document.getElementById('url-bar');
3929
- urlBarSkip.textContent = pageUrl;
3930
- urlBarSkip.title = pageUrl;
4156
+ setUrlBarValue(pageUrl);
3931
4157
  try {
3932
4158
  applyActiveVariationHtml();
3933
4159
  syncIframeInteractions('load-experiment-skip-url');
@@ -3958,9 +4184,7 @@ function handleLoadExperiment(data) {
3958
4184
  writePersistedActiveVariationId(activeVarId);
3959
4185
  renderVariationTabs();
3960
4186
 
3961
- var urlBar = document.getElementById('url-bar');
3962
- urlBar.textContent = pageUrl;
3963
- urlBar.title = pageUrl;
4187
+ setUrlBarValue(pageUrl);
3964
4188
  if (currentMainTab === 'history') renderHistoryTab();
3965
4189
  captureBaselineFromVariations(variations);
3966
4190
  recomputeEditorDirty();
@@ -3969,12 +4193,128 @@ function handleLoadExperiment(data) {
3969
4193
 
3970
4194
  function showNoUrl(show) {
3971
4195
  document.getElementById('no-url').style.display = show ? 'flex' : 'none';
4196
+ var urlBar = document.getElementById('url-bar');
4197
+ if (urlBar) urlBar.disabled = !!show;
3972
4198
  if (show) {
4199
+ hideIframeLoadError();
3973
4200
  detachIframeLoadingListeners();
3974
4201
  setIframePageLoadingUi(false);
3975
4202
  }
3976
4203
  }
3977
4204
 
4205
+ function hideIframeLoadError() {
4206
+ if (iframeLoadErrorCheckTimer) {
4207
+ clearTimeout(iframeLoadErrorCheckTimer);
4208
+ iframeLoadErrorCheckTimer = null;
4209
+ }
4210
+ var el = document.getElementById('iframe-load-error');
4211
+ if (el) el.style.display = 'none';
4212
+ }
4213
+
4214
+ function showIframeLoadError(message) {
4215
+ if (iframeLoadErrorCheckTimer) {
4216
+ clearTimeout(iframeLoadErrorCheckTimer);
4217
+ iframeLoadErrorCheckTimer = null;
4218
+ }
4219
+ var el = document.getElementById('iframe-load-error');
4220
+ var msgEl = document.getElementById('iframe-load-error-msg');
4221
+ if (msgEl) msgEl.textContent = message || 'Check the URL and try again.';
4222
+ if (el) el.style.display = 'flex';
4223
+ setIframePageLoadingUi(false);
4224
+ detachIframeLoadingListeners();
4225
+ deselectElement({ preserveMainTab: true });
4226
+ }
4227
+
4228
+ function detectIframeLoadFailure(iframe, doc) {
4229
+ if (!iframe || !iframe.src || iframe.src === 'about:blank') return null;
4230
+
4231
+ if (!doc) {
4232
+ return 'Unable to load this page. The connection was refused or blocked.';
4233
+ }
4234
+
4235
+ var docUrl = '';
4236
+ try { docUrl = String(doc.URL || ''); } catch(_) {}
4237
+ if (docUrl === 'about:blank') {
4238
+ return 'This page could not be loaded. Check the URL and try again.';
4239
+ }
4240
+ if (
4241
+ docUrl.indexOf('chrome-error://') === 0 ||
4242
+ docUrl.indexOf('about:neterror') === 0
4243
+ ) {
4244
+ return 'This page could not be loaded. Check the URL and try again.';
4245
+ }
4246
+
4247
+ var bodyText = '';
4248
+ try {
4249
+ bodyText = doc.body && doc.body.innerText ? String(doc.body.innerText).trim() : '';
4250
+ } catch(_) {}
4251
+
4252
+ var bodyLower = bodyText.toLowerCase();
4253
+ if (
4254
+ bodyLower.indexOf('refused to connect') >= 0 ||
4255
+ bodyLower.indexOf('err_connection_refused') >= 0 ||
4256
+ bodyLower.indexOf('err_name_not_resolved') >= 0 ||
4257
+ bodyLower.indexOf('err_ssl') >= 0 ||
4258
+ bodyLower.indexOf('err_connection_timed_out') >= 0 ||
4259
+ bodyLower.indexOf("this site can't be reached") >= 0 ||
4260
+ bodyLower.indexOf('this site cant be reached') >= 0 ||
4261
+ bodyLower.indexOf('unable to connect') >= 0 ||
4262
+ bodyLower.indexOf("page isn't working") >= 0 ||
4263
+ bodyLower.indexOf('404 not found') >= 0 && bodyLower.length < 280
4264
+ ) {
4265
+ return 'This page could not be loaded. Check the URL and try again.';
4266
+ }
4267
+
4268
+ if (bodyText.charAt(0) === '{') {
4269
+ try {
4270
+ var parsed = JSON.parse(bodyText);
4271
+ if (parsed && parsed.error) return String(parsed.error);
4272
+ } catch(_) {}
4273
+ }
4274
+
4275
+ try {
4276
+ var ct = doc.contentType || '';
4277
+ if (String(ct).indexOf('application/json') >= 0 && bodyText.charAt(0) === '{') {
4278
+ var parsedCt = JSON.parse(bodyText);
4279
+ if (parsedCt && parsedCt.error) return String(parsedCt.error);
4280
+ }
4281
+ } catch(_) {}
4282
+
4283
+ return null;
4284
+ }
4285
+
4286
+ function scheduleIframeLoadErrorCheck(iframe) {
4287
+ if (iframeLoadErrorCheckTimer) clearTimeout(iframeLoadErrorCheckTimer);
4288
+ iframeLoadErrorCheckTimer = setTimeout(function() {
4289
+ iframeLoadErrorCheckTimer = null;
4290
+ if (!iframe || !iframe.src || iframe.src === 'about:blank') return;
4291
+ var doc = null;
4292
+ try { doc = iframe.contentDocument; } catch(_) {}
4293
+ var failMsg = detectIframeLoadFailure(iframe, doc);
4294
+ if (failMsg) showIframeLoadError(failMsg);
4295
+ }, 450);
4296
+ }
4297
+
4298
+ function reloadCurrentPage() {
4299
+ hideIframeLoadError();
4300
+ if (lastLoadedProxyUrl) {
4301
+ loadPage(lastLoadedProxyUrl);
4302
+ return;
4303
+ }
4304
+ var pageUrl = experimentData && experimentData.pageUrl;
4305
+ if (pageUrl) navigateToUserPageUrl(pageUrl);
4306
+ }
4307
+
4308
+ function bindIframeLoadErrorControls() {
4309
+ var btn = document.getElementById('iframe-load-error-reload');
4310
+ if (!btn || btn._iframeLoadErrorBound) return;
4311
+ btn._iframeLoadErrorBound = true;
4312
+ btn.addEventListener('click', function(e) {
4313
+ e.preventDefault();
4314
+ reloadCurrentPage();
4315
+ });
4316
+ }
4317
+
3978
4318
  function detachIframeLoadingListeners() {
3979
4319
  if (!iframeDocLoadingListeners) return;
3980
4320
  try {
@@ -4348,15 +4688,10 @@ function parseEditorUrlPayload(rawUrl) {
4348
4688
  var lastEditorUrlPayloadKey = '';
4349
4689
  function emitEditorUrlChangedPayload(payload) {
4350
4690
  if (!payload) return;
4691
+ if (payload.url) applyUrlBarFromIframeUrl(payload.url);
4351
4692
  var key = String(payload.url || '') + '|' + String(payload.password || '');
4352
4693
  if (key === lastEditorUrlPayloadKey) return;
4353
4694
  lastEditorUrlPayloadKey = key;
4354
- try {
4355
- console.info('[V2 iframe-url]', {
4356
- url: payload.url || '',
4357
- passwordPresent: !!payload.password,
4358
- });
4359
- } catch(_) {}
4360
4695
  send('editor-url-changed', payload);
4361
4696
  }
4362
4697
  function emitEditorUrlChanged(rawUrl) {
@@ -4365,6 +4700,7 @@ function emitEditorUrlChanged(rawUrl) {
4365
4700
  }
4366
4701
 
4367
4702
  function loadPage(proxyUrl) {
4703
+ hideIframeLoadError();
4368
4704
  showNoUrl(false);
4369
4705
  lastLoadedProxyUrl = proxyUrl;
4370
4706
  var navGen = nextIframeContentNavGen();
@@ -5529,6 +5865,149 @@ function setDomTreeStatus(mode) {
5529
5865
  }
5530
5866
  }
5531
5867
 
5868
+ function canDomTreeMoveElement(el) {
5869
+ if (!el || el.nodeType !== 1 || !el.parentElement) return false;
5870
+ var tag = (el.tagName || '').toLowerCase();
5871
+ if (tag === 'html' || tag === 'body') return false;
5872
+ try {
5873
+ var doc = el.ownerDocument;
5874
+ if (doc && (el === doc.documentElement || el === doc.body)) return false;
5875
+ } catch(_) {}
5876
+ return true;
5877
+ }
5878
+
5879
+ function canDomTreeDropRelativeTo(dragEl, targetEl) {
5880
+ if (!canDomTreeMoveElement(dragEl) || !targetEl || targetEl.nodeType !== 1) return false;
5881
+ if (dragEl === targetEl) return false;
5882
+ try {
5883
+ if (dragEl.contains(targetEl) || targetEl.contains(dragEl)) return false;
5884
+ } catch(_) {
5885
+ return false;
5886
+ }
5887
+ var parent = targetEl.parentElement;
5888
+ if (!parent) return false;
5889
+ if (isDomTreeSkippableTagName(parent.tagName)) return false;
5890
+ return true;
5891
+ }
5892
+
5893
+ function moveDomTreeElementRelativeTo(dragEl, targetEl, insertBefore) {
5894
+ if (!canDomTreeDropRelativeTo(dragEl, targetEl)) return false;
5895
+ var parent = targetEl.parentElement;
5896
+ if (!parent) return false;
5897
+ var ref = insertBefore ? targetEl : targetEl.nextSibling;
5898
+ if (dragEl.parentElement === parent && dragEl === ref) return false;
5899
+ if (!insertBefore && dragEl.parentElement === parent && dragEl === targetEl.nextSibling) return false;
5900
+ try {
5901
+ parent.insertBefore(dragEl, ref);
5902
+ return true;
5903
+ } catch(_) {
5904
+ return false;
5905
+ }
5906
+ }
5907
+
5908
+ function clearDomTreeDropIndicators() {
5909
+ var root = document.getElementById('dom-tree-root');
5910
+ if (!root) return;
5911
+ var rows = root.querySelectorAll('.dt-row');
5912
+ for (var i = 0; i < rows.length; i++) {
5913
+ rows[i].classList.remove('dt-drop-before', 'dt-drop-after', 'dt-drop-invalid', 'dt-dragging');
5914
+ }
5915
+ }
5916
+
5917
+ function cleanupDomTreeDrag() {
5918
+ clearDomTreeDropIndicators();
5919
+ domTreeDragState = null;
5920
+ }
5921
+
5922
+ function attachDomTreeDragHandlers() {
5923
+ var root = document.getElementById('dom-tree-root');
5924
+ if (!root || root._domTreeDragBound) return;
5925
+ root._domTreeDragBound = true;
5926
+
5927
+ root.addEventListener('dragstart', function(e) {
5928
+ if (currentMode !== 'editor') {
5929
+ e.preventDefault();
5930
+ return;
5931
+ }
5932
+ var row = e.target && e.target.closest ? e.target.closest('.dt-row') : null;
5933
+ if (!row || !row._dtEl || (e.target.closest && e.target.closest('.dt-chev'))) {
5934
+ e.preventDefault();
5935
+ return;
5936
+ }
5937
+ if (!canDomTreeMoveElement(row._dtEl)) {
5938
+ e.preventDefault();
5939
+ return;
5940
+ }
5941
+ domTreeDragState = { el: row._dtEl, row: row };
5942
+ row.classList.add('dt-dragging');
5943
+ try {
5944
+ e.dataTransfer.effectAllowed = 'move';
5945
+ e.dataTransfer.setData('text/plain', 'dom-tree');
5946
+ } catch(_) {}
5947
+ suppressClickUntil = Date.now() + 300;
5948
+ });
5949
+
5950
+ root.addEventListener('dragover', function(e) {
5951
+ if (!domTreeDragState) return;
5952
+ e.preventDefault();
5953
+ clearDomTreeDropIndicators();
5954
+ if (domTreeDragState.row) domTreeDragState.row.classList.add('dt-dragging');
5955
+
5956
+ var row = e.target && e.target.closest ? e.target.closest('.dt-row') : null;
5957
+ if (!row || !row._dtEl) {
5958
+ try { e.dataTransfer.dropEffect = 'none'; } catch(_) {}
5959
+ return;
5960
+ }
5961
+
5962
+ var dragEl = domTreeDragState.el;
5963
+ var targetEl = row._dtEl;
5964
+ if (!canDomTreeDropRelativeTo(dragEl, targetEl)) {
5965
+ row.classList.add('dt-drop-invalid');
5966
+ try { e.dataTransfer.dropEffect = 'none'; } catch(_) {}
5967
+ return;
5968
+ }
5969
+
5970
+ var rect = row.getBoundingClientRect();
5971
+ var insertBefore = e.clientY < rect.top + rect.height / 2;
5972
+ row.classList.add(insertBefore ? 'dt-drop-before' : 'dt-drop-after');
5973
+ domTreeDragState.dropRow = row;
5974
+ domTreeDragState.insertBefore = insertBefore;
5975
+ try { e.dataTransfer.dropEffect = 'move'; } catch(_) {}
5976
+ });
5977
+
5978
+ root.addEventListener('drop', function(e) {
5979
+ e.preventDefault();
5980
+ if (!domTreeDragState) return;
5981
+
5982
+ var dragEl = domTreeDragState.el;
5983
+ var row = domTreeDragState.dropRow || (e.target && e.target.closest ? e.target.closest('.dt-row') : null);
5984
+ var moved = false;
5985
+ if (row && row._dtEl && canDomTreeDropRelativeTo(dragEl, row._dtEl)) {
5986
+ var insertBefore = domTreeDragState.insertBefore;
5987
+ if (insertBefore === undefined) {
5988
+ var rect = row.getBoundingClientRect();
5989
+ insertBefore = e.clientY < rect.top + rect.height / 2;
5990
+ }
5991
+ if (moveDomTreeElementRelativeTo(dragEl, row._dtEl, insertBefore)) {
5992
+ moved = true;
5993
+ if (activeVarId) recordReorderAfterDrag(dragEl);
5994
+ saveCurrentVariationHtml();
5995
+ recomputeEditorDirty();
5996
+ selectElement(dragEl);
5997
+ scrollIframeElementIntoView(dragEl);
5998
+ updateSelectionToolbar();
5999
+ scheduleDomTreeRefresh();
6000
+ }
6001
+ }
6002
+ cleanupDomTreeDrag();
6003
+ if (moved) suppressClickUntil = Date.now() + 300;
6004
+ });
6005
+
6006
+ root.addEventListener('dragend', function() {
6007
+ cleanupDomTreeDrag();
6008
+ });
6009
+ }
6010
+
5532
6011
  function domTreePathSegment(el) {
5533
6012
  var tag = el.tagName.toLowerCase();
5534
6013
  var idx = 1;
@@ -5907,6 +6386,7 @@ function renderDomTree(filterRaw) {
5907
6386
  row.onmouseenter = function() {
5908
6387
  setTreeHoverHighlight(el);
5909
6388
  };
6389
+ if (canDomTreeMoveElement(el)) row.draggable = true;
5910
6390
  root.appendChild(row);
5911
6391
 
5912
6392
  if (!hasKids || collapsed) return;
@@ -5928,6 +6408,7 @@ function renderDomTree(filterRaw) {
5928
6408
  root.onmouseleave = function() {
5929
6409
  clearTreeHoverHighlight();
5930
6410
  };
6411
+ attachDomTreeDragHandlers();
5931
6412
  }
5932
6413
 
5933
6414
  // \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
@@ -6356,7 +6837,7 @@ function renderVideoSection(el) {
6356
6837
  }
6357
6838
 
6358
6839
  html += pr('Src', '<input class="pr-inp" id="pp-video-src" type="url" value="'+esc(src)+'" placeholder="https://example.com/video.mp4">');
6359
- html += pr('Poster', '<input class="pr-inp" id="pp-video-poster" type="url" value="'+esc(el.getAttribute('poster')||'')+'" placeholder="https://\u2026">');
6840
+ html += pr('Thumbnail', '<input class="pr-inp" id="pp-video-poster" type="url" value="'+esc(el.getAttribute('poster')||'')+'" placeholder="https://\u2026">');
6360
6841
 
6361
6842
  document.getElementById('acc-body-video').innerHTML = html;
6362
6843
 
@@ -6623,11 +7104,6 @@ function renderRightPanel(el, options) {
6623
7104
  // \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
6624
7105
  var contentHtml = '';
6625
7106
  if (isLinkElement(el)) contentHtml += buildAnchorContentFieldsHtml(el);
6626
- if (isFormControlElement(el)) {
6627
- contentHtml +=
6628
- subLbl('Value') +
6629
- '<textarea class="pr-inp" id="pp-value" style="width:100%;min-height:44px;margin-bottom:8px">'+esc(getFormControlValue(el))+'</textarea>';
6630
- }
6631
7107
  if (tag==='input' || tag==='textarea') {
6632
7108
  contentHtml +=
6633
7109
  subLbl('Placeholder') +
@@ -6740,17 +7216,13 @@ function renderRightPanel(el, options) {
6740
7216
  function wireContentFieldSync(el, sel) {
6741
7217
  var textInp = document.getElementById('pp-text');
6742
7218
  var htmlInp = document.getElementById('pp-html');
6743
- var valueInp = document.getElementById('pp-value');
6744
7219
  var syncing = false;
6745
7220
  function applyContentChange(changedId) {
6746
7221
  if (syncing) return;
6747
7222
  syncing = true;
6748
7223
  try {
6749
7224
  var orig = getOriginalValue(changedId, el);
6750
- if (changedId === 'pp-value') {
6751
- el.value = valueInp.value;
6752
- logChange(sel, 'pp-value', valueInp.value, el, orig);
6753
- } else if (changedId === 'pp-text') {
7225
+ if (changedId === 'pp-text') {
6754
7226
  el.innerText = textInp.value;
6755
7227
  if (htmlInp) htmlInp.value = el.innerHTML;
6756
7228
  logChange(sel, 'pp-text', textInp.value, el, orig);
@@ -6763,10 +7235,6 @@ function wireContentFieldSync(el, sel) {
6763
7235
  syncing = false;
6764
7236
  }
6765
7237
  }
6766
- if (valueInp && isFormControlElement(el)) {
6767
- valueInp.addEventListener('input', function() { applyContentChange('pp-value'); });
6768
- valueInp.addEventListener('change', function() { applyContentChange('pp-value'); });
6769
- }
6770
7238
  if (textInp) {
6771
7239
  textInp.addEventListener('input', function() { applyContentChange('pp-text'); });
6772
7240
  textInp.addEventListener('change', function() { applyContentChange('pp-text'); });
@@ -7727,6 +8195,8 @@ function registerCROSections() {
7727
8195
  window.addEventListener('load', function() {
7728
8196
  registerCROSections();
7729
8197
  bindViewportControls();
8198
+ bindUrlBar();
8199
+ bindIframeLoadErrorControls();
7730
8200
  // switchSectionComponentsTab(currentSectionComponentsTab);
7731
8201
  renderElementsTree(document.getElementById('comp-search').value);
7732
8202
  vvvebReady = true;
@@ -7741,14 +8211,8 @@ window.addEventListener('load', function() {
7741
8211
  try {
7742
8212
  var d = ev && ev.data;
7743
8213
  if (!d || d.channel !== 'vvveb-proxy-url' || d.type !== 'editor-url-changed') return;
7744
- if (!iframe || !iframe.contentWindow || ev.source !== iframe.contentWindow) return;
7745
8214
  var payload = d.payload || {};
7746
- try {
7747
- console.info('[V2 iframe-url bridge]', {
7748
- url: payload.url || '',
7749
- passwordPresent: !!payload.password,
7750
- });
7751
- } catch(_) {}
8215
+ if (payload.url) applyUrlBarFromIframeUrl(payload.url);
7752
8216
  emitEditorUrlChangedPayload({
7753
8217
  url: payload.url || undefined,
7754
8218
  password: payload.password || undefined,
@@ -7760,17 +8224,24 @@ window.addEventListener('load', function() {
7760
8224
  var doc = iframe.contentDocument;
7761
8225
  if (!doc) {
7762
8226
  resetIframeBindings();
7763
- syncIframeInteractions('iframe-load-no-doc');
8227
+ showIframeLoadError('Unable to load this page. The connection was refused or blocked.');
7764
8228
  return;
7765
8229
  }
7766
8230
  var docUrl = '';
7767
8231
  try { docUrl = String(doc.URL || ''); } catch(_) {}
8232
+ var immediateFail = detectIframeLoadFailure(iframe, doc);
8233
+ if (immediateFail) {
8234
+ showIframeLoadError(immediateFail);
8235
+ return;
8236
+ }
8237
+ hideIframeLoadError();
7768
8238
  emitEditorUrlChanged(iframe.src || docUrl);
7769
8239
  // Stale events: src may already be the proxy URL while the document is still
7770
8240
  // about:blank (e.g. src cleared then reset to force reload). Ask sync path to retry.
7771
8241
  if (docUrl === 'about:blank') {
7772
8242
  resetIframeBindings();
7773
8243
  syncIframeInteractions('iframe-load-about-blank');
8244
+ scheduleIframeLoadErrorCheck(iframe);
7774
8245
  return;
7775
8246
  }
7776
8247
  // If early-paint and final load DOM signatures match, avoid a second full apply/reset
@@ -7798,6 +8269,7 @@ window.addEventListener('load', function() {
7798
8269
  }
7799
8270
  // Always attempt sync; it has its own readiness checks + retry loop.
7800
8271
  syncIframeInteractions('iframe-load');
8272
+ scheduleIframeLoadErrorCheck(iframe);
7801
8273
  });
7802
8274
 
7803
8275
  var sfAdd = document.getElementById('sf-add');