<!DOCTYPE html>
<html lang="{{ get_admin_language() }}" dir="{{ 'rtl' if get_admin_language() in ('ar', 'he') else 'ltr' }}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>{{ t('builder.page_builder') }} — {{ page.title }} — AliothPress</title>

<!-- Builder CSS (self-contained) -->
<link rel="stylesheet" href="/static/css/page-builder-editor.css">
<link rel="stylesheet" href="/static/css/components.css">
<link rel="stylesheet" href="/static/css/page-builder-blocks.css">
<style>{{ theme_css | safe }}</style>

</head>
<body data-theme="{{ current_user.admin_theme or 'light' }}">
<script>
(function(){
    var t = document.body.getAttribute('data-theme');
    if (t === 'system') {
        document.body.setAttribute('data-theme',
            window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
        window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function(e) {
            document.body.setAttribute('data-theme', e.matches ? 'dark' : 'light');
        });
    }
})();
</script>

<!-- ====== TOP BAR ====== -->
<div class="pb-topbar">
  <div class="pb-topbar__left">
    <a href="{{ url_for('admin_pages_edit', page_id=page.id) }}" class="pb-btn pb-btn--ghost" title="{{ t('builder.back_to_editor') }}">
      <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><polyline points="15 18 9 12 15 6"/></svg>
    </a>
    <span class="pb-topbar__title">{{ t('builder.page_builder') }}</span>
    <span class="pb-topbar__sep">·</span>
    <span class="pb-topbar__page-title">{{ page.title }}</span>
  </div>
  <div class="pb-topbar__right">
    <button class="pb-btn pb-btn--ghost pb-topbar__more" id="pbTopbarMore" aria-label="Menu">
      <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="5" r="2"/><circle cx="12" cy="12" r="2"/><circle cx="12" cy="19" r="2"/></svg>
    </button>
    <div class="pb-topbar__actions" id="pbTopbarActions">
    <button class="pb-btn pb-btn--outline pb-btn--ai" id="pbAIBtn" onclick="openBuilderAI()">
      <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M12 2a4 4 0 014 4c0 1.95-1.4 3.57-3.25 3.92L12 10V8a2 2 0 10-2-2H8a4 4 0 014-4z"/><path d="M8.5 14.5L5 18"/><path d="M15.5 14.5L19 18"/><circle cx="12" cy="14" r="4"/></svg>
      🤖 AI
    </button>
    <span class="pb-theme-badge" title="{{ active_theme }}">
      <span class="pb-theme-badge__dot"></span>
      {{ active_theme | replace('-', ' ') | title }}
    </span>
    <button class="pb-btn pb-btn--outline" id="pbThemeToggle" onclick="toggleCanvasTheme()" title="{{ t('builder.toggle_preview') }}" aria-label="{{ t('builder.toggle_preview') }}">
      <svg id="pbThemeIconSun" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>
      <svg id="pbThemeIconMoon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z"/></svg>
    </button>
    <a href="{{ url_for('admin_pages_preview', page_id=page.id) }}" target="_blank" class="pb-btn pb-btn--outline">
      <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
      {{ t('builder.preview') }}
    </a>
    </div>
    <button class="pb-btn pb-btn--ghost" id="pbUndo" disabled title="{{ t('builder.undo') }}">
      <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 105.64-8.36L1 10"/></svg>
    </button>
    <button class="pb-btn pb-btn--ghost" id="pbRedo" disabled title="{{ t('builder.redo') }}">
      <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 11-5.64-8.36L23 10"/></svg>
    </button>
    <button class="pb-btn pb-btn--primary" id="pbSave">
      <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M19 21H5a2 2 0 01-2-2V5a2 2 0 012-2h11l5 5v11a2 2 0 01-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>
      {{ t('builder.save') }}
    </button>
  </div>
</div>

<!-- ====== MAIN LAYOUT ====== -->
<div class="pb-layout">

  <!-- LEFT SIDEBAR — Block palette -->
  <div class="pb-sidebar" role="navigation" aria-label="{{ t('builder.page_builder') }}">
    <div class="pb-sidebar__section">
      <div class="pb-sidebar__title">{{ t('builder.basic') }}</div>
      <div class="pb-block-grid">
        <button class="pb-block-btn" data-block-type="heading">
          <svg viewBox="0 0 24 24"><path d="M4 4v16"/><path d="M4 12h12"/><path d="M16 4v16"/></svg>
          {{ t('builder.heading') }}
        </button>
        <button class="pb-block-btn" data-block-type="text">
          <svg viewBox="0 0 24 24"><path d="M4 7V4h16v3"/><path d="M9 20h6"/><path d="M12 4v16"/></svg>
          {{ t('builder.text') }}
        </button>
        <button class="pb-block-btn" data-block-type="image">
          <svg viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>
          {{ t('builder.image') }}
        </button>
        <button class="pb-block-btn" data-block-type="button">
          <svg viewBox="0 0 24 24"><rect x="2" y="7" width="20" height="10" rx="2"/><path d="M9 12h6"/></svg>
          {{ t('builder.button') }}
        </button>
        <button class="pb-block-btn" data-block-type="divider">
          <svg viewBox="0 0 24 24"><path d="M3 12h18"/></svg>
          {{ t('builder.divider') }}
        </button>
        <button class="pb-block-btn" data-block-type="spacer">
          <svg viewBox="0 0 24 24"><path d="M12 5v14"/><path d="M5 5h14"/><path d="M5 19h14"/></svg>
          {{ t('builder.spacer') }}
        </button>
      </div>
    </div>

    <div class="pb-sidebar__section">
      <div class="pb-sidebar__title">{{ t('builder.structure') }}</div>
      <div class="pb-block-grid">
        <button class="pb-block-btn" data-block-type="columns">
          <svg viewBox="0 0 24 24"><rect x="3" y="3" width="7" height="18"/><rect x="14" y="3" width="7" height="18"/></svg>
          {{ t('builder.columns') }}
        </button>
        <button class="pb-block-btn" data-block-type="section">
          <svg viewBox="0 0 24 24"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="M2 8h20"/></svg>
          {{ t('builder.section') }}
        </button>
      </div>
    </div>

    <div class="pb-sidebar__section">
      <div class="pb-sidebar__title">{{ t('builder.media') }}</div>
      <div class="pb-block-grid">
        <button class="pb-block-btn" data-block-type="video">
          <svg viewBox="0 0 24 24"><polygon points="5 3 19 12 5 21 5 3"/></svg>
          {{ t('builder.video') }}
        </button>
        <button class="pb-block-btn" data-block-type="gallery">
          <svg viewBox="0 0 24 24"><rect x="2" y="2" width="8" height="8" rx="1"/><rect x="14" y="2" width="8" height="8" rx="1"/><rect x="2" y="14" width="8" height="8" rx="1"/><rect x="14" y="14" width="8" height="8" rx="1"/></svg>
          {{ t('builder.gallery') }}
        </button>
        <button class="pb-block-btn" data-block-type="video_gallery">
          <svg viewBox="0 0 24 24"><rect x="1" y="3" width="9" height="7" rx="1"/><polygon points="5 5.5 8 7 5 8.5" fill="currentColor" stroke="none"/><rect x="14" y="3" width="9" height="7" rx="1"/><polygon points="18 5.5 21 7 18 8.5" fill="currentColor" stroke="none"/><rect x="1" y="14" width="9" height="7" rx="1"/><polygon points="5 16.5 8 18 5 19.5" fill="currentColor" stroke="none"/><rect x="14" y="14" width="9" height="7" rx="1"/><polygon points="18 16.5 21 18 18 19.5" fill="currentColor" stroke="none"/></svg>
          {{ t('builder.video_gallery') }}
        </button>
        <button class="pb-block-btn" data-block-type="audio">
          <svg viewBox="0 0 24 24"><path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/></svg>
          {{ t('builder.audio') }}
        </button>
        <button class="pb-block-btn" data-block-type="slideshow">
          <svg viewBox="0 0 24 24"><rect x="2" y="4" width="20" height="16" rx="2"/><polyline points="7 15 9 12 7 9"/><polyline points="17 9 15 12 17 15"/><circle cx="12" cy="20" r="1" fill="currentColor"/><circle cx="9" cy="20" r="0.7" fill="currentColor"/><circle cx="15" cy="20" r="0.7" fill="currentColor"/></svg>
          {{ t('builder.slideshow') }}
        </button>
        <button class="pb-block-btn" data-block-type="ticker">
          <svg viewBox="0 0 24 24"><rect x="1" y="7" width="5" height="10" rx="1"/><rect x="8" y="7" width="5" height="10" rx="1"/><rect x="15" y="7" width="5" height="10" rx="1"/><path d="M3 21h18"/><polyline points="18 20 21 21 18 22"/></svg>
          {{ t('builder.ticker') }}
        </button>
      </div>
    </div>

    <div class="pb-sidebar__section">
      <div class="pb-sidebar__title">{{ t('builder.advanced') }}</div>
      <div class="pb-block-grid">
        <button class="pb-block-btn" data-block-type="quote">
          <svg viewBox="0 0 24 24"><path d="M3 21c3 0 7-1 7-8V5c0-1.25-.756-2.017-2-2H4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2 1 0 1 0 1 1v1c0 1-1 2-2 2s-1 .008-1 1.031V21z"/><path d="M15 21c3 0 7-1 7-8V5c0-1.25-.757-2.017-2-2h-4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2h.75c0 2.25.25 4-2.75 4v3z"/></svg>
          {{ t('builder.quote') }}
        </button>
        <button class="pb-block-btn" data-block-type="table">
          <svg viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18"/><path d="M3 15h18"/><path d="M9 3v18"/></svg>
          {{ t('builder.table') }}
        </button>
        <button class="pb-block-btn" data-block-type="accordion">
          <svg viewBox="0 0 24 24"><path d="M3 6h18"/><path d="M3 12h18"/><path d="M3 18h18"/><polyline points="15 8 18 6 15 4"/></svg>
          {{ t('builder.accordion') }}
        </button>
        <button class="pb-block-btn" data-block-type="tabs">
          <svg viewBox="0 0 24 24"><rect x="3" y="7" width="18" height="14" rx="2"/><path d="M3 7V5a2 2 0 012-2h4l2 4"/><path d="M11 3h8a2 2 0 012 2v2"/></svg>
          {{ t('builder.tabs') }}
        </button>
        <button class="pb-block-btn" data-block-type="html">
          <svg viewBox="0 0 24 24"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>
          {{ t('builder.html') }}
        </button>
        <button class="pb-block-btn" data-block-type="code">
          <svg viewBox="0 0 24 24"><rect x="4" y="4" width="16" height="16" rx="2"/><path d="M9 10l-2 2 2 2"/><path d="M15 10l2 2-2 2"/></svg>
          {{ t('builder.code') }}
        </button>
        <button class="pb-block-btn" data-block-type="form">
          <svg viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M7 7h10"/><path d="M7 12h10"/><path d="M7 17h4"/></svg>
          {{ t('builder.form') }}
        </button>
      </div>
    </div>
  </div>

  <!-- CENTER CANVAS -->
  <div class="pb-canvas {% if current_user.admin_theme == 'dark' %}pb-canvas--theme-dark{% else %}pb-canvas--theme-light{% endif %}" id="pbCanvas" role="main" aria-label="{{ t('builder.canvas') }}">
    <div class="pb-canvas__inner" id="pbBlocks" role="list">
      <!-- Blocks rendered here by JS -->
    </div>
  </div>

  <!-- RIGHT PANEL — Block settings -->
  <div class="pb-panel" id="pbPanel" role="complementary" aria-label="{{ t('builder.block_settings') }}">
    <div class="pb-panel__header">
      <span class="pb-panel__title" id="pbPanelTitle">{{ t('builder.block_settings') }}</span>
      <button class="pb-panel__close" id="pbPanelClose" title="{{ t('common.close') }}" aria-label="{{ t('common.close') }}">
        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
      </button>
    </div>
    <div class="pb-panel__body" id="pbPanelBody">
      <!-- Dynamic settings form rendered here -->
    </div>
  </div>

</div>

<!-- Toast notification -->
<div class="pb-toast" id="pbToast"></div>

<!-- ====== CANVAS THEME TOGGLE ====== -->
<script>
(function() {
  var canvas = document.getElementById('pbCanvas');
  var iconSun = document.getElementById('pbThemeIconSun');
  var iconMoon = document.getElementById('pbThemeIconMoon');
  
  function updateIcons() {
    var isDark = document.body.getAttribute('data-theme') === 'dark';
    iconSun.style.display = isDark ? 'block' : 'none';
    iconMoon.style.display = isDark ? 'none' : 'block';
  }
  
  function syncCanvasToBody() {
    var isDark = document.body.getAttribute('data-theme') === 'dark';
    canvas.classList.toggle('pb-canvas--theme-dark', isDark);
    canvas.classList.toggle('pb-canvas--theme-light', !isDark);
    updateIcons();
  }
  
  /* Handle system preference */
  if ('{{ current_user.admin_theme or "light" }}' === 'system') {
    window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function() {
      syncCanvasToBody();
    });
  }
  
  /* Initial sync */
  syncCanvasToBody();
  
  /* Toggle: switch entire builder + canvas together */
  window.toggleCanvasTheme = function() {
    var isDark = document.body.getAttribute('data-theme') === 'dark';
    document.body.setAttribute('data-theme', isDark ? 'light' : 'dark');
    syncCanvasToBody();
  };
})();
</script>

<!-- ====== PAGE BUILDER JS ====== -->
<script>
/* i18n translations for builder JS — passed from Python as safe JSON */
var PB_I18N = {{ builder_i18n_json | safe }};

/* Safety net: if a key is missing from builder_i18n_json, return the key name
   (e.g. "gallery_images" instead of undefined). Prevents "undefined" in UI. */
if (typeof Proxy !== 'undefined') {
  PB_I18N = new Proxy(PB_I18N, {
    get: function(target, key) {
      if (key in target) return target[key];
      /* Convert snake_case key to readable: gallery_images → "gallery images" */
      return String(key).replace(/_/g, ' ');
    }
  });
}

(function() {
  'use strict';

  // ============================================================
  // CSRF PROTECTION — auto-add token to all fetch requests
  // ============================================================
  var _csrfMeta = document.querySelector('meta[name="csrf-token"]');
  var _csrfToken = _csrfMeta ? _csrfMeta.getAttribute('content') : '';
  var _origFetch = window.fetch;
  window.fetch = function(url, opts) {
    opts = opts || {};
    if (opts.method && ['POST','PUT','DELETE','PATCH'].indexOf(opts.method.toUpperCase()) !== -1) {
      opts.headers = opts.headers || {};
      if (!opts.headers['X-CSRFToken']) opts.headers['X-CSRFToken'] = _csrfToken;
    }
    return _origFetch.call(this, url, opts);
  };

  // ============================================================
  // STATE
  // ============================================================
  var PAGE_ID = {{ page.id }};
  var state = {
    blocks: [],
    version: 1,
    selectedId: null,
    isDirty: false
  };

  // DOM refs
  var $blocks = document.getElementById('pbBlocks');
  var $canvas = document.getElementById('pbCanvas');
  var $panel = document.getElementById('pbPanel');
  var $panelTitle = document.getElementById('pbPanelTitle');
  var $panelBody = document.getElementById('pbPanelBody');
  var $toast = document.getElementById('pbToast');
  var $save = document.getElementById('pbSave');

  // ============================================================
  // BLOCK DEFAULTS — default data for each block type
  // ============================================================
  var BLOCK_DEFAULTS = {
    heading:   { text: PB_I18N.default_heading || '', level: 2 },
    text:      { html: '<p>' + (PB_I18N.default_text || '') + '</p>' },
    image:     { url: '', alt: '', caption: '', link_url: '', size: 'full' },
    button:    { text: PB_I18N.default_button || '', url: '#', style: 'primary', target: '_self', size: 'md' },
    divider:   { style: 'solid' },
    spacer:    { height: '48' },
    columns:   { layout: '1/2+1/2', columns: [{ blocks: [] }, { blocks: [] }] },
    section:   { background_type: 'color', background_value: '', background_image: '', background_overlay: '', background_type_dark: 'color', background_value_dark: '', background_overlay_dark: '', padding: 'lg', text_color: 'auto', min_height: 'auto', full_width: false, content_width: 'default', blocks: [] },
    video:     { source: 'youtube', url: '', embed_id: '', aspect_ratio: '16/9', title: '' },
    gallery:   { images: [], layout: 'grid', columns: 3, gap: 'md', lightbox: true },
    video_gallery: { videos: [], columns: 2, gap: 'md' },
    html:      { code: '<!-- HTML -->' },
    table:     { headers: ['A', 'B', 'C'], rows: [['', '', '']], striped: true, responsive: true, table_width: 'full' },
    accordion: { items: [{ title: (PB_I18N.default_accordion_title || ''), content: '<p></p>' }], allow_multiple: false },
    tabs:      { tabs: [{ title: (PB_I18N.default_tab || '') + ' 1', content: '<p></p>' }, { title: (PB_I18N.default_tab || '') + ' 2', content: '<p></p>' }] },
    audio:     { url: '', title: '' },
    slideshow: { mode: 'hero', slides: [{ image: '', alt: '', blocks: [] }], animation: 'slide', autoplay: true, interval: 5, show_arrows: true, show_dots: true, aspect_ratio: '16/9', overlay: '', padding: 'lg', text_color: 'light', full_width: false, content_width: 'default' },
    quote:     { text: '', author: '', style: 'default' },
    code:      { code: '', language: 'javascript' },
    form:      { form_id: '', show_title: true, show_description: true },
    ticker:    { images: [], speed: 'medium', pause_on_hover: true, grayscale: true, height: 'md', direction: 'rtl' }
  };

  var BLOCK_LABELS = {
    heading: PB_I18N.heading, text: PB_I18N.text, image: PB_I18N.image, button: PB_I18N.button,
    divider: PB_I18N.divider, spacer: PB_I18N.spacer, columns: PB_I18N.columns, section: PB_I18N.section,
    video: PB_I18N.video, gallery: PB_I18N.gallery, video_gallery: PB_I18N.video_gallery, html: PB_I18N.html, table: PB_I18N.table,
    accordion: PB_I18N.accordion, tabs: PB_I18N.tabs, audio: PB_I18N.audio, quote: PB_I18N.quote,
    code: PB_I18N.code, form: PB_I18N.form, slideshow: PB_I18N.slideshow, ticker: PB_I18N.ticker
  };

  // ============================================================
  // HELPERS
  // ============================================================
  function uuid() {
    return 'b' + Math.random().toString(36).substr(2, 9);
  }

  function esc(s) {
    var d = document.createElement('div');
    d.textContent = s || '';
    return d.innerHTML;
  }

  function stripTags(s) {
    if (!s || typeof s !== 'string') return s || '';
    var d = document.createElement('div');
    d.innerHTML = s;
    return d.textContent || '';
  }

  function showToast(msg, type) {
    $toast.textContent = msg;
    $toast.className = 'pb-toast is-visible' + (type ? ' pb-toast--' + type : '');
    setTimeout(function() { $toast.className = 'pb-toast'; }, 2500);
  }

  function markDirty() {
    state.isDirty = true;
    $save.textContent = PB_I18N.save + ' *';
  }

  // ============================================================
  // UNDO / REDO — snapshot-based history
  // ============================================================
  var _undoStack = [];
  var _redoStack = [];
  var _undoMax = 50;
  var _isRestoring = false;
  var _lastSavedState = null;
  var _undoDebounce = null;

  function _saveUndoState() {
    if (_isRestoring) return;
    clearTimeout(_undoDebounce);
    var current = JSON.stringify(state.blocks);
    if (_lastSavedState !== null && _lastSavedState !== current) {
      _undoStack.push(_lastSavedState);
      if (_undoStack.length > _undoMax) _undoStack.shift();
      _redoStack = [];
    }
    _lastSavedState = current;
    _updateUndoButtons();
  }

  function _saveUndoDebounced() {
    if (_isRestoring) return;
    clearTimeout(_undoDebounce);
    _undoDebounce = setTimeout(_saveUndoState, 800);
  }

  function _undo() {
    if (_undoStack.length === 0) return;
    _isRestoring = true;
    _redoStack.push(JSON.stringify(state.blocks));
    state.blocks = JSON.parse(_undoStack.pop());
    _lastSavedState = JSON.stringify(state.blocks);
    state.isDirty = true;
    $save.textContent = PB_I18N.save + ' *';
    renderCanvas();
    closePanel();
    _isRestoring = false;
    _updateUndoButtons();
  }

  function _redo() {
    if (_redoStack.length === 0) return;
    _isRestoring = true;
    _undoStack.push(JSON.stringify(state.blocks));
    state.blocks = JSON.parse(_redoStack.pop());
    _lastSavedState = JSON.stringify(state.blocks);
    state.isDirty = true;
    $save.textContent = PB_I18N.save + ' *';
    renderCanvas();
    closePanel();
    _isRestoring = false;
    _updateUndoButtons();
  }

  function _updateUndoButtons() {
    var u = document.getElementById('pbUndo');
    var r = document.getElementById('pbRedo');
    if (u) u.disabled = _undoStack.length === 0;
    if (r) r.disabled = _redoStack.length === 0;
  }

  // Patch markDirty to auto-save undo state (debounced for typing)
  var _origMarkDirty = markDirty;
  markDirty = function() {
    _saveUndoDebounced();
    _origMarkDirty();
  };

  // Keyboard shortcuts
  document.addEventListener('keydown', function(e) {
    if ((e.ctrlKey || e.metaKey) && e.key === 'z' && !e.shiftKey) {
      e.preventDefault();
      _undo();
    }
    if ((e.ctrlKey || e.metaKey) && (e.key === 'y' || (e.key === 'z' && e.shiftKey))) {
      e.preventDefault();
      _redo();
    }
  });

  // Undo/Redo button clicks
  document.getElementById('pbUndo').addEventListener('click', _undo);
  document.getElementById('pbRedo').addEventListener('click', _redo);

  // Topbar mobile menu toggle
  (function() {
    var moreBtn = document.getElementById('pbTopbarMore');
    var actions = document.getElementById('pbTopbarActions');
    if (moreBtn && actions) {
      moreBtn.addEventListener('click', function(e) {
        e.stopPropagation();
        actions.classList.toggle('is-open');
      });
      document.addEventListener('click', function(e) {
        if (!actions.contains(e.target) && e.target !== moreBtn) {
          actions.classList.remove('is-open');
        }
      });
    }
  })();

  // ============================================================
  // LOAD blocks from API
  // ============================================================
  function loadBlocks() {
    console.log('[PageBuilder] Loading blocks for page', PAGE_ID);
    fetch('/api/pages/' + PAGE_ID + '/blocks')
      .then(function(r) { 
        console.log('[PageBuilder] API response status:', r.status);
        return r.json(); 
      })
      .then(function(data) {
        console.log('[PageBuilder] API data:', data);
        if (data.success && data.blocks) {
          state.blocks = data.blocks.blocks || [];
          state.version = data.blocks.version || 1;
          console.log('[PageBuilder] Loaded', state.blocks.length, 'blocks');
          _lastSavedState = JSON.stringify(state.blocks);
        }
        renderCanvas();
      })
      .catch(function(err) {
        console.error('[PageBuilder] Load error:', err);
        showToast(PB_I18N.save_failed, 'error');
        renderCanvas();
      });
  }

  // ============================================================
  // SAVE blocks to API
  // ============================================================
  function saveBlocks() {
    $save.disabled = true;
    $save.textContent = PB_I18N.saving;

    // Strip transient preview data from form blocks (fields live in Form model)
    var cleanBlocks = JSON.parse(JSON.stringify(state.blocks));
    function stripFormPreview(blocks) {
      (blocks || []).forEach(function(b) {
        if (b.type === 'form' && b.data) {
          delete b.data._fields;
          delete b.data._settings;
          delete b.data._description;
          delete b.data._form_title_raw;
          delete b.data._form_title;
        }
        if (b.type === 'columns' && b.data && b.data.columns) {
          b.data.columns.forEach(function(col) { stripFormPreview(col.blocks); });
        }
        if (b.type === 'section' && b.data && b.data.blocks) {
          stripFormPreview(b.data.blocks);
        }
        if (b.type === 'slideshow' && b.data && b.data.slides) {
          b.data.slides.forEach(function(s) { stripFormPreview(s.blocks); });
        }
      });
    }
    stripFormPreview(cleanBlocks);

    fetch('/api/pages/' + PAGE_ID + '/blocks', {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        blocks: { blocks: cleanBlocks, version: state.version }
      })
    })
    .then(function(r) { return r.json(); })
    .then(function(data) {
      if (data.success) {
        state.isDirty = false;
        $save.textContent = PB_I18N.save;
        showToast(PB_I18N.saved, 'success');
      } else {
        showToast(PB_I18N.save_failed + ': ' + (data.error || ''), 'error');
        $save.textContent = PB_I18N.save + ' *';
      }
    })
    .catch(function() {
      showToast(PB_I18N.network_error, 'error');
      $save.textContent = PB_I18N.save + ' *';
    })
    .finally(function() {
      $save.disabled = false;
    });
  }

  // ============================================================
  // RENDER CANVAS — draws all blocks
  // ============================================================
  function _inserterHTML(index) {
    return '<div class="pb-inserter" data-insert-at="' + index + '">' +
      '<div class="pb-inserter__line"></div>' +
      '<button class="pb-inserter__btn" title="' + (PB_I18N.add_block || 'Add block') + '">' +
      '<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>' +
      '</button></div>';
  }

  // Build proxy URL for a video thumbnail. All YouTube/Vimeo thumbnails go
  // through our own server — no img.youtube.com calls from the admin browser.
  function videoThumbUrl(source, id) {
    if (!source || !id) return '';
    return '/media/video-thumb/' + esc(source) + '/' + esc(id) + '.jpg';
  }

  function renderCanvas() {
    if (state.blocks.length === 0) {
      $blocks.innerHTML =
        '<div class="pb-canvas__empty" id="pbEmpty">' +
        '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/></svg>' +
        '<span>' + PB_I18N.drag_hint + '</span>' +
        '</div>';
      return;
    }

    var html = _inserterHTML(0);
    state.blocks.forEach(function(block, i) {
      html += renderBlockItem(block);
      html += _inserterHTML(i + 1);
    });
    $blocks.innerHTML = html;

    // Re-select if was selected
    if (state.selectedId) {
      var el = $blocks.querySelector('[data-id="' + state.selectedId + '"]');
      if (el) el.classList.add('is-selected');
    }

    // Bind column nested block events
    bindColumnEvents();
  }

  function renderBlockItem(block) {
    var preview = getBlockPreview(block);
    var label = BLOCK_LABELS[block.type] || block.type;
    var s = block.settings || {};
    var contentStyle = s.content_alignment ? 'text-align:' + s.content_alignment : 'text-align:' + (s.alignment || 'center');

    return '<div class="pb-item" data-id="' + block.id + '" data-type="' + block.type + '" role="listitem">' +
      '<span class="pb-item__label">' + label + '</span>' +
      '<div class="pb-item__toolbar" role="toolbar" aria-label="' + label + '">' +
        '<button class="pb-item__drag-handle" title="' + (PB_I18N.drag || 'Drag') + '" aria-label="' + (PB_I18N.drag || 'Drag') + ' ' + label + '"><svg viewBox="0 0 24 24"><circle cx="9" cy="5" r="1" fill="currentColor"/><circle cx="15" cy="5" r="1" fill="currentColor"/><circle cx="9" cy="12" r="1" fill="currentColor"/><circle cx="15" cy="12" r="1" fill="currentColor"/><circle cx="9" cy="19" r="1" fill="currentColor"/><circle cx="15" cy="19" r="1" fill="currentColor"/></svg></button>' +
        '<button class="pb-item__move-up" title="' + (PB_I18N.move_up || 'Move up') + '" aria-label="' + (PB_I18N.move_up || 'Move up') + ' ' + label + '"><svg viewBox="0 0 24 24"><polyline points="18 15 12 9 6 15"/></svg></button>' +
        '<button class="pb-item__move-down" title="' + (PB_I18N.move_down || 'Move down') + '" aria-label="' + (PB_I18N.move_down || 'Move down') + ' ' + label + '"><svg viewBox="0 0 24 24"><polyline points="6 9 12 15 18 9"/></svg></button>' +
        '<button class="pb-item__duplicate" title="' + (PB_I18N.duplicate || 'Duplicate') + '" aria-label="' + (PB_I18N.duplicate || 'Duplicate') + ' ' + label + '"><svg viewBox="0 0 24 24"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg></button>' +
        '<button class="pb-item__delete" title="' + (PB_I18N.delete || 'Delete') + '" aria-label="' + (PB_I18N.delete || 'Delete') + ' ' + label + '"><svg viewBox="0 0 24 24"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/></svg></button>' +
      '</div>' +
      '<div class="pb-item__content"' + (contentStyle ? ' style="' + contentStyle + '"' : '') + '>' + preview + '</div>' +
    '</div>';
  }

  // ============================================================
  // BLOCK PREVIEWS — visual representation in canvas
  // ============================================================

  // Text block toolbar HTML — B, I, U, S, align, lists, link, clear
  var _textToolbarHTML = '<div class="pb-text-toolbar" role="toolbar">' +
    '<button class="pb-text-toolbar__btn" type="button" data-cmd="bold" title="' + (PB_I18N.bold || 'Bold') + '"><svg viewBox="0 0 24 24"><path d="M6 4h8a4 4 0 014 4 4 4 0 01-4 4H6z"/><path d="M6 12h9a4 4 0 014 4 4 4 0 01-4 4H6z"/></svg></button>' +
    '<button class="pb-text-toolbar__btn" type="button" data-cmd="italic" title="' + (PB_I18N.italic || 'Italic') + '"><svg viewBox="0 0 24 24"><line x1="19" y1="4" x2="10" y2="4"/><line x1="14" y1="20" x2="5" y2="20"/><line x1="15" y1="4" x2="9" y2="20"/></svg></button>' +
    '<button class="pb-text-toolbar__btn" type="button" data-cmd="underline" title="' + (PB_I18N.underline || 'Underline') + '"><svg viewBox="0 0 24 24"><path d="M6 3v7a6 6 0 006 6 6 6 0 006-6V3"/><line x1="4" y1="21" x2="20" y2="21"/></svg></button>' +
    '<button class="pb-text-toolbar__btn" type="button" data-cmd="strikeThrough" title="' + (PB_I18N.strikethrough || 'Strikethrough') + '"><svg viewBox="0 0 24 24"><line x1="4" y1="12" x2="20" y2="12"/><path d="M17.3 4.9C16.2 4 14.7 3.5 13 3.5c-3.6 0-5.5 2-5.5 4 0 .7.2 1.4.5 2"/><path d="M8 14.5c0 3 2.5 5 6 5 1.8 0 3.3-.5 4.3-1.4"/></svg></button>' +
    '<span class="pb-text-toolbar__sep"></span>' +
    '<button class="pb-text-toolbar__btn" type="button" data-cmd="justifyLeft" title="' + (PB_I18N.align_left || 'Align left') + '"><svg viewBox="0 0 24 24"><line x1="17" y1="10" x2="3" y2="10"/><line x1="21" y1="6" x2="3" y2="6"/><line x1="21" y1="14" x2="3" y2="14"/><line x1="17" y1="18" x2="3" y2="18"/></svg></button>' +
    '<button class="pb-text-toolbar__btn" type="button" data-cmd="justifyCenter" title="' + (PB_I18N.align_center || 'Align center') + '"><svg viewBox="0 0 24 24"><line x1="18" y1="10" x2="6" y2="10"/><line x1="21" y1="6" x2="3" y2="6"/><line x1="21" y1="14" x2="3" y2="14"/><line x1="18" y1="18" x2="6" y2="18"/></svg></button>' +
    '<button class="pb-text-toolbar__btn" type="button" data-cmd="justifyRight" title="' + (PB_I18N.align_right || 'Align right') + '"><svg viewBox="0 0 24 24"><line x1="21" y1="10" x2="7" y2="10"/><line x1="21" y1="6" x2="3" y2="6"/><line x1="21" y1="14" x2="3" y2="14"/><line x1="21" y1="18" x2="7" y2="18"/></svg></button>' +
    '<button class="pb-text-toolbar__btn" type="button" data-cmd="justifyFull" title="' + (PB_I18N.align_justify || 'Justify') + '"><svg viewBox="0 0 24 24"><line x1="21" y1="6" x2="3" y2="6"/><line x1="21" y1="10" x2="3" y2="10"/><line x1="21" y1="14" x2="3" y2="14"/><line x1="21" y1="18" x2="3" y2="18"/></svg></button>' +
    '<span class="pb-text-toolbar__sep"></span>' +
    '<button class="pb-text-toolbar__btn" type="button" data-cmd="insertUnorderedList" title="' + (PB_I18N.bullet_list || 'Bullet list') + '"><svg viewBox="0 0 24 24"><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><circle cx="4" cy="6" r="1" fill="currentColor" stroke="none"/><circle cx="4" cy="12" r="1" fill="currentColor" stroke="none"/><circle cx="4" cy="18" r="1" fill="currentColor" stroke="none"/></svg></button>' +
    '<button class="pb-text-toolbar__btn" type="button" data-cmd="insertOrderedList" title="' + (PB_I18N.numbered_list || 'Numbered list') + '"><svg viewBox="0 0 24 24"><line x1="10" y1="6" x2="21" y2="6"/><line x1="10" y1="12" x2="21" y2="12"/><line x1="10" y1="18" x2="21" y2="18"/><text x="4" y="8" font-size="7" fill="currentColor" stroke="none" font-family="var(--pb-font)">1</text><text x="4" y="14" font-size="7" fill="currentColor" stroke="none" font-family="var(--pb-font)">2</text><text x="4" y="20" font-size="7" fill="currentColor" stroke="none" font-family="var(--pb-font)">3</text></svg></button>' +
    '<span class="pb-text-toolbar__sep"></span>' +
    '<button class="pb-text-toolbar__btn" type="button" data-cmd="createLink" title="' + (PB_I18N.link || 'Link') + '"><svg viewBox="0 0 24 24"><path d="M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71"/></svg></button>' +
    '<button class="pb-text-toolbar__btn" type="button" data-cmd="removeFormat" title="' + (PB_I18N.clear_formatting || 'Clear formatting') + '"><svg viewBox="0 0 24 24"><path d="M4 7V4h16v3"/><path d="M9 20h6"/><path d="M12 4v16"/><line x1="3" y1="21" x2="21" y2="3" stroke-width="1.5"/></svg></button>' +
    '</div>';

  // Compact toolbar for table cells — B, I, U, S, link, clear (no lists/alignment)
  var _tableToolbarHTML = '<div class="pb-text-toolbar pb-text-toolbar--compact" role="toolbar">' +
    '<button class="pb-text-toolbar__btn" type="button" data-cmd="bold" title="' + (PB_I18N.bold || 'Bold') + '"><svg viewBox="0 0 24 24"><path d="M6 4h8a4 4 0 014 4 4 4 0 01-4 4H6z"/><path d="M6 12h9a4 4 0 014 4 4 4 0 01-4 4H6z"/></svg></button>' +
    '<button class="pb-text-toolbar__btn" type="button" data-cmd="italic" title="' + (PB_I18N.italic || 'Italic') + '"><svg viewBox="0 0 24 24"><line x1="19" y1="4" x2="10" y2="4"/><line x1="14" y1="20" x2="5" y2="20"/><line x1="15" y1="4" x2="9" y2="20"/></svg></button>' +
    '<button class="pb-text-toolbar__btn" type="button" data-cmd="underline" title="' + (PB_I18N.underline || 'Underline') + '"><svg viewBox="0 0 24 24"><path d="M6 3v7a6 6 0 006 6 6 6 0 006-6V3"/><line x1="4" y1="21" x2="20" y2="21"/></svg></button>' +
    '<button class="pb-text-toolbar__btn" type="button" data-cmd="strikeThrough" title="' + (PB_I18N.strikethrough || 'Strikethrough') + '"><svg viewBox="0 0 24 24"><line x1="4" y1="12" x2="20" y2="12"/><path d="M17.3 4.9C16.2 4 14.7 3.5 13 3.5c-3.6 0-5.5 2-5.5 4 0 .7.2 1.4.5 2"/><path d="M8 14.5c0 3 2.5 5 6 5 1.8 0 3.3-.5 4.3-1.4"/></svg></button>' +
    '<span class="pb-text-toolbar__sep"></span>' +
    '<button class="pb-text-toolbar__btn" type="button" data-cmd="createLink" title="' + (PB_I18N.link || 'Link') + '"><svg viewBox="0 0 24 24"><path d="M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71"/></svg></button>' +
    '<button class="pb-text-toolbar__btn" type="button" data-cmd="removeFormat" title="' + (PB_I18N.clear_formatting || 'Clear formatting') + '"><svg viewBox="0 0 24 24"><path d="M4 7V4h16v3"/><path d="M9 20h6"/><path d="M12 4v16"/><line x1="3" y1="21" x2="21" y2="3" stroke-width="1.5"/></svg></button>' +
    '</div>';

  // Clean sanitized HTML — strip comments, invisible chars, normalize spaces
  function _cleanPasteResult(html) {
    return html
      .replace(/<!--[\s\S]*?-->/g, '')
      .replace(/&nbsp;/g, ' ')
      .replace(/[\u00A0\u200B\u200C\u200D\u2060\uFEFF\u200E\u200F\u2028\u2029]/g, function(ch) {
        return ch === '\u00A0' ? ' ' : '';
      })
      .replace(/ {2,}/g, ' ')
      .trim();
  }

  // Tags to remove entirely with their content (not unwrap)
  var _removeTags = {STYLE:1, SCRIPT:1, HEAD:1, TITLE:1, META:1, LINK:1, NOSCRIPT:1, IFRAME:1, OBJECT:1, EMBED:1};

  // Sanitize table cell HTML — allow only basic inline formatting
  function _sanitizeTableCell(html) {
    if (!html || html.indexOf('<') === -1) return html || '';
    var allowed = {B:1, STRONG:1, I:1, EM:1, U:1, S:1, A:1, BR:1};
    var tmp = document.createElement('div');
    tmp.innerHTML = html;
    var els = tmp.querySelectorAll('*');
    for (var i = els.length - 1; i >= 0; i--) {
      var el = els[i];
      if (_removeTags[el.tagName]) {
        el.parentNode.removeChild(el);
      } else if (!allowed[el.tagName]) {
        while (el.firstChild) el.parentNode.insertBefore(el.firstChild, el);
        el.parentNode.removeChild(el);
      } else if (el.tagName === 'A') {
        var href = el.getAttribute('href');
        while (el.attributes.length > 0) el.removeAttribute(el.attributes[0].name);
        if (href) el.setAttribute('href', href);
      } else {
        while (el.attributes.length > 0) el.removeAttribute(el.attributes[0].name);
      }
    }
    return _cleanPasteResult(tmp.innerHTML);
  }

  // Sanitize rich text paste — allow formatting + structure, strip styles/backgrounds
  function _sanitizeRichPaste(html) {
    if (!html || html.indexOf('<') === -1) return html || '';
    var allowed = {B:1, STRONG:1, I:1, EM:1, U:1, S:1, A:1, BR:1, P:1, UL:1, OL:1, LI:1, H1:1, H2:1, H3:1, H4:1, H5:1, H6:1, BLOCKQUOTE:1};
    var tmp = document.createElement('div');
    tmp.innerHTML = html;
    var els = tmp.querySelectorAll('*');
    for (var i = els.length - 1; i >= 0; i--) {
      var el = els[i];
      if (_removeTags[el.tagName]) {
        el.parentNode.removeChild(el);
      } else if (!allowed[el.tagName]) {
        while (el.firstChild) el.parentNode.insertBefore(el.firstChild, el);
        el.parentNode.removeChild(el);
      } else if (el.tagName === 'A') {
        var href = el.getAttribute('href');
        while (el.attributes.length > 0) el.removeAttribute(el.attributes[0].name);
        if (href) el.setAttribute('href', href);
      } else {
        while (el.attributes.length > 0) el.removeAttribute(el.attributes[0].name);
      }
    }
    return _cleanPasteResult(tmp.innerHTML);
  }

  function getBlockPreview(block) {
    var d = block.data || {};
    switch (block.type) {
      case 'heading':
        var tag = 'h' + (d.level || 2);
        return '<' + tag + ' class="pb-inline-heading" contenteditable="true" data-inline-key="data.text" data-inline-block="' + block.id + '" data-placeholder="' + esc(PB_I18N.default_heading || 'New Heading') + '">' + esc(d.text || '') + '</' + tag + '>';
      case 'text':
        return '<div class="pb-rich-zone">' + _textToolbarHTML +
          '<div class="pb-inline-text" contenteditable="true" data-inline-html="data.html" data-inline-block="' + block.id + '">' + (d.html || '<p>' + (PB_I18N.default_text || 'Start writing here...') + '</p>') + '</div></div>';
      case 'image':
        if (d.url) {
          var altTag = d.alt || '';
          var altBadge = altTag
            ? '<div class="pb-text-hint--sm pb-alt-badge-wrap"><span class="pb-thumb-label">alt: ' + esc(altTag) + '</span></div>'
            : '<div class="pb-text-hint--sm pb-alt-badge-wrap"><span class="pb-alt-warn">⚠ ' + (PB_I18N.alt_missing || 'No alt text — select image from library') + '</span></div>';
          return '<div class="pb-relative"><img src="' + esc(d.url) + '" class="pb-preview-img--canvas" alt="' + esc(d.alt) + '">' +
            '<button data-clear-image="' + block.id + '" title="' + (PB_I18N.remove || 'Remove') + '" class="pb-clear-btn">✕</button></div>' + altBadge;
        }
        return '<div data-image-pick-block="' + block.id + '" class="pb-picker-zone">' +
          '<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="pb-picker-icon"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>' +
          '<div class="pb-picker-label">' + (PB_I18N.click_select_image || 'Click to select image') + '</div>' +
          '<span class="pb-picker-btn">📷 ' + (PB_I18N.choose_image || 'Select Image') + '</span>' +
          '</div>';
      case 'button':
        return '<span class="pb-inline-btn pb-canvas-button" contenteditable="true" data-inline-key="data.text" data-inline-block="' + block.id + '" data-placeholder="' + esc(PB_I18N.default_button || 'Click Me') + '">' + esc(d.text || PB_I18N.default_button || 'Click Me') + '</span>';
      case 'divider':
        return '<hr style="border:none;border-top:1px ' + (d.style || 'solid') + ' var(--pb-border)">';
      case 'spacer':
        return '<div class="pb-spacer-preview" style="height:' + (d.height || 48) + 'px">' + (d.height || 48) + 'px</div>';
      case 'columns':
        var cols = d.columns || [];
        var layoutStyle = '';
        switch (d.layout || '1/2+1/2') {
          case '1': layoutStyle = 'grid-template-columns:1fr'; break;
          case '1/2+1/2': layoutStyle = 'grid-template-columns:1fr 1fr'; break;
          case '1/3+1/3+1/3': layoutStyle = 'grid-template-columns:1fr 1fr 1fr'; break;
          case '2/3+1/3': layoutStyle = 'grid-template-columns:2fr 1fr'; break;
          case '1/3+2/3': layoutStyle = 'grid-template-columns:1fr 2fr'; break;
          case '1/4+1/4+1/4+1/4': layoutStyle = 'grid-template-columns:1fr 1fr 1fr 1fr'; break;
          default: layoutStyle = 'grid-template-columns:1fr 1fr';
        }
        var colsHtml = '<div style="display:grid;' + layoutStyle + ';gap:8px">';
        cols.forEach(function(c, ci) {
          colsHtml += '<div class="pb-col-zone" data-col-parent="' + block.id + '" data-col-index="' + ci + '">';
          var colBlocks = c.blocks || [];
          if (colBlocks.length === 0) {
            colsHtml += '<div class="pb-col-zone__empty" data-col-add="' + block.id + '" data-col-idx="' + ci + '">+ ' + (PB_I18N.add_block || 'Add block') + '</div>';
          } else {
            colBlocks.forEach(function(cb, cbi) {
              if (cbi === 0) {
                colsHtml += '<div class="pb-inserter pb-inserter--sm" data-col-insert-parent="' + block.id + '" data-col-insert-col="' + ci + '" data-col-insert-at="0"><div class="pb-inserter__line"></div><button class="pb-inserter__btn" title="' + (PB_I18N.add_block || 'Add block') + '"><svg viewBox="0 0 24 24" width="10" height="10" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg></button></div>';
              }
              var cbPreview = getNestedBlockPreview(cb);
              var cbS = cb.settings || {};
              var cbStyle = cbS.content_alignment ? 'text-align:' + cbS.content_alignment : 'text-align:' + (cbS.alignment || 'center');
              colsHtml += '<div class="pb-col-block" data-col-block-parent="' + block.id + '" data-col-block-col="' + ci + '" data-col-block-idx="' + cbi + '" data-col-block-id="' + cb.id + '">';
              colsHtml += '<div class="pb-col-block__toolbar">';
              colsHtml += '<button class="pb-col-block__drag-handle" title="' + (PB_I18N.drag || 'Drag') + '"><svg viewBox="0 0 24 24" fill="currentColor"><circle cx="9" cy="5" r="1.5"/><circle cx="15" cy="5" r="1.5"/><circle cx="9" cy="12" r="1.5"/><circle cx="15" cy="12" r="1.5"/><circle cx="9" cy="19" r="1.5"/><circle cx="15" cy="19" r="1.5"/></svg></button>';
              colsHtml += '<button class="pb-col-block__move-up" title="' + (PB_I18N.move_up || 'Move up') + '"><svg viewBox="0 0 24 24"><polyline points="18 15 12 9 6 15"/></svg></button>';
              colsHtml += '<button class="pb-col-block__move-down" title="' + (PB_I18N.move_down || 'Move down') + '"><svg viewBox="0 0 24 24"><polyline points="6 9 12 15 18 9"/></svg></button>';
              colsHtml += '<button class="pb-col-block__duplicate" title="' + (PB_I18N.duplicate || 'Duplicate') + '"><svg viewBox="0 0 24 24"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg></button>';
              colsHtml += '<button class="pb-col-block__delete" title="' + (PB_I18N.delete || 'Delete') + '"><svg viewBox="0 0 24 24"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/></svg></button>';
              colsHtml += '</div>';
              colsHtml += '<div class="pb-col-block__content"' + (cbStyle ? ' style="' + cbStyle + '"' : '') + '>' + cbPreview + '</div>';
              colsHtml += '</div>';
              colsHtml += '<div class="pb-inserter pb-inserter--sm" data-col-insert-parent="' + block.id + '" data-col-insert-col="' + ci + '" data-col-insert-at="' + (cbi + 1) + '"><div class="pb-inserter__line"></div><button class="pb-inserter__btn" title="' + (PB_I18N.add_block || 'Add block') + '"><svg viewBox="0 0 24 24" width="10" height="10" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg></button></div>';
            });
            colsHtml += '<button class="pb-col-add" data-col-add="' + block.id + '" data-col-idx="' + ci + '" title="' + (PB_I18N.add_block || 'Add block') + '">+</button>';
          }
          colsHtml += '</div>';
        });
        colsHtml += '</div>';
        return colsHtml;
      case 'section':
        // Preview writes --pb-section-bg (light) and --pb-section-bg-dark
        // inline. CSS rule .pb-canvas--theme-dark .pb-section-zone picks the
        // dark variable when the canvas is in dark theme — no re-render.
        // Defaults for both vars live at the top of this CSS file.
        var bgType = d.background_type || 'color';
        var bgVal = d.background_value || '';
        var bgTypeDark = d.background_type_dark || 'color';
        var bgValDark = d.background_value_dark || '';

        function _bgVarValue(type, val) {
          if (type === 'color' && val) return esc(val);
          if (type === 'gradient' && val) return esc(val);
          if (type === 'image' && val) return 'url(' + esc(val) + ') center/cover no-repeat';
          return '';  // none or empty — CSS var() fallback handles it
        }
        var lightBg = _bgVarValue(bgType, bgVal);
        var darkBg = _bgVarValue(bgTypeDark, bgValDark);

        var bgStyleParts = [];
        if (lightBg) bgStyleParts.push('--pb-section-bg:' + lightBg);
        if (darkBg)  bgStyleParts.push('--pb-section-bg-dark:' + darkBg);
        if (bgType === 'none' && !lightBg) bgStyleParts.push('--pb-section-bg:transparent');
        if (bgTypeDark === 'none' && !darkBg) bgStyleParts.push('--pb-section-bg-dark:transparent');
        var bgStyle = bgStyleParts.join(';');
        var noneClass = (bgType === 'none' || bgTypeDark === 'none') ? ' pb-section-zone--none' : '';
        var textColorAttr = '';
        if (d.text_color === 'light') textColorAttr = ' data-text-color="light"';
        else if (d.text_color === 'dark') textColorAttr = ' data-text-color="dark"';
        var secBlocks = d.blocks || [];
        var fwBadge = d.full_width ? '<span class="pb-fw-badge">' + (PB_I18N.section_full_width_badge || 'FULL WIDTH') + '</span>' : '';
        var cwLabel = d.content_width && d.content_width !== 'default' ? ' · ' + (d.content_width === 'narrow' ? '768px' : d.content_width === 'wide' ? '1200px' : 'Full') : '';
        var secHtml = '<div class="pb-section-zone' + noneClass + '" data-sec-parent="' + block.id + '"' + textColorAttr + ' style="padding:16px;border-radius:8px;' + bgStyle + ';min-height:60px;position:relative">';
        secHtml += '<div class="pb-section-zone__label">' + PB_I18N.section + fwBadge + '<span class="pb-dim">' + cwLabel + '</span></div>';
        if (secBlocks.length === 0) {
          secHtml += '<div class="pb-sec-zone__empty pb-section-zone__empty" data-sec-add="' + block.id + '">+ ' + (PB_I18N.add_block || 'Add block') + '</div>';
        } else {
          secBlocks.forEach(function(sb, sbi) {
            if (sbi === 0) {
              secHtml += '<div class="pb-inserter pb-inserter--sm" data-sec-insert-parent="' + block.id + '" data-sec-insert-at="0"><div class="pb-inserter__line"></div><button class="pb-inserter__btn" title="' + (PB_I18N.add_block || 'Add block') + '"><svg viewBox="0 0 24 24" width="10" height="10" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg></button></div>';
            }
            var sbPreview = getNestedBlockPreview(sb);
            var sbS = sb.settings || {};
            var sbStyle = sbS.content_alignment ? 'text-align:' + sbS.content_alignment : 'text-align:' + (sbS.alignment || 'center');
            secHtml += '<div class="pb-sec-block pb-sec-block" data-sec-block-parent="' + block.id + '" data-sec-block-idx="' + sbi + '" data-sec-block-id="' + sb.id + '">';
            secHtml += '<div class="pb-sec-block__toolbar pb-sec-block__toolbar">';
            secHtml += '<button class="pb-sec-block__drag-handle pb-sec-toolbar-btn" title="' + (PB_I18N.drag || 'Drag') + '"><svg width="10" height="10" viewBox="0 0 24 24" fill="currentColor"><circle cx="9" cy="5" r="1.5"/><circle cx="15" cy="5" r="1.5"/><circle cx="9" cy="12" r="1.5"/><circle cx="15" cy="12" r="1.5"/><circle cx="9" cy="19" r="1.5"/><circle cx="15" cy="19" r="1.5"/></svg></button>';
            secHtml += '<button class="pb-sec-block__move-up pb-sec-toolbar-btn" title="' + (PB_I18N.move_up || 'Move up') + '"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="18 15 12 9 6 15"/></svg></button>';
            secHtml += '<button class="pb-sec-block__move-down pb-sec-toolbar-btn" title="' + (PB_I18N.move_down || 'Move down') + '"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg></button>';
            secHtml += '<button class="pb-sec-block__duplicate pb-sec-toolbar-btn" title="' + (PB_I18N.duplicate || 'Duplicate') + '"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg></button>';
            secHtml += '<button class="pb-sec-block__delete pb-sec-toolbar-btn" title="' + (PB_I18N.delete || 'Delete') + '"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/></svg></button>';
            secHtml += '</div>';
            secHtml += '<div class="pb-sec-block__content"' + (sbStyle ? ' style="' + sbStyle + '"' : '') + '>' + sbPreview + '</div>';
            secHtml += '</div>';
            secHtml += '<div class="pb-inserter pb-inserter--sm" data-sec-insert-parent="' + block.id + '" data-sec-insert-at="' + (sbi + 1) + '"><div class="pb-inserter__line"></div><button class="pb-inserter__btn" title="' + (PB_I18N.add_block || 'Add block') + '"><svg viewBox="0 0 24 24" width="10" height="10" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg></button></div>';
          });
          secHtml += '<button class="pb-sec-add pb-dashed-add--compact" data-sec-add="' + block.id + '" title="' + (PB_I18N.add_block || 'Add block') + '">+</button>';
        }
        secHtml += '</div>';
        return secHtml;
      case 'video':
        var vidId = d.embed_id || '';
        var vidSource = d.source || '';
        if (vidId) {
          var vidThumb = '<img src="' + videoThumbUrl(vidSource, vidId) + '" alt="' + esc(d.title || '') + '" class="pb-cover-img">';
          return '<div class="pb-video-wrap">' +
            '<div class="pb-aspect-video">' + vidThumb + '</div>' +
            '<div class="pb-overlay-center"><div class="pb-play-btn pb-play-btn--lg">▶</div></div>' +
            '<button data-clear-video="' + block.id + '" title="' + (PB_I18N.remove || 'Remove') + '" class="pb-clear-btn pb-clear-btn--z2">✕</button>' +
            (d.title ? '<div class="pb-video-title-bar">' + esc(d.title) + '</div>' : '') +
            '</div>';
        }
        return '<div data-video-pick-block="' + block.id + '" class="pb-gal-canvas-empty">' +
          '<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="pb-picker-icon"><rect x="2" y="4" width="20" height="16" rx="2"/><polygon points="10 8 16 12 10 16 10 8"/></svg>' +
          '<div class="pb-picker-label">' + (PB_I18N.video_gallery_empty || 'Click to add video') + '</div>' +
          '<span class="pb-picker-btn">+ ' + (PB_I18N.video_gallery_add || 'Add video') + '</span>' +
          '</div>';
      case 'gallery':
        var galImages = d.images || [];
        var count = galImages.length;
        var galCols = d.columns || 3;
        if (count > 0) {
          var displayCols = Math.min(galCols, count);
          var galHtml = '<div style="display:grid;grid-template-columns:repeat(' + displayCols + ',1fr);gap:6px;width:100%;border-radius:8px;overflow:hidden">';
          galImages.forEach(function(img, gi) {
            galHtml += '<div class="pb-gal-thumb pb-gal-thumb--square">';
            if (img.url) galHtml += '<img src="' + esc(img.url) + '" alt="' + esc(img.alt || '') + '" class="pb-cover-img">';
            galHtml += '<button data-gal-canvas-remove="' + gi + '" data-gal-canvas-block="' + block.id + '" title="' + (PB_I18N.remove || 'Remove') + '" class="pb-gal-thumb__remove">&times;</button>';
            if (count > 1) {
              galHtml += '<div class="pb-gal-thumb__arrows">';
              if (gi > 0) galHtml += '<button data-gal-move-block="' + block.id + '" data-gal-move-from="' + gi + '" data-gal-move-dir="-1" class="pb-gal-thumb__arrow" title="' + (PB_I18N.move_up || 'Move left') + '">‹</button>';
              if (gi < count - 1) galHtml += '<button data-gal-move-block="' + block.id + '" data-gal-move-from="' + gi + '" data-gal-move-dir="1" class="pb-gal-thumb__arrow" title="' + (PB_I18N.move_down || 'Move right') + '">›</button>';
              galHtml += '</div>';
            }
            galHtml += '</div>';
          });
          galHtml += '</div>';
          galHtml += '<div class="pb-mt-2"><button data-gal-canvas-add="' + block.id + '" class="pb-gal-canvas-add">📷 ' + (PB_I18N.gallery_add_from_library || 'Add from Library') + '</button></div>';
          return galHtml;
        }
        return '<div data-gal-canvas-add="' + block.id + '" class="pb-gal-canvas-empty">' +
          '<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="pb-picker-icon"><rect x="2" y="2" width="8" height="8" rx="1"/><rect x="14" y="2" width="8" height="8" rx="1"/><rect x="2" y="14" width="8" height="8" rx="1"/><rect x="14" y="14" width="8" height="8" rx="1"/></svg>' +
          '<div class="pb-picker-label">' + (PB_I18N.click_select_image || 'Click to select images') + '</div>' +
          '<span class="pb-picker-btn">📷 ' + (PB_I18N.gallery_add_from_library || 'Add from Library') + '</span>' +
          '</div>';
      case 'video_gallery':
        var vgVideos = d.videos || [];
        var vgCount = vgVideos.length;
        var vgCols = d.columns || 2;
        if (vgCount > 0) {
          var vgDisplayCols = Math.min(vgCols, vgCount);
          var vgHtml = '<div style="display:grid;grid-template-columns:repeat(' + vgDisplayCols + ',1fr);gap:6px;width:100%">';
          vgVideos.forEach(function(vid, vi) {
            var thumbUrl = '';
            if (vid.embed_id && vid.source) {
              thumbUrl = videoThumbUrl(vid.source, vid.embed_id);
            }
            vgHtml += '<div class="pb-gal-thumb pb-video-wrap--sm">';
            vgHtml += '<div class="pb-aspect-video">';
            if (thumbUrl) {
              vgHtml += '<img src="' + thumbUrl + '" alt="' + esc(vid.title || '') + '" class="pb-cover-img">';
            } else {
              vgHtml += '<div class="pb-video-placeholder">' + (vid.source === 'vimeo' ? 'Vimeo' : '▶') + '</div>';
            }
            vgHtml += '<div class="pb-overlay-center"><div class="pb-play-btn pb-play-btn--sm">▶</div></div>';
            vgHtml += '</div>';
            if (vid.title) vgHtml += '<div class="pb-vg-title--sm">' + esc(vid.title) + '</div>';
            vgHtml += '<button data-vgal-canvas-remove="' + vi + '" data-vgal-canvas-block="' + block.id + '" title="' + (PB_I18N.remove || 'Remove') + '" class="pb-gal-thumb__remove">&times;</button>';
            if (vgCount > 1) {
              vgHtml += '<div class="pb-gal-thumb__arrows">';
              if (vi > 0) vgHtml += '<button data-vgal-move-block="' + block.id + '" data-vgal-move-from="' + vi + '" data-vgal-move-dir="-1" class="pb-gal-thumb__arrow" title="' + (PB_I18N.move_up || 'Move left') + '">‹</button>';
              if (vi < vgCount - 1) vgHtml += '<button data-vgal-move-block="' + block.id + '" data-vgal-move-from="' + vi + '" data-vgal-move-dir="1" class="pb-gal-thumb__arrow" title="' + (PB_I18N.move_down || 'Move right') + '">›</button>';
              vgHtml += '</div>';
            }
            vgHtml += '</div>';
          });
          vgHtml += '</div>';
          vgHtml += '<div class="pb-mt-2 pb-text-center"><button data-vgal-canvas-add="' + block.id + '" class="pb-gal-canvas-add">+ ' + (PB_I18N.video_gallery_add || 'Add video') + '</button></div>';
          return vgHtml;
        }
        return '<div data-vgal-canvas-add="' + block.id + '" class="pb-gal-canvas-empty">' +
          '<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="pb-picker-icon"><rect x="2" y="4" width="20" height="16" rx="2"/><polygon points="10 8 16 12 10 16 10 8"/></svg>' +
          '<div class="pb-picker-label">' + (PB_I18N.video_gallery_empty || 'Click to add videos') + '</div>' +
          '<span class="pb-picker-btn">+ ' + (PB_I18N.video_gallery_add || 'Add video') + '</span>' +
          '</div>';
      case 'html':
        return '<div class="pb-relative"><span class="pb-badge--warning">⚠ ' + (PB_I18N.html_executes || 'Executes on page') + '</span>' +
          '<textarea class="pb-inline-textarea pb-pt-7" data-inline-textarea="data.code" data-inline-block="' + block.id + '" placeholder="<!-- HTML -->">' + esc(d.code || '') + '</textarea></div>';
      case 'table':
        var th = (d.headers || []);
        var tr = (d.rows || []);
        var tblLayout = d.table_width === 'auto' ? 'auto' : '100%';
        var tableHtml = '<div class="pb-inline-table pb-table-wrap">';
        // Compact formatting toolbar
        tableHtml += _tableToolbarHTML;
        // Symbol picker toolbar
        tableHtml += '<div class="pb-table-symbols" role="toolbar">' +
          '<button class="pb-table-symbols__btn" type="button" data-insert-symbol="✅" title="' + (PB_I18N.icon_check || 'Check') + '">✅</button>' +
          '<button class="pb-table-symbols__btn" type="button" data-insert-symbol="❌" title="' + (PB_I18N.icon_cross || 'Cross') + '">❌</button>' +
          '<span class="pb-table-symbols__sep"></span>' +
          '<button class="pb-table-symbols__btn" type="button" data-insert-symbol="⭐" title="' + (PB_I18N.icon_star || 'Star') + '">⭐</button>' +
          '<button class="pb-table-symbols__btn" type="button" data-insert-symbol="⚡" title="' + (PB_I18N.icon_feature || 'Feature') + '">⚡</button>' +
          '<button class="pb-table-symbols__btn" type="button" data-insert-symbol="💡" title="' + (PB_I18N.icon_tip || 'Tip') + '">💡</button>' +
          '<button class="pb-table-symbols__btn" type="button" data-insert-symbol="⚠️" title="' + (PB_I18N.icon_warning || 'Warning') + '">⚠️</button>' +
          '<span class="pb-table-symbols__sep"></span>' +
          '<button class="pb-table-symbols__btn" type="button" data-insert-symbol="➕" title="' + (PB_I18N.icon_plus || 'Plus') + '">➕</button>' +
          '<button class="pb-table-symbols__btn" type="button" data-insert-symbol="➖" title="' + (PB_I18N.icon_minus || 'Minus') + '">➖</button>' +
          '<button class="pb-table-symbols__btn pb-table-symbols__btn--dot" type="button" data-insert-symbol="●" title="' + (PB_I18N.icon_dot || 'Dot') + '">●</button>' +
          '</div>';
        tableHtml += '<table style="width:' + tblLayout + ';border-collapse:collapse">';
        var cw = d.column_widths || [];
        var ca = d.column_alignments || [];
        if (cw.some(function(w) { return w; })) {
          tableHtml += '<colgroup>';
          th.forEach(function(h, hi) { var w = cw[hi]; tableHtml += w ? '<col style="width:' + parseInt(w) + '%">' : '<col>'; });
          tableHtml += '</colgroup>';
        }
        if (th.length > 0) {
          tableHtml += '<tr>';
          th.forEach(function(h, hi) {
            var alignStyle = ca[hi] ? ' style="text-align:' + ca[hi] + '"' : '';
            tableHtml += '<th class="pb-table-th"' + alignStyle + '><div contenteditable="true" data-inline-th="' + hi + '" data-inline-block="' + block.id + '" data-placeholder="' + PB_I18N.heading + '">' + (h || '') + '</div></th>';
          });
          tableHtml += '<th class="pb-table-row-actions"></th>';
          tableHtml += '</tr>';
        }
        var colCount = th.length || (tr[0] || []).length || 1;
        var colSpanAll = colCount + 1;
        tr.forEach(function(row, ri) {
          if (ri === 0) {
            tableHtml += '<tr class="pb-table-insert-row" data-table-canvas-insert="' + block.id + '" data-table-canvas-idx="0"><td colspan="' + colSpanAll + '"><div class="pb-inserter pb-inserter--sm"><div class="pb-inserter__line"></div><button class="pb-inserter__btn" title="' + (PB_I18N.table_insert_row || 'Insert row') + '"><svg viewBox="0 0 24 24" width="10" height="10" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg></button></div></td></tr>';
          }
          tableHtml += '<tr>';
          (row || []).forEach(function(cell, ci) {
            var alignStyle = ca[ci] ? ' style="text-align:' + ca[ci] + '"' : '';
            tableHtml += '<td class="pb-table-td"' + alignStyle + '><div contenteditable="true" data-inline-td="' + ri + ',' + ci + '" data-inline-block="' + block.id + '" data-placeholder="…">' + (cell || '') + '</div></td>';
          });
          tableHtml += '<td class="pb-table-row-actions"><button class="pb-table-row-delete" data-table-canvas-delete="' + block.id + '" data-table-canvas-delete-idx="' + ri + '" title="' + (PB_I18N.remove || 'Remove') + '"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg></button></td>';
          tableHtml += '</tr>';
          tableHtml += '<tr class="pb-table-insert-row" data-table-canvas-insert="' + block.id + '" data-table-canvas-idx="' + (ri + 1) + '"><td colspan="' + colSpanAll + '"><div class="pb-inserter pb-inserter--sm"><div class="pb-inserter__line"></div><button class="pb-inserter__btn" title="' + (PB_I18N.table_insert_row || 'Insert row') + '"><svg viewBox="0 0 24 24" width="10" height="10" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg></button></div></td></tr>';
        });
        tableHtml += '</table></div>';
        return tableHtml;
      case 'accordion':
        var items = (d.items || []);
        var accHtml = '';
        items.forEach(function(it, i) {
          accHtml += '<div class="pb-acc-item" data-acc-item>';
          accHtml += '<div class="pb-acc-item__header" data-acc-toggle>';
          accHtml += '<span class="pb-acc-item__arrow">▸</span>';
          accHtml += '<span class="pb-inline-acc-title" contenteditable="true" data-inline-acc="' + i + '" data-inline-block="' + block.id + '" data-placeholder="' + esc(PB_I18N.default_accordion_title || 'Question') + '">' + esc(it.title || '') + '</span>';
          accHtml += '</div>';
          accHtml += '<div class="pb-acc-item__body"><div class="pb-rich-zone">' + _textToolbarHTML +
            '<div class="pb-inline-text" contenteditable="true" data-inline-html="data.items.' + i + '.content" data-inline-block="' + block.id + '">' + (it.content || '<p></p>') + '</div>' +
            '</div></div>';
          accHtml += '</div>';
        });
        return accHtml;
      case 'tabs':
        var tabs = (d.tabs || []);
        var activeIdx = _activeTabIdx(block.id);
        if (activeIdx >= tabs.length) { activeIdx = 0; _tabEditIdx[block.id] = 0; }
        var tabsHtml = '<div class="pb-tabs-nav">';
        tabs.forEach(function(t, i) {
          tabsHtml += '<span class="pb-tabs-btn' + (i === activeIdx ? ' is-active' : '') + '" contenteditable="true" data-inline-tab="' + i + '" data-inline-block="' + block.id + '" data-placeholder="' + esc(PB_I18N.default_tab || 'Tab') + '" data-tab-switch="' + i + '" data-tab-block="' + block.id + '">' + esc(t.title || '') + '</span>';
        });
        tabsHtml += '</div>';
        tabs.forEach(function(t, i) {
          tabsHtml += '<div class="pb-tab-panel' + (i === activeIdx ? ' is-active' : '') + '" data-tab-panel="' + i + '" data-tab-block="' + block.id + '">';
          tabsHtml += '<div class="pb-rich-zone">' + _textToolbarHTML +
            '<div class="pb-inline-text" contenteditable="true" data-inline-html="data.tabs.' + i + '.content" data-inline-block="' + block.id + '">' + (t.content || '<p></p>') + '</div></div>';
          tabsHtml += '</div>';
        });
        return tabsHtml;
      case 'audio':
        return '<div class="pb-info-box">' +
          '<div class="pb-audio-row"><span class="pb-emoji-muted">♪</span>' +
          '<input class="pb-inline-input pb-flex-1" type="text" data-inline-input="data.title" data-inline-block="' + block.id + '" value="' + esc(d.title || '') + '" placeholder="' + esc(PB_I18N.audio_title || 'Title') + '"></div>' +
          '<input class="pb-inline-input pb-input--sm" type="text" data-inline-input="data.url" data-inline-block="' + block.id + '" value="' + esc(d.url || '') + '" placeholder="' + esc(PB_I18N.audio_url || 'Audio URL') + '">' +
          '</div>';
      case 'quote':
        return '<blockquote class="pb-inline-quote" contenteditable="true" data-inline-key="data.text" data-inline-block="' + block.id + '" data-placeholder="' + esc(PB_I18N.quote_text || 'Enter quote...') + '">' + esc(d.text || '') + '</blockquote>' + (d.author ? '<small class="pb-quote-author">— ' + esc(d.author) + '</small>' : '');
      case 'code':
        var langLabel = d.language || 'plain text';
        return '<div class="pb-relative"><span class="pb-badge--muted">' + esc(langLabel) + '</span>' +
          '<textarea class="pb-inline-textarea pb-pt-7" data-inline-textarea="data.code" data-inline-block="' + block.id + '" placeholder="// code">' + esc(d.code || '') + '</textarea></div>';
      case 'form':
        if (d.form_id && d._fields) {
          return '<div class="pb-relative">' +
            renderFormPreview(block) +
            '<button data-clear-form="' + block.id + '" title="' + (PB_I18N.remove || 'Remove') + '" class="pb-clear-btn">✕</button>' +
            '</div>';
        }
        if (d.form_id && !d._fields) {
          if (!_formPreviewFetching[d.form_id]) {
            _formPreviewFetching[d.form_id] = true;
            fetch('/api/forms/' + d.form_id)
              .then(function(r) { return r.json(); })
              .then(function(data) {
                var formData = data.fields ? data : (data.form || {});
                if (formData.fields) {
                  block.data._fields = formData.fields;
                  block.data._settings = formData.settings || {};
                  block.data._description = formData.description || '';
                  block.data._form_title_raw = formData.title || '';
                  block.data._form_title = (formData.title || '') + ' (' + (formData.form_type || '') + ', ' + (formData.fields.length || 0) + ' ' + (PB_I18N.form_fields || 'fields') + ')';
                  renderCanvas();
                  if (state.selectedId === block.id) {
                    openPanel(block);
                  }
                }
              })
              .catch(function() {})
              .finally(function() { delete _formPreviewFetching[d.form_id]; });
          }
          var loadingName = d._form_title || d._form_title_raw || ((PB_I18N.form || 'Form') + ' #' + d.form_id);
          return '<div class="pb-relative">' +
            '<div class="pb-form-preview pb-form-preview--loading">' +
            '<div class="ap-form"><div class="pb-form-loading">' + esc(loadingName) + '</div></div></div>' +
            '<button data-clear-form="' + block.id + '" title="' + (PB_I18N.remove || 'Remove') + '" class="pb-clear-btn">✕</button>' +
            '</div>';
        }
        return '<div data-form-pick-block="' + block.id + '" class="pb-picker-zone">' +
          '<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="pb-picker-icon"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M7 7h10"/><path d="M7 12h10"/><path d="M7 17h4"/></svg>' +
          '<div class="pb-picker-label">' + (PB_I18N.click_select_form || 'Click to select form') + '</div>' +
          '<span class="pb-picker-btn">📋 ' + (PB_I18N.form_select || 'Select form') + '</span>' +
          '</div>';
      case 'slideshow':
        var slideCount = (d.slides || []).length;
        var activeSlideIdx = _activeSlideIdx(block.id);
        if (activeSlideIdx >= slideCount) { activeSlideIdx = 0; _slideEditIdx[block.id] = 0; }
        var activeSlide = (d.slides && d.slides[activeSlideIdx]) ? d.slides[activeSlideIdx] : null;
        var slideImg = activeSlide ? (activeSlide.image || '') : '';
        var slideshowPreview = '<div class="pb-slideshow-wrap">';
        if (slideImg) {
          slideshowPreview += '<img src="' + esc(slideImg) + '" class="pb-hero-img" alt="">';
          slideshowPreview += '<button class="pb-slide-clear-image" data-slide-clear="' + block.id + '" data-slide-clear-idx="' + activeSlideIdx + '" title="' + (PB_I18N.remove || 'Remove') + '">✕</button>';
        } else {
          slideshowPreview += '<div class="pb-slide-placeholder"></div>';
        }
        // Nested blocks zone on active slide
        var slideTextColorAttr = '';
        if (d.text_color === 'light') slideTextColorAttr = ' data-text-color="light"';
        else if (d.text_color === 'dark') slideTextColorAttr = ' data-text-color="dark"';
        slideshowPreview += '<div class="pb-slide-zone pb-hero-overlay" data-slide-parent="' + block.id + '" data-slide-idx="' + activeSlideIdx + '"' + slideTextColorAttr + '>';
          // Image picker at top when no image
          if (!slideImg) {
            slideshowPreview += '<div class="pb-slide-empty-image" data-slide-pick="' + block.id + '" data-slide-pick-idx="' + activeSlideIdx + '"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg> ' + PB_I18N.click_select_image + '</div>';
          }
          var slideBlocks = (activeSlide && activeSlide.blocks) || [];
          if (slideBlocks.length === 0) {
            slideshowPreview += '<div class="pb-slide-zone__empty pb-section-zone__empty--dark" data-slide-add="' + block.id + '">+ ' + (PB_I18N.slideshow_add_block || 'Add block to slide') + '</div>';
          } else {
            slideBlocks.forEach(function(sb, sbi) {
              if (sbi === 0) {
                slideshowPreview += '<div class="pb-inserter pb-inserter--sm" data-slide-insert-parent="' + block.id + '" data-slide-insert-at="0"><div class="pb-inserter__line"></div><button class="pb-inserter__btn" title="' + (PB_I18N.add_block || 'Add block') + '"><svg viewBox="0 0 24 24" width="10" height="10" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg></button></div>';
              }
              var sbPreview = getNestedBlockPreview(sb);
              var sbS = sb.settings || {};
              var sbStyle = sbS.content_alignment ? 'text-align:' + sbS.content_alignment : 'text-align:' + (sbS.alignment || 'center');
              slideshowPreview += '<div class="pb-slide-block pb-sec-block--dark" data-slide-block-parent="' + block.id + '" data-slide-block-idx="' + sbi + '" data-slide-block-id="' + sb.id + '">';
              slideshowPreview += '<div class="pb-slide-block__toolbar pb-sec-block__toolbar">';
              slideshowPreview += '<button class="pb-slide-block__drag-handle pb-vg-toolbar-btn" title="' + (PB_I18N.drag || 'Drag') + '"><svg width="10" height="10" viewBox="0 0 24 24" fill="currentColor"><circle cx="9" cy="5" r="1.5"/><circle cx="15" cy="5" r="1.5"/><circle cx="9" cy="12" r="1.5"/><circle cx="15" cy="12" r="1.5"/><circle cx="9" cy="19" r="1.5"/><circle cx="15" cy="19" r="1.5"/></svg></button>';
              slideshowPreview += '<button class="pb-slide-block__move-up pb-vg-toolbar-btn" title="' + (PB_I18N.move_up || 'Move up') + '"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="18 15 12 9 6 15"/></svg></button>';
              slideshowPreview += '<button class="pb-slide-block__move-down pb-vg-toolbar-btn" title="' + (PB_I18N.move_down || 'Move down') + '"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg></button>';
              slideshowPreview += '<button class="pb-slide-block__duplicate pb-vg-toolbar-btn" title="' + (PB_I18N.duplicate || 'Duplicate') + '"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg></button>';
              slideshowPreview += '<button class="pb-slide-block__delete pb-vg-toolbar-btn" title="' + (PB_I18N.delete || 'Delete') + '"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/></svg></button>';
              slideshowPreview += '</div>';
              slideshowPreview += '<div class="pb-slide-block__content"' + (sbStyle ? ' style="' + sbStyle + '"' : '') + '>' + sbPreview + '</div>';
              slideshowPreview += '</div>';
              slideshowPreview += '<div class="pb-inserter pb-inserter--sm" data-slide-insert-parent="' + block.id + '" data-slide-insert-at="' + (sbi + 1) + '"><div class="pb-inserter__line"></div><button class="pb-inserter__btn" title="' + (PB_I18N.add_block || 'Add block') + '"><svg viewBox="0 0 24 24" width="10" height="10" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg></button></div>';
            });
            slideshowPreview += '<button class="pb-slide-add pb-dashed-add--dark" data-slide-add="' + block.id + '">+</button>';
          }
          slideshowPreview += '</div>';
        // Info badge + slide selector tabs + add button (on top of everything)
        slideshowPreview += '<div class="pb-sec-toolbar-pos">';
        for (var si = 0; si < slideCount; si++) {
          var isAct = si === activeSlideIdx;
          slideshowPreview += '<button class="pb-slide-tab' + (isAct ? ' is-active' : '') + '" data-slide-tab-parent="' + block.id + '" data-slide-tab-idx="' + si + '">' + (si + 1) + '</button>';
        }
        slideshowPreview += '<button class="pb-slide-tab-add pb-vg-add-btn" data-slide-canvas-add="' + block.id + '" title="' + (PB_I18N.slideshow_add_slide || 'Add slide') + '">+</button>';
        slideshowPreview += '</div>';
        slideshowPreview += '</div>';
        return slideshowPreview;
      case 'ticker':
        var tickerImgs = d.images || [];
        if (tickerImgs.length > 0) {
          var tickerHtml = '<div class="pb-ticker-preview">';
          tickerImgs.forEach(function(img, ti) {
            tickerHtml += '<div class="pb-gal-thumb pb-gal-thumb--square">';
            if (img.url) tickerHtml += '<img src="' + esc(img.url) + '" alt="' + esc(img.alt || '') + '">';
            tickerHtml += '<button data-ticker-canvas-remove="' + ti + '" data-ticker-canvas-block="' + block.id + '" title="' + (PB_I18N.remove || 'Remove') + '" class="pb-gal-thumb__remove">&times;</button>';
            tickerHtml += '</div>';
          });
          tickerHtml += '</div>';
          tickerHtml += '<div class="pb-mt-2"><button data-ticker-canvas-add="' + block.id + '" class="pb-gal-canvas-add">📷 ' + (PB_I18N.gallery_add_from_library || 'Add from Library') + '</button></div>';
          return tickerHtml;
        }
        return '<div data-ticker-canvas-add="' + block.id + '" class="pb-gal-canvas-empty">' +
          '<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="pb-picker-icon"><rect x="1" y="7" width="5" height="10" rx="1"/><rect x="8" y="7" width="5" height="10" rx="1"/><rect x="15" y="7" width="5" height="10" rx="1"/><path d="M3 21h18"/></svg>' +
          '<div class="pb-picker-label">' + (PB_I18N.ticker || 'Logo Ticker') + '</div>' +
          '<span class="pb-picker-btn">📷 ' + (PB_I18N.gallery_add_from_library || 'Add from Library') + '</span>' +
          '</div>';
      default:
        return '<div class="pb-block-unknown">Unknown block: ' + esc(block.type) + '</div>';
    }
  }

  // ============================================================
  // SETTINGS PANELS — right panel content per block type
  // ============================================================

  // Compact preview for blocks nested inside columns
  function getNestedBlockPreview(block) {
    var d = block.data || {};
    switch (block.type) {
      case 'heading':
        var nTag = 'h' + (d.level || 2);
        return '<' + nTag + ' class="pb-inline-heading pb-nested-heading" contenteditable="true" data-inline-key="data.text" data-inline-block="' + block.id + '" data-placeholder="' + esc(PB_I18N.default_heading || 'New Heading') + '">' + esc(d.text || '') + '</' + nTag + '>';
      case 'text':
        return '<div class="pb-rich-zone">' + _textToolbarHTML +
          '<div class="pb-inline-text pb-nested-text" contenteditable="true" data-inline-html="data.html" data-inline-block="' + block.id + '">' + (d.html || '<p>' + (PB_I18N.default_text || 'Start writing here...') + '</p>') + '</div></div>';
      case 'image':
        if (d.url) {
          var nAlt = d.alt || '';
          var nAltBadge = nAlt ? '<div class="pb-thumb-label pb-mt-1 pb-nested-alt">alt: ' + esc(nAlt) + '</div>' : '';
          return '<div class="pb-relative"><img src="' + esc(d.url) + '" class="pb-preview-img--canvas" alt="' + esc(nAlt) + '">' +
            '<button data-clear-image="' + block.id + '" title="' + (PB_I18N.remove || 'Remove') + '" class="pb-clear-btn">✕</button></div>' + nAltBadge;
        }
        return '<div data-image-pick-block="' + block.id + '" class="pb-picker-zone--small-center">📷 ' + PB_I18N.image + '</div>';
      case 'button':
        return '<span class="pb-inline-btn pb-canvas-button--sm" contenteditable="true" data-inline-key="data.text" data-inline-block="' + block.id + '" data-placeholder="' + esc(PB_I18N.default_button || 'Click Me') + '">' + esc(d.text || PB_I18N.button) + '</span>';
      case 'video':
        var nVidId = d.embed_id || '';
        var nVidSource = d.source || '';
        if (nVidId) {
          var nVidThumb = '<img src="' + videoThumbUrl(nVidSource, nVidId) + '" alt="' + esc(d.title || '') + '" class="pb-cover-img">';
          return '<div class="pb-video-wrap">' +
            '<div class="pb-aspect-video">' + nVidThumb + '</div>' +
            '<div class="pb-overlay-center"><div class="pb-play-btn pb-play-btn--lg">▶</div></div>' +
            '<button data-clear-video="' + block.id + '" title="' + (PB_I18N.remove || 'Remove') + '" class="pb-clear-btn pb-clear-btn--z2">✕</button>' +
            (d.title ? '<div class="pb-video-title-bar">' + esc(d.title) + '</div>' : '') +
            '</div>';
        }
        return '<div data-video-pick-block="' + block.id + '" class="pb-gal-canvas-empty">' +
          '<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="pb-picker-icon"><rect x="2" y="4" width="20" height="16" rx="2"/><polygon points="10 8 16 12 10 16 10 8"/></svg>' +
          '<div class="pb-picker-label">' + (PB_I18N.video_gallery_empty || 'Click to add video') + '</div>' +
          '<span class="pb-picker-btn">+ ' + (PB_I18N.video_gallery_add || 'Add video') + '</span>' +
          '</div>';
      case 'divider':
        return '<hr class="pb-nested-divider">';
      case 'spacer':
        return '<div class="pb-nested-spacer" style="height:' + Math.min(d.height || 24, 40) + 'px"></div>';
      case 'quote':
        return '<blockquote class="pb-inline-quote pb-text-hint" contenteditable="true" data-inline-key="data.text" data-inline-block="' + block.id + '" data-placeholder="' + esc(PB_I18N.quote_text || 'Enter quote...') + '">' + esc(d.text || '') + '</blockquote>';
      case 'audio':
        return '<div class="pb-text-hint">♪ ' + esc(d.title || 'Audio') + '</div>';
      case 'html':
        return '<div class="pb-text-hint--mono">' + esc((d.code || '').substring(0, 80)) + '</div>';
      case 'code':
        return '<div class="pb-text-hint--mono">' + esc((d.code || '').substring(0, 80)) + '</div>';
      case 'table':
        return '<div class="pb-text-hint">' + PB_I18N.table + ': ' + (d.headers || []).join(', ') + '</div>';
      case 'accordion':
        return '<div class="pb-text-hint--dim">' + (d.items || []).map(function(it) { return '▸ ' + esc(it.title); }).join('<br>') + '</div>';
      case 'tabs':
        return '<div class="pb-text-hint--dim">' + (d.tabs || []).map(function(t) { return '[' + esc(t.title) + ']'; }).join(' ') + '</div>';
      case 'gallery':
        return '<div class="pb-text-hint">' + PB_I18N.gallery + ' — ' + (d.images || []).length + ' ' + PB_I18N.gallery_images + '</div>';
      case 'video_gallery':
        var nVgVideos = d.videos || [];
        var nVgCount = nVgVideos.length;
        var nVgCols = d.columns || 2;
        if (nVgCount > 0) {
          var nVgDisplayCols = Math.min(nVgCols, nVgCount);
          var nVgHtml = '<div style="display:grid;grid-template-columns:repeat(' + nVgDisplayCols + ',1fr);gap:6px;width:100%">';
          nVgVideos.forEach(function(vid, vi) {
            var nThumbUrl = '';
            if (vid.embed_id && vid.source) {
              nThumbUrl = videoThumbUrl(vid.source, vid.embed_id);
            }
            nVgHtml += '<div class="pb-gal-thumb pb-video-wrap--sm">';
            nVgHtml += '<div class="pb-aspect-video">';
            if (nThumbUrl) {
              nVgHtml += '<img src="' + nThumbUrl + '" alt="' + esc(vid.title || '') + '" class="pb-cover-img">';
            } else {
              nVgHtml += '<div class="pb-video-placeholder">' + (vid.source === 'vimeo' ? 'Vimeo' : '▶') + '</div>';
            }
            nVgHtml += '<div class="pb-overlay-center"><div class="pb-play-btn pb-play-btn--sm">▶</div></div>';
            nVgHtml += '</div>';
            if (vid.title) nVgHtml += '<div class="pb-vg-title--sm">' + esc(vid.title) + '</div>';
            nVgHtml += '<button data-vgal-canvas-remove="' + vi + '" data-vgal-canvas-block="' + block.id + '" title="' + (PB_I18N.remove || 'Remove') + '" class="pb-gal-thumb__remove">&times;</button>';
            if (nVgCount > 1) {
              nVgHtml += '<div class="pb-gal-thumb__arrows">';
              if (vi > 0) nVgHtml += '<button data-vgal-move-block="' + block.id + '" data-vgal-move-from="' + vi + '" data-vgal-move-dir="-1" class="pb-gal-thumb__arrow" title="' + (PB_I18N.move_up || 'Move left') + '">‹</button>';
              if (vi < nVgCount - 1) nVgHtml += '<button data-vgal-move-block="' + block.id + '" data-vgal-move-from="' + vi + '" data-vgal-move-dir="1" class="pb-gal-thumb__arrow" title="' + (PB_I18N.move_down || 'Move right') + '">›</button>';
              nVgHtml += '</div>';
            }
            nVgHtml += '</div>';
          });
          nVgHtml += '</div>';
          nVgHtml += '<div class="pb-mt-2 pb-text-center"><button data-vgal-canvas-add="' + block.id + '" class="pb-gal-canvas-add">+ ' + (PB_I18N.video_gallery_add || 'Add video') + '</button></div>';
          return nVgHtml;
        }
        return '<div data-vgal-canvas-add="' + block.id + '" class="pb-gal-canvas-empty">' +
          '<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="pb-picker-icon"><rect x="2" y="4" width="20" height="16" rx="2"/><polygon points="10 8 16 12 10 16 10 8"/></svg>' +
          '<div class="pb-picker-label">' + (PB_I18N.video_gallery_empty || 'Click to add videos') + '</div>' +
          '<span class="pb-picker-btn">+ ' + (PB_I18N.video_gallery_add || 'Add video') + '</span>' +
          '</div>';
      case 'columns':
        var nCols = d.columns || [];
        var nLayoutStyle = '';
        switch (d.layout || '1/2+1/2') {
          case '1': nLayoutStyle = 'grid-template-columns:1fr'; break;
          case '1/2+1/2': nLayoutStyle = 'grid-template-columns:1fr 1fr'; break;
          case '1/3+1/3+1/3': nLayoutStyle = 'grid-template-columns:1fr 1fr 1fr'; break;
          case '2/3+1/3': nLayoutStyle = 'grid-template-columns:2fr 1fr'; break;
          case '1/3+2/3': nLayoutStyle = 'grid-template-columns:1fr 2fr'; break;
          case '1/4+1/4+1/4+1/4': nLayoutStyle = 'grid-template-columns:1fr 1fr 1fr 1fr'; break;
          default: nLayoutStyle = 'grid-template-columns:1fr 1fr';
        }
        var nColsHtml = '<div style="display:grid;' + nLayoutStyle + ';gap:8px">';
        nCols.forEach(function(c, ci) {
          nColsHtml += '<div class="pb-col-zone" data-col-parent="' + block.id + '" data-col-index="' + ci + '">';
          var nColBlocks = c.blocks || [];
          if (nColBlocks.length === 0) {
            nColsHtml += '<div class="pb-col-zone__empty" data-col-add="' + block.id + '" data-col-idx="' + ci + '">+ ' + (PB_I18N.add_block || 'Add block') + '</div>';
          } else {
            nColBlocks.forEach(function(cb, cbi) {
              if (cbi === 0) {
                nColsHtml += '<div class="pb-inserter pb-inserter--sm" data-col-insert-parent="' + block.id + '" data-col-insert-col="' + ci + '" data-col-insert-at="0"><div class="pb-inserter__line"></div><button class="pb-inserter__btn" title="' + (PB_I18N.add_block || 'Add block') + '"><svg viewBox="0 0 24 24" width="10" height="10" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg></button></div>';
              }
              var ncbPreview = getNestedBlockPreview(cb);
              nColsHtml += '<div class="pb-col-block" data-col-block-parent="' + block.id + '" data-col-block-col="' + ci + '" data-col-block-idx="' + cbi + '" data-col-block-id="' + cb.id + '">';
              nColsHtml += '<div class="pb-col-block__toolbar">';
              nColsHtml += '<button class="pb-col-block__drag-handle" title="' + (PB_I18N.drag || 'Drag') + '"><svg viewBox="0 0 24 24" fill="currentColor"><circle cx="9" cy="5" r="1.5"/><circle cx="15" cy="5" r="1.5"/><circle cx="9" cy="12" r="1.5"/><circle cx="15" cy="12" r="1.5"/><circle cx="9" cy="19" r="1.5"/><circle cx="15" cy="19" r="1.5"/></svg></button>';
              nColsHtml += '<button class="pb-col-block__move-up" title="' + (PB_I18N.move_up || 'Move up') + '"><svg viewBox="0 0 24 24"><polyline points="18 15 12 9 6 15"/></svg></button>';
              nColsHtml += '<button class="pb-col-block__move-down" title="' + (PB_I18N.move_down || 'Move down') + '"><svg viewBox="0 0 24 24"><polyline points="6 9 12 15 18 9"/></svg></button>';
              nColsHtml += '<button class="pb-col-block__duplicate" title="' + (PB_I18N.duplicate || 'Duplicate') + '"><svg viewBox="0 0 24 24"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg></button>';
              nColsHtml += '<button class="pb-col-block__delete" title="' + (PB_I18N.delete || 'Delete') + '"><svg viewBox="0 0 24 24"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/></svg></button>';
              nColsHtml += '</div>';
              nColsHtml += '<div class="pb-col-block__content">' + ncbPreview + '</div>';
              nColsHtml += '</div>';
              nColsHtml += '<div class="pb-inserter pb-inserter--sm" data-col-insert-parent="' + block.id + '" data-col-insert-col="' + ci + '" data-col-insert-at="' + (cbi + 1) + '"><div class="pb-inserter__line"></div><button class="pb-inserter__btn" title="' + (PB_I18N.add_block || 'Add block') + '"><svg viewBox="0 0 24 24" width="10" height="10" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg></button></div>';
            });
            nColsHtml += '<button class="pb-col-add" data-col-add="' + block.id + '" data-col-idx="' + ci + '" title="' + (PB_I18N.add_block || 'Add block') + '">+</button>';
          }
          nColsHtml += '</div>';
        });
        nColsHtml += '</div>';
        return nColsHtml;
      case 'slideshow':
        return '<div class="pb-text-hint">🎠 ' + PB_I18N.slideshow + ' — ' + (d.slides || []).length + ' ' + (PB_I18N.slideshow_slides || 'slides').toLowerCase() + '</div>';
      case 'form':
        if (d.form_id && d._fields) {
          return '<div class="pb-relative">' +
            renderFormPreview(block) +
            '<button data-clear-form="' + block.id + '" title="' + (PB_I18N.remove || 'Remove') + '" class="pb-clear-btn">✕</button>' +
            '</div>';
        }
        if (d.form_id && !d._fields) {
          if (!_formPreviewFetching[d.form_id]) {
            _formPreviewFetching[d.form_id] = true;
            fetch('/api/forms/' + d.form_id)
              .then(function(r) { return r.json(); })
              .then(function(data) {
                var formData = data.fields ? data : (data.form || {});
                if (formData.fields) {
                  block.data._fields = formData.fields;
                  block.data._settings = formData.settings || {};
                  block.data._description = formData.description || '';
                  block.data._form_title_raw = formData.title || '';
                  block.data._form_title = (formData.title || '') + ' (' + (formData.form_type || '') + ', ' + (formData.fields.length || 0) + ' ' + (PB_I18N.form_fields || 'fields') + ')';
                  renderCanvas();
                  if (state.selectedId === block.id) {
                    openPanel(block);
                  }
                }
              })
              .catch(function() {})
              .finally(function() { delete _formPreviewFetching[d.form_id]; });
          }
          var nLoadingName = d._form_title || d._form_title_raw || ((PB_I18N.form || 'Form') + ' #' + d.form_id);
          return '<div class="pb-relative">' +
            '<div class="pb-form-preview pb-form-preview--loading">' +
            '<div class="ap-form"><div class="pb-form-loading">' + esc(nLoadingName) + '</div></div></div>' +
            '<button data-clear-form="' + block.id + '" title="' + (PB_I18N.remove || 'Remove') + '" class="pb-clear-btn">✕</button>' +
            '</div>';
        }
        return '<div data-form-pick-block="' + block.id + '" class="pb-picker-zone--small-center">📋 ' + PB_I18N.form + '</div>';
      case 'ticker':
        return '<div class="pb-text-hint">' + PB_I18N.ticker + ' — ' + (d.images || []).length + ' ' + PB_I18N.ticker_images + '</div>';
      default:
        return '<div class="pb-text-hint">' + esc(block.type) + '</div>';
    }
  }

  // ============================================================
  // FORM PREVIEW — renders real form fields in canvas (read-only)
  // ============================================================
  function renderFormPreview(block) {
    var d = block.data || {};
    var fields = d._fields || [];
    var settings = d._settings || {};
    var showTitle = d.show_title !== false;
    var showDesc = d.show_description !== false;

    var html = '<div class="pb-form-preview"><div class="ap-form">';

    if (showTitle && d._form_title_raw) {
      html += '<h3 class="ap-form__title">' + esc(d._form_title_raw) + '</h3>';
    }
    if (showDesc && d._description) {
      html += '<p class="ap-form__desc">' + esc(d._description) + '</p>';
    }

    html += '<div class="ap-form__body">';

    fields.forEach(function(field) {
      var ftype = field.type || 'text';
      var flabel = esc(field.label || '');
      var fplaceholder = esc(field.placeholder || '');
      var fwidth = field.width || 'full';
      var reqMark = field.required ? ' <span class="ap-form__req">*</span>' : '';

      if (ftype === 'hidden') return;

      html += '<div class="ap-form__field ap-form__field--' + esc(fwidth) + '">';

      if (ftype === 'textarea') {
        html += '<label class="ap-form__label">' + flabel + reqMark + '</label>';
        html += '<textarea class="ap-form__textarea" disabled placeholder="' + fplaceholder + '" rows="3"></textarea>';
      } else if (ftype === 'select') {
        html += '<label class="ap-form__label">' + flabel + reqMark + '</label>';
        html += '<select class="ap-form__select" disabled><option>' + (fplaceholder || '—') + '</option></select>';
      } else if (ftype === 'radio') {
        html += '<label class="ap-form__label">' + flabel + reqMark + '</label>';
        html += '<div class="ap-form__options">';
        (field.options || []).forEach(function(opt) {
          html += '<label class="ap-form__radio"><input type="radio" disabled> ' + esc(opt) + '</label>';
        });
        html += '</div>';
      } else if (ftype === 'checkbox') {
        var opts = field.options || [];
        if (opts.length) {
          html += '<label class="ap-form__label">' + flabel + reqMark + '</label>';
          html += '<div class="ap-form__options">';
          opts.forEach(function(opt) {
            html += '<label class="ap-form__checkbox"><input type="checkbox" disabled> ' + esc(opt) + '</label>';
          });
          html += '</div>';
        } else {
          html += '<label class="ap-form__checkbox"><input type="checkbox" disabled> ' + flabel + '</label>';
        }
      } else if (ftype === 'rating') {
        var maxR = field.max_rating || 5;
        html += '<label class="ap-form__label">' + flabel + reqMark + '</label>';
        html += '<div class="ap-form__rating">';
        for (var i = 0; i < maxR; i++) {
          html += '<span class="ap-form__star">★</span>';
        }
        html += '</div>';
      } else if (ftype === 'consent') {
        html += '<label class="ap-form__consent"><input type="checkbox" disabled> <span>' + esc(field.consent_text || '') + '</span></label>';
      } else {
        html += '<label class="ap-form__label">' + flabel + reqMark + '</label>';
        html += '<input class="ap-form__input" type="text" disabled placeholder="' + fplaceholder + '">';
      }

      if (field.help_text && ftype !== 'hidden') {
        html += '<div class="ap-form__help">' + esc(field.help_text) + '</div>';
      }

      html += '</div>';
    });

    var submitLabel = esc(settings.submit_label || PB_I18N.form_submit_default || 'Submit');
    html += '<div class="ap-form__field ap-form__field--full">';
    html += '<button type="button" class="ap-btn ap-btn--primary ap-btn--md ap-form__submit" disabled>' + submitLabel + '</button>';
    html += '</div>';

    html += '</div></div></div>';
    return html;
  }

  // ============================================================
  // COLUMN BLOCK OPERATIONS
  // ============================================================
  function addBlockToColumn(parentId, colIdx, blockType, index) {
    var parent = findBlockDeep(parentId);
    if (!parent || !parent.data.columns[colIdx]) return;
    var data = JSON.parse(JSON.stringify(BLOCK_DEFAULTS[blockType] || {}));
    var newBlock = { id: uuid(), type: blockType, data: data, settings: { alignment: 'center' } };
    if (index !== undefined && index >= 0) {
      parent.data.columns[colIdx].blocks.splice(index, 0, newBlock);
    } else {
      parent.data.columns[colIdx].blocks.push(newBlock);
    }
    markDirty();
    renderCanvas();
  }

  function deleteColumnBlock(parentId, colIdx, blockIdx) {
    var parent = findBlockDeep(parentId);
    if (!parent || !parent.data.columns[colIdx]) return;
    var block = parent.data.columns[colIdx].blocks[blockIdx];
    var label = block ? (BLOCK_LABELS[block.type] || block.type) : '';
    if (!confirm((PB_I18N.delete_block_confirm || 'Delete this {type} block?').replace('{type}', label))) return;
    parent.data.columns[colIdx].blocks.splice(blockIdx, 1);
    markDirty();
    renderCanvas();
  }

  function moveColumnBlock(parentId, colIdx, blockIdx, dir) {
    var parent = findBlockDeep(parentId);
    if (!parent || !parent.data.columns[colIdx]) return;
    var blocks = parent.data.columns[colIdx].blocks;
    var newIdx = blockIdx + dir;
    if (newIdx < 0 || newIdx >= blocks.length) return;
    var b = blocks.splice(blockIdx, 1)[0];
    blocks.splice(newIdx, 0, b);
    markDirty();
    renderCanvas();
  }

  // Section nested block management
  function addBlockToSection(parentId, blockType, index) {
    var parent = findBlock(parentId);
    if (!parent) return;
    if (!parent.data.blocks) parent.data.blocks = [];
    var data = JSON.parse(JSON.stringify(BLOCK_DEFAULTS[blockType] || {}));
    var newBlock = { id: uuid(), type: blockType, data: data, settings: { alignment: 'center' } };
    if (index !== undefined && index >= 0) {
      parent.data.blocks.splice(index, 0, newBlock);
    } else {
      parent.data.blocks.push(newBlock);
    }
    markDirty();
    renderCanvas();
  }

  function deleteSectionBlock(parentId, blockIdx) {
    var parent = findBlock(parentId);
    if (!parent || !parent.data.blocks) return;
    var block = parent.data.blocks[blockIdx];
    var label = block ? (BLOCK_LABELS[block.type] || block.type) : '';
    if (!confirm((PB_I18N.delete_block_confirm || 'Delete this {type} block?').replace('{type}', label))) return;
    parent.data.blocks.splice(blockIdx, 1);
    markDirty();
    renderCanvas();
  }

  function moveSectionBlock(parentId, blockIdx, dir) {
    var parent = findBlock(parentId);
    if (!parent || !parent.data.blocks) return;
    var blocks = parent.data.blocks;
    var newIdx = blockIdx + dir;
    if (newIdx < 0 || newIdx >= blocks.length) return;
    var b = blocks.splice(blockIdx, 1)[0];
    blocks.splice(newIdx, 0, b);
    markDirty();
    renderCanvas();
  }

  function duplicateSectionBlock(parentId, blockIdx) {
    var parent = findBlock(parentId);
    if (!parent || !parent.data.blocks || !parent.data.blocks[blockIdx]) return;
    var copy = JSON.parse(JSON.stringify(parent.data.blocks[blockIdx]));
    _regenerateIds(copy);
    parent.data.blocks.splice(blockIdx + 1, 0, copy);
    markDirty();
    renderCanvas();
  }

  function duplicateColumnBlock(parentId, colIdx, blockIdx) {
    var parent = findBlockDeep(parentId);
    if (!parent || !parent.data.columns[colIdx] || !parent.data.columns[colIdx].blocks[blockIdx]) return;
    var copy = JSON.parse(JSON.stringify(parent.data.columns[colIdx].blocks[blockIdx]));
    _regenerateIds(copy);
    parent.data.columns[colIdx].blocks.splice(blockIdx + 1, 0, copy);
    markDirty();
    renderCanvas();
  }

  // Slideshow nested block management (hero mode — blocks inside slides)
  function addBlockToSlide(parentId, blockType, index) {
    var parent = findBlock(parentId);
    if (!parent || !parent.data.slides || !parent.data.slides[_activeSlideIdx(parentId)]) return;
    var slide = parent.data.slides[_activeSlideIdx(parentId)];
    if (!slide.blocks) slide.blocks = [];
    var data = JSON.parse(JSON.stringify(BLOCK_DEFAULTS[blockType] || {}));
    var newBlock = { id: uuid(), type: blockType, data: data, settings: { alignment: 'center' } };
    if (index !== undefined && index >= 0) {
      slide.blocks.splice(index, 0, newBlock);
    } else {
      slide.blocks.push(newBlock);
    }
    markDirty();
    renderCanvas();
  }

  function deleteSlideBlock(parentId, blockIdx) {
    var parent = findBlock(parentId);
    if (!parent || !parent.data.slides || !parent.data.slides[_activeSlideIdx(parentId)]) return;
    var blocks = parent.data.slides[_activeSlideIdx(parentId)].blocks;
    if (!blocks) return;
    var block = blocks[blockIdx];
    var label = block ? (BLOCK_LABELS[block.type] || block.type) : '';
    if (!confirm((PB_I18N.delete_block_confirm || 'Delete this {type} block?').replace('{type}', label))) return;
    blocks.splice(blockIdx, 1);
    markDirty();
    renderCanvas();
  }

  function moveSlideBlock(parentId, blockIdx, dir) {
    var parent = findBlock(parentId);
    if (!parent || !parent.data.slides || !parent.data.slides[_activeSlideIdx(parentId)]) return;
    var blocks = parent.data.slides[_activeSlideIdx(parentId)].blocks;
    if (!blocks) return;
    var newIdx = blockIdx + dir;
    if (newIdx < 0 || newIdx >= blocks.length) return;
    var b = blocks.splice(blockIdx, 1)[0];
    blocks.splice(newIdx, 0, b);
    markDirty();
    renderCanvas();
  }

  function duplicateSlideBlock(parentId, blockIdx) {
    var parent = findBlock(parentId);
    if (!parent || !parent.data.slides || !parent.data.slides[_activeSlideIdx(parentId)]) return;
    var blocks = parent.data.slides[_activeSlideIdx(parentId)].blocks;
    if (!blocks || !blocks[blockIdx]) return;
    var copy = JSON.parse(JSON.stringify(blocks[blockIdx]));
    _regenerateIds(copy);
    blocks.splice(blockIdx + 1, 0, copy);
    markDirty();
    renderCanvas();
  }

  // Track which slide is being edited per slideshow block
  var _slideEditIdx = {};
  function _activeSlideIdx(parentId) {
    return _slideEditIdx[parentId] || 0;
  }

  // Track which tab is active per tabs block
  var _tabEditIdx = {};
  function _activeTabIdx(blockId) {
    return _tabEditIdx[blockId] || 0;
  }

  var _formPreviewFetching = {};

  // Smart picker positioning — opens above if no room below
  function _positionPicker(picker, anchorEl) {
    picker.style.position = 'fixed';
    picker.style.zIndex = '150';
    picker.style.visibility = 'hidden';
    document.body.appendChild(picker);
    var rect = anchorEl.getBoundingClientRect();
    var ph = picker.offsetHeight;
    var spaceBelow = window.innerHeight - rect.bottom - 8;
    if (spaceBelow >= ph) {
      picker.style.top = (rect.bottom + 4) + 'px';
    } else {
      picker.style.top = Math.max(4, rect.top - ph - 4) + 'px';
    }
    picker.style.left = rect.left + 'px';
    picker.style.visibility = '';
  }

  // Show block type picker near a column
  var activePicker = null;
  function showColumnPicker(parentId, colIdx, anchorEl) {
    closeColumnPicker();
    var blockTypes = [
      { type: 'heading', icon: '<path d="M4 4v16"/><path d="M4 12h12"/><path d="M16 4v16"/>', label: PB_I18N.heading },
      { type: 'text', icon: '<path d="M4 7V4h16v3"/><path d="M9 20h6"/><path d="M12 4v16"/>', label: PB_I18N.text },
      { type: 'image', icon: '<rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/>', label: PB_I18N.image },
      { type: 'button', icon: '<rect x="2" y="7" width="20" height="10" rx="2"/><path d="M9 12h6"/>', label: PB_I18N.button },
      { type: 'video', icon: '<polygon points="5 3 19 12 5 21 5 3"/>', label: PB_I18N.video },
      { type: 'video_gallery', icon: '<rect x="2" y="4" width="8" height="6" rx="1"/><polygon points="5 6 8 7.5 5 9" fill="currentColor" stroke="none"/><rect x="14" y="4" width="8" height="6" rx="1"/><polygon points="17 6 20 7.5 17 9" fill="currentColor" stroke="none"/><rect x="2" y="14" width="8" height="6" rx="1"/><polygon points="5 16 8 17.5 5 19" fill="currentColor" stroke="none"/><rect x="14" y="14" width="8" height="6" rx="1"/><polygon points="17 16 20 17.5 17 19" fill="currentColor" stroke="none"/>', label: PB_I18N.video_gallery },
      { type: 'quote', icon: '<path d="M3 21c3 0 7-1 7-8V5c0-1.25-.756-2.017-2-2H4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2 1 0 1 0 1 1v1c0 1-1 2-2 2s-1 .008-1 1.031V21z"/>', label: PB_I18N.quote },
      { type: 'divider', icon: '<path d="M3 12h18"/>', label: PB_I18N.divider },
      { type: 'spacer', icon: '<path d="M12 5v14"/><path d="M5 5h14"/><path d="M5 19h14"/>', label: PB_I18N.spacer },
      { type: 'html', icon: '<polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/>', label: PB_I18N.html },
      { type: 'audio', icon: '<path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/>', label: PB_I18N.audio },
      { type: 'code', icon: '<rect x="4" y="4" width="16" height="16" rx="2"/><path d="M9 10l-2 2 2 2"/><path d="M15 10l2 2-2 2"/>', label: PB_I18N.code },
      { type: 'form', icon: '<rect x="3" y="3" width="18" height="18" rx="2"/><path d="M7 7h10"/><path d="M7 12h10"/><path d="M7 17h4"/>', label: PB_I18N.form }
    ];
    var picker = document.createElement('div');
    picker.className = 'pb-col-picker';
    picker.innerHTML = blockTypes.map(function(bt) {
      return '<button class="pb-col-picker__btn" data-pick-type="' + bt.type + '">' +
        '<svg viewBox="0 0 24 24">' + bt.icon + '</svg>' + bt.label + '</button>';
    }).join('');
    
    // Position near the anchor
    _positionPicker(picker, anchorEl);
    activePicker = picker;
    
    picker.querySelectorAll('[data-pick-type]').forEach(function(btn) {
      btn.addEventListener('click', function(e) {
        e.stopPropagation();
        addBlockToColumn(parentId, colIdx, btn.dataset.pickType);
        closeColumnPicker();
      });
    });
    
    // Close on click outside
    setTimeout(function() {
      document.addEventListener('click', closeColumnPickerOutside);
    }, 10);
  }

  function closeColumnPicker() {
    if (activePicker) {
      activePicker.remove();
      activePicker = null;
    }
    document.removeEventListener('click', closeColumnPickerOutside);
  }

  function closeColumnPickerOutside(e) {
    if (activePicker && !activePicker.contains(e.target)) {
      closeColumnPicker();
    }
  }

  // ============================================================
  // CANVAS INSERTER — "+" button between blocks to add new blocks
  // ============================================================
  var _canvasPickerTypes = [
    { type: 'heading', icon: '<path d="M4 4v16"/><path d="M4 12h12"/><path d="M16 4v16"/>', label: PB_I18N.heading },
    { type: 'text', icon: '<path d="M4 7V4h16v3"/><path d="M9 20h6"/><path d="M12 4v16"/>', label: PB_I18N.text },
    { type: 'image', icon: '<rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/>', label: PB_I18N.image },
    { type: 'button', icon: '<rect x="2" y="7" width="20" height="10" rx="2"/><path d="M9 12h6"/>', label: PB_I18N.button },
    { type: 'divider', icon: '<path d="M3 12h18"/>', label: PB_I18N.divider },
    { type: 'spacer', icon: '<path d="M12 5v14"/><path d="M5 5h14"/><path d="M5 19h14"/>', label: PB_I18N.spacer },
    { type: 'columns', icon: '<rect x="3" y="3" width="7" height="18"/><rect x="14" y="3" width="7" height="18"/>', label: PB_I18N.columns },
    { type: 'section', icon: '<rect x="2" y="4" width="20" height="16" rx="2"/><path d="M2 8h20"/>', label: PB_I18N.section },
    { type: 'video', icon: '<polygon points="5 3 19 12 5 21 5 3"/>', label: PB_I18N.video },
    { type: 'gallery', icon: '<rect x="2" y="2" width="8" height="8" rx="1"/><rect x="14" y="2" width="8" height="8" rx="1"/><rect x="2" y="14" width="8" height="8" rx="1"/><rect x="14" y="14" width="8" height="8" rx="1"/>', label: PB_I18N.gallery },
    { type: 'video_gallery', icon: '<rect x="1" y="3" width="9" height="7" rx="1"/><polygon points="5 5.5 8 7 5 8.5" fill="currentColor" stroke="none"/><rect x="14" y="3" width="9" height="7" rx="1"/><polygon points="18 5.5 21 7 18 8.5" fill="currentColor" stroke="none"/><rect x="1" y="14" width="9" height="7" rx="1"/><polygon points="5 16.5 8 18 5 19.5" fill="currentColor" stroke="none"/><rect x="14" y="14" width="9" height="7" rx="1"/><polygon points="18 16.5 21 18 18 19.5" fill="currentColor" stroke="none"/>', label: PB_I18N.video_gallery },
    { type: 'audio', icon: '<path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/>', label: PB_I18N.audio },
    { type: 'slideshow', icon: '<rect x="2" y="4" width="20" height="16" rx="2"/><polyline points="7 15 9 12 7 9"/><polyline points="17 9 15 12 17 15"/>', label: PB_I18N.slideshow },
    { type: 'ticker', icon: '<rect x="1" y="7" width="5" height="10" rx="1"/><rect x="8" y="7" width="5" height="10" rx="1"/><rect x="15" y="7" width="5" height="10" rx="1"/><path d="M3 21h18"/>', label: PB_I18N.ticker },
    { type: 'quote', icon: '<path d="M3 21c3 0 7-1 7-8V5c0-1.25-.756-2.017-2-2H4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2 1 0 1 0 1 1v1c0 1-1 2-2 2s-1 .008-1 1.031V21z"/>', label: PB_I18N.quote },
    { type: 'table', icon: '<rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18"/><path d="M3 15h18"/><path d="M9 3v18"/>', label: PB_I18N.table },
    { type: 'accordion', icon: '<path d="M3 6h18"/><path d="M3 12h18"/><path d="M3 18h18"/><polyline points="15 8 18 6 15 4"/>', label: PB_I18N.accordion },
    { type: 'tabs', icon: '<rect x="3" y="7" width="18" height="14" rx="2"/><path d="M3 7V5a2 2 0 012-2h4l2 4"/><path d="M11 3h8a2 2 0 012 2v2"/>', label: PB_I18N.tabs },
    { type: 'html', icon: '<polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/>', label: PB_I18N.html },
    { type: 'code', icon: '<rect x="4" y="4" width="16" height="16" rx="2"/><path d="M9 10l-2 2 2 2"/><path d="M15 10l2 2-2 2"/>', label: PB_I18N.code },
    { type: 'form', icon: '<rect x="3" y="3" width="18" height="18" rx="2"/><path d="M7 7h10"/><path d="M7 12h10"/><path d="M7 17h4"/>', label: PB_I18N.form }
  ];

  function showCanvasInserter(index, anchorEl) {
    closeColumnPicker();
    var picker = document.createElement('div');
    picker.className = 'pb-col-picker';
    picker.innerHTML = _canvasPickerTypes.map(function(bt) {
      return '<button class="pb-col-picker__btn" data-pick-type="' + bt.type + '">' +
        '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' + bt.icon + '</svg>' +
        bt.label + '</button>';
    }).join('');
    _positionPicker(picker, anchorEl);
    activePicker = picker;
    picker.querySelectorAll('[data-pick-type]').forEach(function(btn) {
      btn.addEventListener('click', function(e) {
        e.stopPropagation();
        addBlock(btn.dataset.pickType, index);
        closeColumnPicker();
      });
    });
    setTimeout(function() {
      document.addEventListener('click', closeColumnPickerOutside);
    }, 10);
  }

  function bindInserters() {
    $blocks.querySelectorAll('.pb-inserter[data-insert-at]').forEach(function(inserter) {
      var btn = inserter.querySelector('.pb-inserter__btn');
      if (!btn) return;
      btn.addEventListener('click', function(e) {
        e.stopPropagation();
        var index = parseInt(inserter.dataset.insertAt);
        showCanvasInserter(index, btn);
      });
    });
  }

  // ============================================================
  // BIND COLUMN EVENTS — click handlers for nested block actions
  // ============================================================
  function bindColumnEvents() {
    // Add block buttons (empty zones and + buttons)
    $blocks.querySelectorAll('[data-col-add]').forEach(function(el) {
      el.addEventListener('click', function(e) {
        e.stopPropagation();
        var parentId = el.dataset.colAdd;
        var colIdx = parseInt(el.dataset.colIdx);
        showColumnPicker(parentId, colIdx, el);
      });
    });

    // Delete nested block
    $blocks.querySelectorAll('.pb-col-block__delete').forEach(function(btn) {
      btn.addEventListener('click', function(e) {
        e.stopPropagation();
        var block = btn.closest('.pb-col-block');
        deleteColumnBlock(block.dataset.colBlockParent, parseInt(block.dataset.colBlockCol), parseInt(block.dataset.colBlockIdx));
      });
    });

    // Move up nested block
    $blocks.querySelectorAll('.pb-col-block__move-up').forEach(function(btn) {
      btn.addEventListener('click', function(e) {
        e.stopPropagation();
        var block = btn.closest('.pb-col-block');
        moveColumnBlock(block.dataset.colBlockParent, parseInt(block.dataset.colBlockCol), parseInt(block.dataset.colBlockIdx), -1);
      });
    });

    // Move down nested block
    $blocks.querySelectorAll('.pb-col-block__move-down').forEach(function(btn) {
      btn.addEventListener('click', function(e) {
        e.stopPropagation();
        var block = btn.closest('.pb-col-block');
        moveColumnBlock(block.dataset.colBlockParent, parseInt(block.dataset.colBlockCol), parseInt(block.dataset.colBlockIdx), 1);
      });
    });

    // Duplicate nested block in column
    $blocks.querySelectorAll('.pb-col-block__duplicate').forEach(function(btn) {
      btn.addEventListener('click', function(e) {
        e.stopPropagation();
        var block = btn.closest('.pb-col-block');
        duplicateColumnBlock(block.dataset.colBlockParent, parseInt(block.dataset.colBlockCol), parseInt(block.dataset.colBlockIdx));
      });
    });

    // Drag reorder column nested blocks
    var _dragColInfo = null;
    $blocks.querySelectorAll('.pb-col-block').forEach(function(el) {
      var handle = el.querySelector('.pb-col-block__drag-handle');
      if (!handle) return;
      handle.style.cursor = 'grab';
      handle.addEventListener('mousedown', function() { el.setAttribute('draggable', 'true'); handle.style.cursor = 'grabbing'; });
      handle.addEventListener('mouseup', function() { el.removeAttribute('draggable'); handle.style.cursor = 'grab'; });
      el.addEventListener('dragstart', function(e) {
        if (!el.getAttribute('draggable')) { e.preventDefault(); return; }
        e.stopPropagation();
        _dragColInfo = { parentId: el.dataset.colBlockParent, colIdx: parseInt(el.dataset.colBlockCol), blockId: el.dataset.colBlockId };
        e.dataTransfer.setData('application/x-pb-source', 'nested-column');
        e.dataTransfer.effectAllowed = 'move';
        el.style.opacity = '0.4';
      });
      el.addEventListener('dragend', function() {
        el.style.opacity = ''; el.removeAttribute('draggable'); handle.style.cursor = 'grab'; _dragColInfo = null;
        $blocks.querySelectorAll('.pb-col-block').forEach(function(s) { s.style.borderTop = ''; s.style.borderBottom = ''; });
      });
      el.addEventListener('dragover', function(e) {
        if (!_dragColInfo || _dragColInfo.parentId !== el.dataset.colBlockParent || _dragColInfo.colIdx !== parseInt(el.dataset.colBlockCol) || _dragColInfo.blockId === el.dataset.colBlockId) return;
        e.preventDefault(); e.stopPropagation(); e.dataTransfer.dropEffect = 'move';
        var rect = el.getBoundingClientRect(); var midY = rect.top + rect.height / 2;
        el.style.borderTop = e.clientY < midY ? '2px solid var(--pb-primary)' : '';
        el.style.borderBottom = e.clientY >= midY ? '2px solid var(--pb-primary)' : '';
      });
      el.addEventListener('dragleave', function() { el.style.borderTop = ''; el.style.borderBottom = ''; });
      el.addEventListener('drop', function(e) {
        e.preventDefault(); e.stopPropagation(); el.style.borderTop = ''; el.style.borderBottom = '';
        if (!_dragColInfo || _dragColInfo.parentId !== el.dataset.colBlockParent || _dragColInfo.colIdx !== parseInt(el.dataset.colBlockCol) || _dragColInfo.blockId === el.dataset.colBlockId) return;
        var parent = findBlockDeep(_dragColInfo.parentId);
        if (!parent || !parent.data.columns[_dragColInfo.colIdx]) return;
        var blocks = parent.data.columns[_dragColInfo.colIdx].blocks;
        var fromIdx = -1, toIdx = -1;
        blocks.forEach(function(b, i) { if (b.id === _dragColInfo.blockId) fromIdx = i; if (b.id === el.dataset.colBlockId) toIdx = i; });
        if (fromIdx < 0 || toIdx < 0) return;
        var rect = el.getBoundingClientRect(); var insertAfter = e.clientY >= rect.top + rect.height / 2;
        var moved = blocks.splice(fromIdx, 1)[0];
        var newIdx = -1; blocks.forEach(function(b, i) { if (b.id === el.dataset.colBlockId) newIdx = i; });
        if (insertAfter) newIdx++;
        blocks.splice(newIdx, 0, moved);
        _dragColInfo = null; markDirty(); renderCanvas();
      });
    });

    // Drag & drop into columns
    $blocks.querySelectorAll('.pb-col-zone').forEach(function(zone) {
      zone.addEventListener('dragover', function(e) {
        e.preventDefault();
        e.stopPropagation();
        e.dataTransfer.dropEffect = 'copy';
        zone.classList.add('drag-over');
      });
      zone.addEventListener('dragleave', function() {
        zone.classList.remove('drag-over');
      });
      zone.addEventListener('drop', function(e) {
        e.preventDefault();
        e.stopPropagation();
        zone.classList.remove('drag-over');
        var type = e.dataTransfer.getData('text/plain');
        if (type && BLOCK_DEFAULTS[type] && type !== 'columns' && type !== 'section') {
          var parentId = zone.dataset.colParent;
          var colIdx = parseInt(zone.dataset.colIndex);
          addBlockToColumn(parentId, colIdx, type);
        }
      });
    });

    // Click on nested block to open its panel
    $blocks.querySelectorAll('.pb-col-block').forEach(function(el) {
      el.addEventListener('click', function(e) {
        if (e.target.closest('.pb-col-block__toolbar')) return;
        e.stopPropagation();
        var parentId = el.dataset.colBlockParent;
        var colIdx = parseInt(el.dataset.colBlockCol);
        var blockIdx = parseInt(el.dataset.colBlockIdx);
        var parent = findBlockDeep(parentId);
        if (parent && parent.data.columns[colIdx] && parent.data.columns[colIdx].blocks[blockIdx]) {
          var nestedBlock = parent.data.columns[colIdx].blocks[blockIdx];
          openPanel(nestedBlock);
        }
      });
    });

    // ── Section nested blocks ──

    // Add block buttons in sections
    $blocks.querySelectorAll('[data-sec-add]').forEach(function(el) {
      el.addEventListener('click', function(e) {
        e.stopPropagation();
        var parentId = el.dataset.secAdd;
        // Reuse column picker but with section callback
        closeColumnPicker();
        var picker = document.createElement('div');
        picker.className = 'pb-col-picker';
        var blockTypes = [
          { type: 'heading', icon: '<path d="M4 4v16"/><path d="M4 12h12"/><path d="M16 4v16"/>', label: PB_I18N.heading },
          { type: 'text', icon: '<path d="M4 7V4h16v3"/><path d="M9 20h6"/><path d="M12 4v16"/>', label: PB_I18N.text },
          { type: 'image', icon: '<rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/>', label: PB_I18N.image },
          { type: 'button', icon: '<rect x="2" y="7" width="20" height="10" rx="2"/><path d="M9 12h6"/>', label: PB_I18N.button },
          { type: 'divider', icon: '<path d="M3 12h18"/>', label: PB_I18N.divider },
          { type: 'spacer', icon: '<path d="M12 5v14"/><path d="M5 12h14"/>', label: PB_I18N.spacer },
          { type: 'columns', icon: '<rect x="3" y="3" width="7" height="18"/><rect x="14" y="3" width="7" height="18"/>', label: PB_I18N.columns },
          { type: 'quote', icon: '<path d="M3 21c3 0 7-1 7-8V5c0-1.25-.756-2.017-2-2H4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2 1 0 1 0 1 1v1c0 1-1 2-2 2s-1 .008-1 1.031V21z"/>', label: PB_I18N.quote },
          { type: 'form', icon: '<rect x="3" y="3" width="18" height="18" rx="2"/><path d="M7 7h10"/><path d="M7 12h10"/><path d="M7 17h6"/>', label: PB_I18N.form }
        ];
        picker.innerHTML = blockTypes.map(function(bt) {
          return '<button class="pb-col-picker__btn" data-pick-type="' + bt.type + '">' +
            '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' + bt.icon + '</svg>' +
            bt.label + '</button>';
        }).join('');
        _positionPicker(picker, el);
        activePicker = picker;
        picker.querySelectorAll('[data-pick-type]').forEach(function(btn) {
          btn.addEventListener('click', function() {
            closeColumnPicker();
            addBlockToSection(parentId, btn.dataset.pickType);
          });
        });
        setTimeout(function() {
          document.addEventListener('click', closeColumnPickerOutside);
        }, 10);
      });
    });

    // Delete section nested block
    $blocks.querySelectorAll('.pb-sec-block__delete').forEach(function(btn) {
      btn.addEventListener('click', function(e) {
        e.stopPropagation();
        var block = btn.closest('.pb-sec-block');
        deleteSectionBlock(block.dataset.secBlockParent, parseInt(block.dataset.secBlockIdx));
      });
    });

    // Move up section nested block
    $blocks.querySelectorAll('.pb-sec-block__move-up').forEach(function(btn) {
      btn.addEventListener('click', function(e) {
        e.stopPropagation();
        var block = btn.closest('.pb-sec-block');
        moveSectionBlock(block.dataset.secBlockParent, parseInt(block.dataset.secBlockIdx), -1);
      });
    });

    // Move down section nested block
    $blocks.querySelectorAll('.pb-sec-block__move-down').forEach(function(btn) {
      btn.addEventListener('click', function(e) {
        e.stopPropagation();
        var block = btn.closest('.pb-sec-block');
        moveSectionBlock(block.dataset.secBlockParent, parseInt(block.dataset.secBlockIdx), 1);
      });
    });

    // Duplicate section nested block
    $blocks.querySelectorAll('.pb-sec-block__duplicate').forEach(function(btn) {
      btn.addEventListener('click', function(e) {
        e.stopPropagation();
        var block = btn.closest('.pb-sec-block');
        duplicateSectionBlock(block.dataset.secBlockParent, parseInt(block.dataset.secBlockIdx));
      });
    });

    // Drag reorder section nested blocks
    var _dragSecInfo = null;
    $blocks.querySelectorAll('.pb-sec-block').forEach(function(el) {
      var handle = el.querySelector('.pb-sec-block__drag-handle');
      if (!handle) return;

      handle.style.cursor = 'grab';
      handle.addEventListener('mousedown', function() { el.setAttribute('draggable', 'true'); handle.style.cursor = 'grabbing'; });
      handle.addEventListener('mouseup', function() { el.removeAttribute('draggable'); handle.style.cursor = 'grab'; });

      el.addEventListener('dragstart', function(e) {
        if (!el.getAttribute('draggable')) { e.preventDefault(); return; }
        e.stopPropagation();
        _dragSecInfo = { parentId: el.dataset.secBlockParent, blockId: el.dataset.secBlockId };
        e.dataTransfer.setData('application/x-pb-source', 'nested-section');
        e.dataTransfer.effectAllowed = 'move';
        el.style.opacity = '0.4';
      });

      el.addEventListener('dragend', function() {
        el.style.opacity = '';
        el.removeAttribute('draggable');
        handle.style.cursor = 'grab';
        _dragSecInfo = null;
        $blocks.querySelectorAll('.pb-sec-block').forEach(function(s) {
          s.style.borderTop = '';
          s.style.borderBottom = '';
        });
      });

      el.addEventListener('dragover', function(e) {
        if (!_dragSecInfo) return;
        if (_dragSecInfo.parentId !== el.dataset.secBlockParent) return;
        if (_dragSecInfo.blockId === el.dataset.secBlockId) return;
        e.preventDefault();
        e.stopPropagation();
        e.dataTransfer.dropEffect = 'move';
        var rect = el.getBoundingClientRect();
        var midY = rect.top + rect.height / 2;
        if (e.clientY < midY) {
          el.style.borderTop = '2px solid var(--pb-primary)';
          el.style.borderBottom = '';
        } else {
          el.style.borderTop = '';
          el.style.borderBottom = '2px solid var(--pb-primary)';
        }
      });

      el.addEventListener('dragleave', function() {
        el.style.borderTop = '';
        el.style.borderBottom = '';
      });

      el.addEventListener('drop', function(e) {
        e.preventDefault();
        e.stopPropagation();
        el.style.borderTop = '';
        el.style.borderBottom = '';
        if (!_dragSecInfo || _dragSecInfo.parentId !== el.dataset.secBlockParent) return;
        if (_dragSecInfo.blockId === el.dataset.secBlockId) return;

        var parent = findBlock(_dragSecInfo.parentId);
        if (!parent || !parent.data.blocks) return;

        var fromIdx = -1, toIdx = -1;
        parent.data.blocks.forEach(function(b, i) {
          if (b.id === _dragSecInfo.blockId) fromIdx = i;
          if (b.id === el.dataset.secBlockId) toIdx = i;
        });
        if (fromIdx < 0 || toIdx < 0) return;

        var rect = el.getBoundingClientRect();
        var midY = rect.top + rect.height / 2;
        var insertAfter = e.clientY >= midY;

        var moved = parent.data.blocks.splice(fromIdx, 1)[0];
        var newIdx = -1;
        parent.data.blocks.forEach(function(b, i) { if (b.id === el.dataset.secBlockId) newIdx = i; });
        if (insertAfter) newIdx++;
        parent.data.blocks.splice(newIdx, 0, moved);

        _dragSecInfo = null;
        markDirty();
        renderCanvas();
      });
    });

    // Drag & drop into sections
    $blocks.querySelectorAll('.pb-section-zone').forEach(function(zone) {
      zone.addEventListener('dragover', function(e) {
        e.preventDefault();
        e.stopPropagation();
        e.dataTransfer.dropEffect = 'copy';
        zone.style.outline = '2px dashed var(--pb-primary)';
      });
      zone.addEventListener('dragleave', function() {
        zone.style.outline = '';
      });
      zone.addEventListener('drop', function(e) {
        e.preventDefault();
        e.stopPropagation();
        zone.style.outline = '';
        var type = e.dataTransfer.getData('text/plain');
        if (type && BLOCK_DEFAULTS[type] && type !== 'section') {
          addBlockToSection(zone.dataset.secParent, type);
        }
      });
    });

    // Section inserter — "+" between nested blocks
    $blocks.querySelectorAll('[data-sec-insert-parent] .pb-inserter__btn').forEach(function(btn) {
      btn.addEventListener('click', function(e) {
        e.stopPropagation();
        var inserter = btn.closest('.pb-inserter');
        var parentId = inserter.dataset.secInsertParent;
        var index = parseInt(inserter.dataset.secInsertAt);
        closeColumnPicker();
        var picker = document.createElement('div');
        picker.className = 'pb-col-picker';
        var blockTypes = [
          { type: 'heading', icon: '<path d="M4 4v16"/><path d="M4 12h12"/><path d="M16 4v16"/>', label: PB_I18N.heading },
          { type: 'text', icon: '<path d="M4 7V4h16v3"/><path d="M9 20h6"/><path d="M12 4v16"/>', label: PB_I18N.text },
          { type: 'image', icon: '<rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/>', label: PB_I18N.image },
          { type: 'button', icon: '<rect x="2" y="7" width="20" height="10" rx="2"/><path d="M9 12h6"/>', label: PB_I18N.button },
          { type: 'divider', icon: '<path d="M3 12h18"/>', label: PB_I18N.divider },
          { type: 'spacer', icon: '<path d="M12 5v14"/><path d="M5 12h14"/>', label: PB_I18N.spacer },
          { type: 'columns', icon: '<rect x="3" y="3" width="7" height="18"/><rect x="14" y="3" width="7" height="18"/>', label: PB_I18N.columns },
          { type: 'quote', icon: '<path d="M3 21c3 0 7-1 7-8V5c0-1.25-.756-2.017-2-2H4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2 1 0 1 0 1 1v1c0 1-1 2-2 2s-1 .008-1 1.031V21z"/>', label: PB_I18N.quote },
          { type: 'form', icon: '<rect x="3" y="3" width="18" height="18" rx="2"/><path d="M7 7h10"/><path d="M7 12h10"/><path d="M7 17h6"/>', label: PB_I18N.form }
        ];
        picker.innerHTML = blockTypes.map(function(bt) {
          return '<button class="pb-col-picker__btn" data-pick-type="' + bt.type + '"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' + bt.icon + '</svg>' + bt.label + '</button>';
        }).join('');
        _positionPicker(picker, btn);
        activePicker = picker;
        picker.querySelectorAll('[data-pick-type]').forEach(function(pbtn) {
          pbtn.addEventListener('click', function() {
            closeColumnPicker();
            addBlockToSection(parentId, pbtn.dataset.pickType, index);
          });
        });
        setTimeout(function() { document.addEventListener('click', closeColumnPickerOutside); }, 10);
      });
    });

    // Column inserter — "+" between nested blocks
    $blocks.querySelectorAll('[data-col-insert-parent] .pb-inserter__btn').forEach(function(btn) {
      btn.addEventListener('click', function(e) {
        e.stopPropagation();
        var inserter = btn.closest('.pb-inserter');
        var parentId = inserter.dataset.colInsertParent;
        var colIdx = parseInt(inserter.dataset.colInsertCol);
        var index = parseInt(inserter.dataset.colInsertAt);
        closeColumnPicker();
        var picker = document.createElement('div');
        picker.className = 'pb-col-picker';
        picker.innerHTML = _canvasPickerTypes.filter(function(bt) {
          return bt.type !== 'columns' && bt.type !== 'section';
        }).map(function(bt) {
          return '<button class="pb-col-picker__btn" data-pick-type="' + bt.type + '"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' + bt.icon + '</svg>' + bt.label + '</button>';
        }).join('');
        _positionPicker(picker, btn);
        activePicker = picker;
        picker.querySelectorAll('[data-pick-type]').forEach(function(pbtn) {
          pbtn.addEventListener('click', function() {
            closeColumnPicker();
            addBlockToColumn(parentId, colIdx, pbtn.dataset.pickType, index);
          });
        });
        setTimeout(function() { document.addEventListener('click', closeColumnPickerOutside); }, 10);
      });
    });

    // Click on section nested block to open its panel
    $blocks.querySelectorAll('.pb-sec-block').forEach(function(el) {
      el.addEventListener('click', function(e) {
        if (e.target.closest('.pb-sec-block__toolbar')) return;
        e.stopPropagation();
        var parentId = el.dataset.secBlockParent;
        var blockIdx = parseInt(el.dataset.secBlockIdx);
        var parent = findBlock(parentId);
        if (parent && parent.data.blocks && parent.data.blocks[blockIdx]) {
          openPanel(parent.data.blocks[blockIdx]);
        }
      });
    });

    // ── Slideshow nested blocks (hero mode) ──

    // Add block buttons in slideshow slides
    $blocks.querySelectorAll('[data-slide-add]').forEach(function(el) {
      el.addEventListener('click', function(e) {
        e.stopPropagation();
        var parentId = el.dataset.slideAdd;
        closeColumnPicker();
        var picker = document.createElement('div');
        picker.className = 'pb-col-picker';
        var blockTypes = [
          { type: 'heading', icon: '<path d="M4 4v16"/><path d="M4 12h12"/><path d="M16 4v16"/>', label: PB_I18N.heading },
          { type: 'text', icon: '<path d="M4 7V4h16v3"/><path d="M9 20h6"/><path d="M12 4v16"/>', label: PB_I18N.text },
          { type: 'button', icon: '<rect x="2" y="7" width="20" height="10" rx="2"/><path d="M9 12h6"/>', label: PB_I18N.button },
          { type: 'divider', icon: '<path d="M3 12h18"/>', label: PB_I18N.divider },
          { type: 'spacer', icon: '<path d="M12 5v14"/><path d="M5 12h14"/>', label: PB_I18N.spacer }
        ];
        picker.innerHTML = blockTypes.map(function(bt) {
          return '<button class="pb-col-picker__btn" data-pick-type="' + bt.type + '">' +
            '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' + bt.icon + '</svg>' +
            bt.label + '</button>';
        }).join('');
        _positionPicker(picker, el);
        activePicker = picker;
        picker.querySelectorAll('[data-pick-type]').forEach(function(btn) {
          btn.addEventListener('click', function() {
            closeColumnPicker();
            addBlockToSlide(parentId, btn.dataset.pickType);
          });
        });
        setTimeout(function() {
          document.addEventListener('click', closeColumnPickerOutside);
        }, 10);
      });
    });

    // Delete slideshow nested block
    $blocks.querySelectorAll('.pb-slide-block__delete').forEach(function(btn) {
      btn.addEventListener('click', function(e) {
        e.stopPropagation();
        var block = btn.closest('.pb-slide-block');
        deleteSlideBlock(block.dataset.slideBlockParent, parseInt(block.dataset.slideBlockIdx));
      });
    });

    // Move up slideshow nested block
    $blocks.querySelectorAll('.pb-slide-block__move-up').forEach(function(btn) {
      btn.addEventListener('click', function(e) {
        e.stopPropagation();
        var block = btn.closest('.pb-slide-block');
        moveSlideBlock(block.dataset.slideBlockParent, parseInt(block.dataset.slideBlockIdx), -1);
      });
    });

    // Move down slideshow nested block
    $blocks.querySelectorAll('.pb-slide-block__move-down').forEach(function(btn) {
      btn.addEventListener('click', function(e) {
        e.stopPropagation();
        var block = btn.closest('.pb-slide-block');
        moveSlideBlock(block.dataset.slideBlockParent, parseInt(block.dataset.slideBlockIdx), 1);
      });
    });

    // Duplicate slideshow nested block
    $blocks.querySelectorAll('.pb-slide-block__duplicate').forEach(function(btn) {
      btn.addEventListener('click', function(e) {
        e.stopPropagation();
        var block = btn.closest('.pb-slide-block');
        duplicateSlideBlock(block.dataset.slideBlockParent, parseInt(block.dataset.slideBlockIdx));
      });
    });

    // Drag reorder slideshow nested blocks
    var _dragSlideInfo = null;
    $blocks.querySelectorAll('.pb-slide-block').forEach(function(el) {
      var handle = el.querySelector('.pb-slide-block__drag-handle');
      if (!handle) return;
      handle.style.cursor = 'grab';
      handle.addEventListener('mousedown', function() { el.setAttribute('draggable', 'true'); handle.style.cursor = 'grabbing'; });
      handle.addEventListener('mouseup', function() { el.removeAttribute('draggable'); handle.style.cursor = 'grab'; });
      el.addEventListener('dragstart', function(e) {
        if (!el.getAttribute('draggable')) { e.preventDefault(); return; }
        e.stopPropagation();
        _dragSlideInfo = { parentId: el.dataset.slideBlockParent, blockId: el.dataset.slideBlockId };
        e.dataTransfer.setData('application/x-pb-source', 'nested-slide');
        e.dataTransfer.effectAllowed = 'move';
        el.style.opacity = '0.4';
      });
      el.addEventListener('dragend', function() {
        el.style.opacity = ''; el.removeAttribute('draggable'); handle.style.cursor = 'grab'; _dragSlideInfo = null;
        $blocks.querySelectorAll('.pb-slide-block').forEach(function(s) { s.style.borderTop = ''; s.style.borderBottom = ''; });
      });
      el.addEventListener('dragover', function(e) {
        if (!_dragSlideInfo || _dragSlideInfo.parentId !== el.dataset.slideBlockParent || _dragSlideInfo.blockId === el.dataset.slideBlockId) return;
        e.preventDefault(); e.stopPropagation(); e.dataTransfer.dropEffect = 'move';
        var rect = el.getBoundingClientRect(); var midY = rect.top + rect.height / 2;
        el.style.borderTop = e.clientY < midY ? '2px solid var(--pb-primary)' : '';
        el.style.borderBottom = e.clientY >= midY ? '2px solid var(--pb-primary)' : '';
      });
      el.addEventListener('dragleave', function() { el.style.borderTop = ''; el.style.borderBottom = ''; });
      el.addEventListener('drop', function(e) {
        e.preventDefault(); e.stopPropagation(); el.style.borderTop = ''; el.style.borderBottom = '';
        if (!_dragSlideInfo || _dragSlideInfo.parentId !== el.dataset.slideBlockParent || _dragSlideInfo.blockId === el.dataset.slideBlockId) return;
        var parent = findBlock(_dragSlideInfo.parentId);
        if (!parent || !parent.data.slides || !parent.data.slides[_activeSlideIdx(_dragSlideInfo.parentId)]) return;
        var blocks = parent.data.slides[_activeSlideIdx(_dragSlideInfo.parentId)].blocks;
        if (!blocks) return;
        var fromIdx = -1, toIdx = -1;
        blocks.forEach(function(b, i) { if (b.id === _dragSlideInfo.blockId) fromIdx = i; if (b.id === el.dataset.slideBlockId) toIdx = i; });
        if (fromIdx < 0 || toIdx < 0) return;
        var rect = el.getBoundingClientRect(); var insertAfter = e.clientY >= rect.top + rect.height / 2;
        var moved = blocks.splice(fromIdx, 1)[0];
        var newIdx = -1; blocks.forEach(function(b, i) { if (b.id === el.dataset.slideBlockId) newIdx = i; });
        if (insertAfter) newIdx++;
        blocks.splice(newIdx, 0, moved);
        _dragSlideInfo = null; markDirty(); renderCanvas();
      });
    });

    // Slideshow inserter — "+" between nested blocks
    $blocks.querySelectorAll('[data-slide-insert-parent] .pb-inserter__btn').forEach(function(btn) {
      btn.addEventListener('click', function(e) {
        e.stopPropagation();
        var inserter = btn.closest('.pb-inserter');
        var parentId = inserter.dataset.slideInsertParent;
        var index = parseInt(inserter.dataset.slideInsertAt);
        closeColumnPicker();
        var picker = document.createElement('div');
        picker.className = 'pb-col-picker';
        var blockTypes = [
          { type: 'heading', icon: '<path d="M4 4v16"/><path d="M4 12h12"/><path d="M16 4v16"/>', label: PB_I18N.heading },
          { type: 'text', icon: '<path d="M4 7V4h16v3"/><path d="M9 20h6"/><path d="M12 4v16"/>', label: PB_I18N.text },
          { type: 'image', icon: '<rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/>', label: PB_I18N.image },
          { type: 'button', icon: '<rect x="2" y="7" width="20" height="10" rx="2"/><path d="M9 12h6"/>', label: PB_I18N.button },
          { type: 'divider', icon: '<path d="M3 12h18"/>', label: PB_I18N.divider },
          { type: 'spacer', icon: '<path d="M12 5v14"/><path d="M5 12h14"/>', label: PB_I18N.spacer }
        ];
        picker.innerHTML = blockTypes.map(function(bt) {
          return '<button class="pb-col-picker__btn" data-pick-type="' + bt.type + '"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' + bt.icon + '</svg>' + bt.label + '</button>';
        }).join('');
        _positionPicker(picker, btn);
        activePicker = picker;
        picker.querySelectorAll('[data-pick-type]').forEach(function(pbtn) {
          pbtn.addEventListener('click', function() {
            closeColumnPicker();
            addBlockToSlide(parentId, pbtn.dataset.pickType, index);
          });
        });
        setTimeout(function() { document.addEventListener('click', closeColumnPickerOutside); }, 10);
      });
    });

    // Drag & drop into slideshow slides
    $blocks.querySelectorAll('.pb-slide-zone').forEach(function(zone) {
      zone.addEventListener('dragover', function(e) {
        e.preventDefault();
        e.stopPropagation();
        e.dataTransfer.dropEffect = 'copy';
        zone.style.outline = '2px dashed rgba(255,255,255,0.6)';
      });
      zone.addEventListener('dragleave', function() {
        zone.style.outline = '';
      });
      zone.addEventListener('drop', function(e) {
        e.preventDefault();
        e.stopPropagation();
        zone.style.outline = '';
        var type = e.dataTransfer.getData('text/plain');
        if (type && BLOCK_DEFAULTS[type] && type !== 'section' && type !== 'columns' && type !== 'slideshow') {
          addBlockToSlide(zone.dataset.slideParent, type);
        }
      });
    });

    // Click on slideshow nested block to open its panel
    $blocks.querySelectorAll('.pb-slide-block').forEach(function(el) {
      el.addEventListener('click', function(e) {
        if (e.target.closest('.pb-slide-block__toolbar')) return;
        e.stopPropagation();
        var parentId = el.dataset.slideBlockParent;
        var blockIdx = parseInt(el.dataset.slideBlockIdx);
        var parent = findBlock(parentId);
        if (parent && parent.data.slides && parent.data.slides[_activeSlideIdx(parentId)] && parent.data.slides[_activeSlideIdx(parentId)].blocks && parent.data.slides[_activeSlideIdx(parentId)].blocks[blockIdx]) {
          openPanel(parent.data.slides[_activeSlideIdx(parentId)].blocks[blockIdx]);
        }
      });
    });

    // Slide tab selector — switch which slide is being edited
    $blocks.querySelectorAll('.pb-slide-tab').forEach(function(tab) {
      tab.addEventListener('click', function(e) {
        e.stopPropagation();
        var parentId = tab.dataset.slideTabParent;
        var slideIdx = parseInt(tab.dataset.slideTabIdx);
        _slideEditIdx[parentId] = slideIdx;
        renderCanvas();
      });
    });

    // ── Slideshow canvas: add new slide via "+" tab ──
    $blocks.querySelectorAll('[data-slide-canvas-add]').forEach(function(btn) {
      btn.addEventListener('click', function(e) {
        e.stopPropagation();
        var blockId = btn.dataset.slideCanvasAdd;
        var block = findBlock(blockId);
        if (!block) return;
        block.data.slides = block.data.slides || [];
        block.data.slides.push({ image: '', alt: '', blocks: [] });
        _slideEditIdx[blockId] = block.data.slides.length - 1;
        markDirty();
        renderCanvas();
        openPanel(block);
      });
    });

    // ── Slideshow canvas: pick slide background image ──
    $blocks.querySelectorAll('[data-slide-pick]').forEach(function(el) {
      el.addEventListener('click', function(e) {
        e.stopPropagation();
        var blockId = el.dataset.slidePick;
        var slideIdx = parseInt(el.dataset.slidePickIdx);
        var block = findBlock(blockId);
        if (!block || !block.data.slides || !block.data.slides[slideIdx]) return;
        // Open image picker — on select, set slide image + alt
        pbPickImage('data.slides.' + slideIdx + '.image');
        // Temporarily select this block so panel alt field gets synced
        openPanel(block);
      });
    });

    // ── Slideshow canvas: clear slide background image ──
    $blocks.querySelectorAll('[data-slide-clear]').forEach(function(btn) {
      btn.addEventListener('click', function(e) {
        e.stopPropagation();
        var blockId = btn.dataset.slideClear;
        var slideIdx = parseInt(btn.dataset.slideClearIdx);
        var block = findBlock(blockId);
        if (block && block.data.slides && block.data.slides[slideIdx]) {
          block.data.slides[slideIdx].image = '';
          block.data.slides[slideIdx].alt = '';
          markDirty();
          renderCanvas();
          openPanel(block);
        }
      });
    });

    // Tab button click — switch active tab panel via CSS classes
    $blocks.querySelectorAll('[data-tab-switch]').forEach(function(btn) {
      btn.addEventListener('mousedown', function(e) {
        if (btn.classList.contains('is-active')) return;
        e.preventDefault();
        var blockId = btn.dataset.tabBlock;
        var idx = parseInt(btn.dataset.tabSwitch);
        _tabEditIdx[blockId] = idx;
        var pbItem = btn.closest('.pb-item');
        pbItem.querySelectorAll('.pb-tabs-btn[data-tab-block="' + blockId + '"]').forEach(function(b) {
          b.classList.toggle('is-active', parseInt(b.dataset.tabSwitch) === idx);
        });
        pbItem.querySelectorAll('.pb-tab-panel[data-tab-block="' + blockId + '"]').forEach(function(p) {
          p.classList.toggle('is-active', parseInt(p.dataset.tabPanel) === idx);
        });
      });
    });

    // Accordion header click — toggle item open/closed via CSS class
    $blocks.querySelectorAll('[data-acc-toggle]').forEach(function(el) {
      el.addEventListener('click', function(e) {
        if (e.target.closest('.pb-inline-acc-title')) return;
        var item = el.closest('.pb-acc-item');
        if (item) item.classList.toggle('is-open');
      });
    });

    // ── Empty image blocks: click to open panel + auto-trigger library picker ──
    $blocks.querySelectorAll('[data-image-pick-block]').forEach(function(el) {
      el.addEventListener('click', function(e) {
        e.stopPropagation();
        var blockId = el.dataset.imagePickBlock;
        var block = findBlockDeep(blockId);
        if (!block) return;
        openPanel(block);
        // Auto-open image library picker after panel renders
        setTimeout(function() {
          var pickBtn = $panelBody.querySelector('.pb-pick-image-btn[data-target-key="data.url"]');
          if (pickBtn) pickBtn.click();
        }, 150);
      });
    });

    // Clear image — return to empty image block
    $blocks.querySelectorAll('[data-clear-image]').forEach(function(btn) {
      btn.addEventListener('click', function(e) {
        e.stopPropagation();
        var blockId = btn.dataset.clearImage;
        var block = findBlockDeep(blockId);
        if (block) {
          block.data.url = '';
          block.data.alt = '';
          markDirty();
          renderCanvas();
          openPanel(block);
        }
      });
    });

    // ── Empty form blocks: click to open form picker modal ──
    $blocks.querySelectorAll('[data-form-pick-block]').forEach(function(el) {
      el.addEventListener('click', function(e) {
        e.stopPropagation();
        var blockId = el.dataset.formPickBlock;
        var block = findBlockDeep(blockId);
        if (!block) return;
        pbPickForm(block);
      });
    });

    // ── Clear form — return to empty form block ──
    $blocks.querySelectorAll('[data-clear-form]').forEach(function(btn) {
      btn.addEventListener('click', function(e) {
        e.stopPropagation();
        var blockId = btn.dataset.clearForm;
        var block = findBlockDeep(blockId);
        if (block) {
          block.data.form_id = '';
          block.data._form_title = '';
          block.data._form_title_raw = '';
          block.data._fields = null;
          block.data._settings = null;
          block.data._description = '';
          markDirty();
          renderCanvas();
          openPanel(block);
        }
      });
    });

    // ── Video canvas: clear video (remove from single video block) ──
    $blocks.querySelectorAll('[data-clear-video]').forEach(function(btn) {
      btn.addEventListener('click', function(e) {
        e.stopPropagation();
        var blockId = btn.dataset.clearVideo;
        var block = findBlockDeep(blockId);
        if (block) {
          block.data.url = '';
          block.data.embed_id = '';
          block.data.source = '';
          block.data.title = '';
          markDirty();
          renderCanvas();
          openPanel(block);
        }
      });
    });

    // ── Video canvas: pick video (empty state click) ──
    $blocks.querySelectorAll('[data-video-pick-block]').forEach(function(el) {
      el.addEventListener('click', function(e) {
        e.stopPropagation();
        var blockId = el.dataset.videoPickBlock;
        var block = findBlockDeep(blockId);
        if (block) {
          openPanel(block);
          pbPickSingleVideo(block);
        }
      });
    });

    // ── Gallery canvas: remove individual image ──
    $blocks.querySelectorAll('[data-gal-canvas-remove]').forEach(function(btn) {
      btn.addEventListener('click', function(e) {
        e.stopPropagation();
        var blockId = btn.dataset.galCanvasBlock;
        var idx = parseInt(btn.dataset.galCanvasRemove);
        var block = findBlockDeep(blockId);
        if (block && block.data.images && idx >= 0 && idx < block.data.images.length) {
          block.data.images.splice(idx, 1);
          markDirty();
          renderCanvas();
          if (state.selectedId === blockId) openPanel(block);
        }
      });
    });

    // ── Gallery canvas: add images from library ──
    $blocks.querySelectorAll('[data-gal-canvas-add]').forEach(function(el) {
      el.addEventListener('click', function(e) {
        e.stopPropagation();
        var blockId = el.dataset.galCanvasAdd;
        var block = findBlockDeep(blockId);
        if (block) {
          openPanel(block);
          pbPickGalleryImages(block);
        }
      });
    });

    // ── Video Gallery canvas: remove individual video ──
    $blocks.querySelectorAll('[data-vgal-canvas-remove]').forEach(function(btn) {
      btn.addEventListener('click', function(e) {
        e.stopPropagation();
        var blockId = btn.dataset.vgalCanvasBlock;
        var idx = parseInt(btn.dataset.vgalCanvasRemove);
        var block = findBlockDeep(blockId);
        if (block && block.data.videos && idx >= 0 && idx < block.data.videos.length) {
          block.data.videos.splice(idx, 1);
          markDirty();
          renderCanvas();
          if (state.selectedId === blockId) openPanel(block);
        }
      });
    });

    // ── Video Gallery canvas: add video ──
    $blocks.querySelectorAll('[data-vgal-canvas-add]').forEach(function(el) {
      el.addEventListener('click', function(e) {
        e.stopPropagation();
        var blockId = el.dataset.vgalCanvasAdd;
        var block = findBlockDeep(blockId);
        if (block) {
          openPanel(block);
          pbAddVideoToGallery(block);
        }
      });
    });

    // ── Gallery canvas: reorder images ──
    $blocks.querySelectorAll('[data-gal-move-block]').forEach(function(btn) {
      btn.addEventListener('click', function(e) {
        e.stopPropagation();
        var blockId = btn.dataset.galMoveBlock;
        var from = parseInt(btn.dataset.galMoveFrom);
        var dir = parseInt(btn.dataset.galMoveDir);
        var block = findBlockDeep(blockId);
        if (!block || !block.data.images) return;
        var to = from + dir;
        if (to < 0 || to >= block.data.images.length) return;
        var item = block.data.images.splice(from, 1)[0];
        block.data.images.splice(to, 0, item);
        markDirty();
        renderCanvas();
        if (state.selectedId === blockId) openPanel(block);
      });
    });

    // ── Video Gallery canvas: reorder videos ──
    $blocks.querySelectorAll('[data-vgal-move-block]').forEach(function(btn) {
      btn.addEventListener('click', function(e) {
        e.stopPropagation();
        var blockId = btn.dataset.vgalMoveBlock;
        var from = parseInt(btn.dataset.vgalMoveFrom);
        var dir = parseInt(btn.dataset.vgalMoveDir);
        var block = findBlockDeep(blockId);
        if (!block || !block.data.videos) return;
        var to = from + dir;
        if (to < 0 || to >= block.data.videos.length) return;
        var item = block.data.videos.splice(from, 1)[0];
        block.data.videos.splice(to, 0, item);
        markDirty();
        renderCanvas();
        if (state.selectedId === blockId) openPanel(block);
      });
    });

    // ── Ticker canvas: remove individual image ──
    $blocks.querySelectorAll('[data-ticker-canvas-remove]').forEach(function(btn) {
      btn.addEventListener('click', function(e) {
        e.stopPropagation();
        var blockId = btn.dataset.tickerCanvasBlock;
        var idx = parseInt(btn.dataset.tickerCanvasRemove);
        var block = findBlockDeep(blockId);
        if (block && block.data.images && idx >= 0 && idx < block.data.images.length) {
          block.data.images.splice(idx, 1);
          markDirty();
          renderCanvas();
          if (state.selectedId === blockId) openPanel(block);
        }
      });
    });

    // ── Ticker canvas: add images from library ──
    $blocks.querySelectorAll('[data-ticker-canvas-add]').forEach(function(el) {
      el.addEventListener('click', function(e) {
        e.stopPropagation();
        var blockId = el.dataset.tickerCanvasAdd;
        var block = findBlockDeep(blockId);
        if (block) {
          openPanel(block);
          pbPickGalleryImages(block);
        }
      });
    });
  }
  function openPanel(block) {
    state.selectedId = block.id;
    $panel.classList.add('is-open');
    $panelTitle.textContent = (BLOCK_LABELS[block.type] || block.type) + ' — ' + PB_I18N.block_settings;
    $panelBody.innerHTML = getPanelHTML(block);
    bindPanelEvents(block);

    // Highlight in canvas
    $blocks.querySelectorAll('.pb-item').forEach(function(el) {
      el.classList.toggle('is-selected', el.dataset.id === block.id);
    });
  }

  function closePanel() {
    state.selectedId = null;
    $panel.classList.remove('is-open');
    $blocks.querySelectorAll('.pb-item.is-selected').forEach(function(el) {
      el.classList.remove('is-selected');
    });
  }

  function getPanelHTML(block) {
    var d = block.data || {};
    var s = block.settings || {};

    var html = '';

    // Type-specific fields
    switch (block.type) {
      case 'heading':
        html += field(PB_I18N.text_content, 'input', 'data.text', d.text || '');
        html += fieldSelect(PB_I18N.level, 'data.level', d.level || 2, [
          {v: 1, l: 'H1'}, {v: 2, l: 'H2'}, {v: 3, l: 'H3'}, {v: 4, l: 'H4'}, {v: 5, l: 'H5'}, {v: 6, l: 'H6'}
        ]);
        break;
      case 'text':
        html += '<div class="pb-field pb-text-canvas-hint">' +
          '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 013 3L7 19l-4 1 1-4L16.5 3.5z"/></svg>' +
          '<span>' + (PB_I18N.text_edit_canvas_hint || 'Click the text on canvas to edit') + '</span>' +
          '</div>';
        break;
      case 'image':
        html += fieldImageUrl(PB_I18N.image_url, 'data.url', d.url || '', PB_I18N.image_url_hint);
        // Hidden field for alt — populated by image picker from library
        html += '<input type="hidden" data-key="data.alt" value="' + esc(d.alt || '') + '">';
        // Show image preview in panel
        if (d.url) {
          html += '<div class="pb-panel-preview"><img src="' + esc(d.url) + '" class="pb-panel-preview" alt=""></div>';
        }
        // Show alt from library (read-only)
        if (d.alt) {
          html += '<div class="pb-alt-badge pb-alt-badge--ok">' +
            '<span class="pb-alt-badge__label">alt:</span> ' + esc(d.alt) +
            '</div>';
          html += '<p class="pb-text-hint pb-hint--pull-lg">' + (PB_I18N.alt_text_library_hint || 'Inherited from media library') + '</p>';
        } else if (d.url) {
          html += '<div class="pb-alt-badge pb-alt-badge--warn">' +
            '⚠ ' + (PB_I18N.alt_missing || 'No alt text — select image from library') +
            '</div>';
        }
        html += field(PB_I18N.caption, 'input', 'data.caption', d.caption || '');
        html += field(PB_I18N.link_url, 'input', 'data.link_url', d.link_url || '');
        html += fieldSelect(PB_I18N.size, 'data.size', d.size || 'full', [
          {v: 'full', l: PB_I18N.size_full}, {v: 'medium', l: PB_I18N.size_medium}, {v: 'small', l: PB_I18N.size_small}
        ]);
        break;
      case 'button':
        html += field(PB_I18N.button_text, 'input', 'data.text', d.text || '');
        html += field(PB_I18N.button_url, 'input', 'data.url', d.url || '#');
        html += fieldSelect(PB_I18N.style, 'data.style', d.style || 'primary', [
          {v: 'primary', l: PB_I18N.style_primary}, {v: 'outline', l: PB_I18N.style_outline}, {v: 'ghost', l: PB_I18N.style_ghost}
        ]);
        html += fieldSelect(PB_I18N.size, 'data.size', d.size || 'md', [
          {v: 'sm', l: PB_I18N.size_sm}, {v: 'md', l: PB_I18N.size_md}, {v: 'lg', l: PB_I18N.size_lg}
        ]);
        html += fieldSelect(PB_I18N.target, 'data.target', d.target || '_self', [
          {v: '_self', l: PB_I18N.target_self}, {v: '_blank', l: PB_I18N.target_blank}
        ]);
        break;
      case 'divider':
        html += fieldSelect(PB_I18N.style, 'data.style', d.style || 'solid', [
          {v: 'solid', l: PB_I18N.style_solid}, {v: 'dashed', l: PB_I18N.style_dashed}, {v: 'dotted', l: PB_I18N.style_dotted}, {v: 'gradient', l: PB_I18N.style_gradient}
        ]);
        break;
      case 'spacer':
        html += field(PB_I18N.height_px, 'number', 'data.height', d.height || '48');
        break;
      case 'columns':
        html += fieldSelect(PB_I18N.columns_layout, 'data.layout', d.layout || '1/2+1/2', [
          {v: '1', l: '100% (1 col)'},
          {v: '1/2+1/2', l: '50% + 50%'}, {v: '1/3+1/3+1/3', l: '33% + 33% + 33%'},
          {v: '2/3+1/3', l: '66% + 33%'}, {v: '1/3+2/3', l: '33% + 66%'},
          {v: '1/4+1/4+1/4+1/4', l: '25% × 4'}
        ]);
        html += '<p class="pb-text-hint pb-mt-2 pb-panel-count">' + PB_I18N.columns_hint + '</p>';
        break;
      case 'video':
        if (d.embed_id) {
          var svThumb = '';
          if (d.source && d.embed_id) {
            svThumb = videoThumbUrl(d.source, d.embed_id);
          }
          html += '<div class="pb-video-wrap--sm pb-mb-2">';
          if (svThumb) {
            html += '<div class="pb-aspect-video"><img src="' + svThumb + '" alt="' + esc(d.title || '') + '" class="pb-cover-img"></div>';
          } else {
            html += '<div class="pb-aspect-video pb-video-placeholder pb-video-placeholder--sm">' + (d.source === 'vimeo' ? 'Vimeo' : '▶') + '</div>';
          }
          html += '<div class="pb-overlay-center--no-events"><div class="pb-play-btn pb-play-btn--xs">▶</div></div>';
          if (d.title) {
            html += '<div class="pb-thumb-label" title="' + esc(d.title) + '">' + esc(d.title) + '</div>';
          }
          html += '<button class="pb-video-clear-btn pb-thumb-remove" title="' + (PB_I18N.remove || 'Remove') + '">&times;</button>';
          html += '</div>';
        }
        html += '<button type="button" class="pb-video-pick-btn pb-dashed-add">' + (d.embed_id ? '↻ ' + (PB_I18N.video_url || 'Change video') : '+ ' + (PB_I18N.video_gallery_add || 'Add video')) + '</button>';
        html += fieldSelect(PB_I18N.aspect_ratio, 'data.aspect_ratio', d.aspect_ratio || '16/9', [
          {v: '16/9', l: '16:9'}, {v: '4/3', l: '4:3'}, {v: '1/1', l: '1:1'}
        ]);
        break;
      case 'html':
        html += fieldTextarea(PB_I18N.html_code, 'data.code', d.code || '');
        break;
      case 'quote':
        html += fieldTextarea(PB_I18N.quote_text, 'data.text', d.text || '');
        html += field(PB_I18N.author, 'input', 'data.author', d.author || '');
        break;
      case 'code':
        html += '<div class="pb-field pb-text-canvas-hint">' +
          '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 013 3L7 19l-4 1 1-4L16.5 3.5z"/></svg>' +
          '<span>' + (PB_I18N.text_edit_canvas_hint || 'Click the text on canvas to edit') + '</span></div>';
        html += fieldSelect(PB_I18N.code_language, 'data.language', d.language || 'javascript', [
          {v: 'html', l: 'HTML'}, {v: 'css', l: 'CSS'}, {v: 'javascript', l: 'JavaScript'},
          {v: 'typescript', l: 'TypeScript'}, {v: 'python', l: 'Python'}, {v: 'php', l: 'PHP'},
          {v: 'sql', l: 'SQL'}, {v: 'bash', l: 'Bash'}, {v: 'json', l: 'JSON'},
          {v: 'xml', l: 'XML'}, {v: 'markdown', l: 'Markdown'}, {v: 'plaintext', l: 'Plain Text'}
        ]);
        break;
      case 'audio':
        html += field(PB_I18N.audio_url, 'input', 'data.url', d.url || '');
        html += field(PB_I18N.audio_title, 'input', 'data.title', d.title || '');
        break;
      case 'accordion':
        html += '<p class="pb-text-hint pb-mb-3 pb-panel-count">' + (d.items||[]).length + ' ' + (PB_I18N.accordion_items || 'items').toLowerCase() + '</p>';
        if ((d.items || []).length === 0) {
          html += '<div class="pb-picker-zone--sm">' + (PB_I18N.empty_hint || 'No items yet. Click the button below to add one.') + '</div>';
        }
        (d.items || []).forEach(function(item, i) {
          html += '<div class="pb-item-card">';
          html += field(PB_I18N.text_content + ' ' + (i+1), 'input', 'data.items.' + i + '.title', item.title || '');
          html += '<div class="pb-field pb-text-canvas-hint">' +
            '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 013 3L7 19l-4 1 1-4L16.5 3.5z"/></svg>' +
            '<span>' + (PB_I18N.text_edit_canvas_hint || 'Click the text on canvas to edit') + '</span></div>';
          html += '<button class="pb-btn pb-btn--danger pb-btn--sm" data-remove-item="accordion" data-index="' + i + '">' + PB_I18N.remove + '</button>';
          html += '</div>';
        });
        html += '<button class="pb-btn pb-btn--outline pb-btn--sm pb-mt-1" data-add-item="accordion">+ ' + PB_I18N.add_item + '</button>';
        break;
      case 'tabs':
        html += '<p class="pb-text-hint pb-mb-3 pb-panel-count">' + (d.tabs||[]).length + ' ' + (PB_I18N.tabs_count || 'tabs').toLowerCase() + '</p>';
        if ((d.tabs || []).length === 0) {
          html += '<div class="pb-picker-zone--sm">' + (PB_I18N.empty_hint || 'No items yet. Click the button below to add one.') + '</div>';
        }
        (d.tabs || []).forEach(function(tab, i) {
          html += '<div class="pb-item-card">';
          html += field('Tab ' + (i+1), 'input', 'data.tabs.' + i + '.title', tab.title || '');
          html += '<div class="pb-field pb-text-canvas-hint">' +
            '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 013 3L7 19l-4 1 1-4L16.5 3.5z"/></svg>' +
            '<span>' + (PB_I18N.text_edit_canvas_hint || 'Click the text on canvas to edit') + '</span></div>';
          html += '<button class="pb-btn pb-btn--danger pb-btn--sm" data-remove-item="tabs" data-index="' + i + '">' + PB_I18N.remove + '</button>';
          html += '</div>';
        });
        html += '<button class="pb-btn pb-btn--outline pb-btn--sm pb-mt-1" data-add-item="tabs">+ ' + PB_I18N.add_tab + '</button>';
        break;
      case 'table':
        var headers = d.headers || [];
        var rows = d.rows || [];
        html += fieldSelect((PB_I18N.table_width || 'Table width'), 'data.table_width', d.table_width || 'full', [
          {v: 'full', l: (PB_I18N.size_full || 'Full width')}, {v: 'auto', l: (PB_I18N.size_auto || 'Auto')}
        ]);
        html += '<div class="pb-field">';
        html += '<div class="pb-table-editor" id="pbTableEditor"><table>';
        // Header row with per-column delete buttons
        html += '<tr>';
        headers.forEach(function(h, hi) {
          html += '<th><div class="pb-table-header-row"><input type="text" data-table-header="' + hi + '" value="' + esc(stripTags(h)) + '" placeholder="' + PB_I18N.heading + '" class="pb-flex-1">';
          if (headers.length > 1) {
            html += '<button type="button" data-table-remove-col="' + hi + '" class="pb-icon-btn--ghost-sm" title="' + (PB_I18N.remove || 'Remove') + '">✕</button>';
          }
          html += '</div></th>';
        });
        html += '</tr>';
        // Data rows with per-row delete buttons
        rows.forEach(function(row, ri) {
          html += '<tr>';
          headers.forEach(function(_, ci) {
            var cellVal = (row && row[ci]) ? row[ci] : '';
            html += '<td><input type="text" data-table-cell data-row="' + ri + '" data-col="' + ci + '" value="' + esc(stripTags(cellVal)) + '"></td>';
          });
          if (rows.length > 1) {
            html += '<td class="pb-table-remove-cell"><button type="button" data-table-insert-row="' + ri + '" class="pb-icon-btn--ghost" title="' + (PB_I18N.table_insert_row || 'Insert row') + '">+</button><button type="button" data-table-remove-row="' + ri + '" class="pb-icon-btn--ghost" title="' + (PB_I18N.remove || 'Remove') + '">✕</button></td>';
          }
          html += '</tr>';
        });
        html += '</table></div>';
        html += '<div class="pb-table-actions">';
        html += '<button type="button" class="pb-btn pb-btn--outline pb-btn--sm" data-table-action="add-row">+ ' + (PB_I18N.table_add_row || 'Row') + '</button>';
        html += '<button type="button" class="pb-btn pb-btn--outline pb-btn--sm" data-table-action="add-col">+ ' + (PB_I18N.table_add_col || 'Column') + '</button>';
        html += '</div>';
        // Per-column alignment
        var colAligns = d.column_alignments || [];
        html += '<div class="pb-field"><label class="pb-field__label">' + (PB_I18N.column_alignment || 'Column alignment') + '</label>';
        html += '<div class="pb-table-col-aligns">';
        headers.forEach(function(h, hi) {
          var colVal = colAligns[hi] || '';
          html += '<select data-col-align="' + hi + '" class="pb-field__select pb-field__select--compact">';
          html += '<option value=""' + (colVal === '' ? ' selected' : '') + '>—</option>';
          html += '<option value="left"' + (colVal === 'left' ? ' selected' : '') + '>' + (PB_I18N.left || 'Left') + '</option>';
          html += '<option value="center"' + (colVal === 'center' ? ' selected' : '') + '>' + (PB_I18N.center || 'Center') + '</option>';
          html += '<option value="right"' + (colVal === 'right' ? ' selected' : '') + '>' + (PB_I18N.right || 'Right') + '</option>';
          html += '<option value="justify"' + (colVal === 'justify' ? ' selected' : '') + '>' + (PB_I18N.justify || 'Justify') + '</option>';
          html += '</select>';
        });
        html += '</div></div>';
        // Per-column width (percentage)
        var colWidths = d.column_widths || [];
        html += '<div class="pb-field"><label class="pb-field__label">' + (PB_I18N.column_width || 'Column width') + '</label>';
        html += '<div class="pb-table-col-aligns">';
        headers.forEach(function(h, hi) {
          var widthVal = colWidths[hi] || '';
          html += '<input type="text" data-col-width="' + hi + '" class="pb-field__input pb-field__select--compact" value="' + esc(widthVal) + '" placeholder="auto" style="text-align:center">';
        });
        html += '</div>';
        var widthSum = colWidths.reduce(function(s, w) { return s + (parseInt(w) || 0); }, 0);
        var sumClass = widthSum > 100 ? 'pb-text-hint pb-hint--pull-sm" style="color:var(--pb-danger)' : 'pb-text-hint pb-hint--pull-sm';
        html += '<p class="' + sumClass + '" id="pbColWidthSum">' + (widthSum > 0 ? (PB_I18N.column_width_hint || '%') + ' · ' + (PB_I18N.total || 'Total') + ': ' + widthSum + '%' + (widthSum > 100 ? ' ⚠' : '') : (PB_I18N.column_width_hint || '%')) + '</p>';
        html += '</div></div>';
        break;
      case 'section':
        html += fieldSelect((PB_I18N.bg_light_mode || 'Light mode background'), 'data.background_type', d.background_type || 'color', [
          {v: 'none', l: PB_I18N.bg_none}, {v: 'color', l: PB_I18N.bg_color}, {v: 'gradient', l: PB_I18N.bg_gradient}, {v: 'image', l: PB_I18N.bg_image}
        ]);
        var bgType = d.background_type || 'color';
        if (bgType === 'color') {
          var colorVal = d.background_value || '';
          html += '<div class="pb-field"><label class="pb-field__label">' + (PB_I18N.bg_light_color_pick || 'Pick light mode color') + '</label>' +
            '<div class="pb-panel-header pb-panel-header--flush">' +
            '<input type="color" data-key="data.background_value" value="' + esc(colorVal || '#ececec') + '" class="pb-color-swatch">' +
            '<input class="pb-field__input pb-flex-1" type="text" data-key="data.background_value" value="' + esc(colorVal) + '" placeholder="' + (PB_I18N.bg_theme_default || 'Theme default') + '">' +
            '</div></div>';
        } else if (bgType === 'gradient') {
          html += fieldGradient((PB_I18N.bg_light_gradient_value || 'Light mode gradient'), 'data.background_value', d.background_value || '');
        } else if (bgType === 'image') {
          html += fieldImageUrl((PB_I18N.bg_light_image_url || 'Light mode image URL'), 'data.background_value', d.background_value || '');
          html += '<p class="pb-text-hint pb-hint--pull-md">' + (PB_I18N.bg_image_hint || 'Recommended: 1920×1080px or wider. Image will cover full section.') + '</p>';
          html += fieldColorAlpha((PB_I18N.bg_light_overlay || 'Light mode overlay'), 'data.background_overlay', d.background_overlay || '', 'rgba(0,0,0,0.5)');
        }
        html += fieldSelect((PB_I18N.bg_dark_mode || 'Dark mode background'), 'data.background_type_dark', d.background_type_dark || 'color', [
          {v: 'none', l: PB_I18N.bg_none}, {v: 'color', l: PB_I18N.bg_color}, {v: 'gradient', l: PB_I18N.bg_gradient}, {v: 'image', l: PB_I18N.bg_image}
        ]);
        var bgTypeDark = d.background_type_dark || 'color';
        if (bgTypeDark === 'color') {
          var colorValDark = d.background_value_dark || '';
          html += '<div class="pb-field"><label class="pb-field__label">' + (PB_I18N.bg_dark_color_pick || 'Pick dark mode color') + '</label>' +
            '<div class="pb-panel-header pb-panel-header--flush">' +
            '<input type="color" data-key="data.background_value_dark" value="' + esc(colorValDark || '#1e293b') + '" class="pb-color-swatch">' +
            '<input class="pb-field__input pb-flex-1" type="text" data-key="data.background_value_dark" value="' + esc(colorValDark) + '" placeholder="' + (PB_I18N.bg_theme_default || 'Theme default') + '">' +
            '</div></div>';
        } else if (bgTypeDark === 'gradient') {
          html += fieldGradient((PB_I18N.bg_dark_gradient_value || 'Dark mode gradient'), 'data.background_value_dark', d.background_value_dark || '');
        } else if (bgTypeDark === 'image') {
          html += fieldImageUrl((PB_I18N.bg_dark_image_url || 'Dark mode image URL'), 'data.background_value_dark', d.background_value_dark || '');
          html += fieldColorAlpha((PB_I18N.bg_dark_overlay || 'Dark mode overlay'), 'data.background_overlay_dark', d.background_overlay_dark || '', 'rgba(0,0,0,0.5)');
        }
        html += '<div class="pb-panel-divider">';
        html += fieldSelect(PB_I18N.padding, 'data.padding', d.padding || 'lg', [
          {v: 'sm', l: PB_I18N.padding_sm + ' (32px)'}, {v: 'md', l: PB_I18N.padding_md + ' (56px)'}, {v: 'lg', l: PB_I18N.padding_lg + ' (96px)'}, {v: 'xl', l: PB_I18N.padding_xl + ' (140px)'}
        ]);
        html += fieldSelect((PB_I18N.text_color || 'Text color'), 'data.text_color', d.text_color || 'auto', [
          {v: 'auto', l: PB_I18N.text_color_auto || 'Auto (theme)'}, {v: 'light', l: PB_I18N.text_color_light || 'Light'}, {v: 'dark', l: PB_I18N.text_color_dark || 'Dark'}
        ]);
        html += fieldSelect((PB_I18N.min_height || 'Min height'), 'data.min_height', d.min_height || 'auto', [
          {v: 'auto', l: (PB_I18N.min_height_auto || 'Auto')}, {v: '300px', l: '300px'}, {v: '50vh', l: (PB_I18N.min_height_50_screen || '50% screen')}, {v: '75vh', l: (PB_I18N.min_height_75_screen || '75% screen')}, {v: '100vh', l: (PB_I18N.min_height_full_screen || 'Full screen')}
        ]);
        html += '</div>';
        html += '<div class="pb-panel-divider">';
        html += '<div class="pb-sidebar__title pb-mb-2">' + (PB_I18N.section_layout || 'Section Layout') + '</div>';
        html += '<label class="pb-toggle pb-mb-2"><input type="checkbox" data-key="data.full_width"' + (d.full_width ? ' checked' : '') + '> ' + (PB_I18N.section_full_width || 'Full width (edge-to-edge background)') + '</label>';
        html += fieldSelect((PB_I18N.section_content_width || 'Content width'), 'data.content_width', d.content_width || 'default', [
          {v: 'narrow', l: PB_I18N.section_width_narrow || 'Narrow (768px)'},
          {v: 'default', l: PB_I18N.section_width_default || 'Default (page width)'}
        ]);
        html += '<p class="pb-text-hint pb-hint--pull-section">' + (PB_I18N.section_full_width_hint || 'Full width stretches the background to the screen edges. Content width controls the inner area.') + '</p>';
        html += '</div>';
        break;
      case 'gallery':
        html += '<p class="pb-text-hint pb-mb-2 pb-panel-count">' + (d.images||[]).length + ' ' + PB_I18N.gallery_images + '</p>';
        // Visual image grid with delete buttons
        if ((d.images || []).length > 0) {
          html += '<div class="pb-gallery-grid pb-mb-2 pb-panel-grid-3">';
          (d.images || []).forEach(function(img, i) {
            html += '<div class="pb-video-wrap--sm">';
            if (img.url) {
              html += '<div class="pb-aspect-square"><img src="' + esc(img.url) + '" alt="' + esc(img.alt || '') + '" class="pb-cover-img"></div>';
            } else {
              html += '<div class="pb-aspect-square pb-panel-empty-cell">No URL</div>';
            }
            // Show alt from library
            if (img.alt) {
              html += '<div class="pb-thumb-label" title="' + esc(img.alt) + '">alt: ' + esc(img.alt) + '</div>';
            }
            html += '<button class="pb-gallery-remove pb-thumb-remove" data-gallery-remove="' + i + '" title="' + (PB_I18N.remove || 'Remove') + '">&times;</button>';
            html += '</div>';
          });
          html += '</div>';
        }
        // Add from library button
        html += '<button type="button" class="pb-gallery-pick-btn pb-dashed-add">📷 ' + (PB_I18N.gallery_add_from_library || 'Add from Library') + '</button>';
        html += fieldSelect(PB_I18N.gallery_columns, 'data.columns', d.columns || 3, [
          {v: 1, l: '1'}, {v: 2, l: '2'}, {v: 3, l: '3'}, {v: 4, l: '4'}
        ]);
        break;
      case 'video_gallery':
        html += '<p class="pb-text-hint pb-mb-2 pb-panel-count">' + (d.videos||[]).length + ' ' + (PB_I18N.video_gallery_videos || 'videos') + '</p>';
        if ((d.videos || []).length > 0) {
          html += '<div class="pb-panel-grid-2 pb-mb-2">';
          (d.videos || []).forEach(function(vid, i) {
            var thumbUrl = '';
            if (vid.embed_id && vid.source) {
              thumbUrl = videoThumbUrl(vid.source, vid.embed_id);
            }
            html += '<div class="pb-video-wrap--sm">';
            if (thumbUrl) {
              html += '<div class="pb-aspect-video"><img src="' + thumbUrl + '" alt="' + esc(vid.title || '') + '" class="pb-cover-img"></div>';
            } else {
              html += '<div class="pb-aspect-video pb-video-placeholder pb-video-placeholder--sm">' + (vid.source === 'vimeo' ? 'Vimeo' : '▶') + '</div>';
            }
            html += '<div class="pb-overlay-center--no-events"><div class="pb-play-btn pb-play-btn--xs">▶</div></div>';
            if (vid.title) {
              html += '<div class="pb-thumb-label" title="' + esc(vid.title) + '">' + esc(vid.title) + '</div>';
            }
            html += '<button class="pb-vgal-remove pb-thumb-remove" data-vgal-remove="' + i + '" title="' + (PB_I18N.remove || 'Remove') + '">&times;</button>';
            html += '</div>';
          });
          html += '</div>';
        }
        html += '<button type="button" class="pb-vgal-add-btn pb-dashed-add">+ ' + (PB_I18N.video_gallery_add || 'Add video') + '</button>';
        html += fieldSelect((PB_I18N.gallery_columns || 'Columns'), 'data.columns', d.columns || 2, [
          {v: 1, l: '1'}, {v: 2, l: '2'}, {v: 3, l: '3'}
        ]);
        break;
      case 'slideshow':
        // --- Mode selector ---
        html += fieldSelect((PB_I18N.slideshow_mode || 'Mode'), 'data.mode', d.mode || 'hero', [
          {v: 'hero', l: PB_I18N.slideshow_mode_hero || 'Hero (image + content)'},
          {v: 'image', l: PB_I18N.slideshow_mode_image || 'Image only'}
        ]);
        // --- Visual settings (matching section order) ---
        // Padding
        html += fieldSelect(PB_I18N.padding, 'data.padding', d.padding || 'lg', [
          {v: 'sm', l: PB_I18N.padding_sm + ' (32px)'}, {v: 'md', l: PB_I18N.padding_md + ' (56px)'}, {v: 'lg', l: PB_I18N.padding_lg + ' (96px)'}, {v: 'xl', l: PB_I18N.padding_xl + ' (140px)'}
        ]);
        // Text color
        html += fieldSelect((PB_I18N.text_color || 'Text color'), 'data.text_color', d.text_color || 'light', [
          {v: 'auto', l: PB_I18N.text_color_auto || 'Auto (theme)'}, {v: 'light', l: PB_I18N.text_color_light || 'Light'}, {v: 'dark', l: PB_I18N.text_color_dark || 'Dark'}
        ]);
        // Overlay
        html += fieldColorAlpha((PB_I18N.slideshow_overlay || 'Overlay'), 'data.overlay', d.overlay || '', 'rgba(0,0,0,0.4)');
        // --- Layout (matching section order) ---
        html += '<div class="pb-panel-divider">';
        html += '<div class="pb-sidebar__title pb-mb-2">' + (PB_I18N.section_layout || 'Layout') + '</div>';
        html += '<label class="pb-toggle pb-mb-2"><input type="checkbox" data-key="data.full_width"' + (d.full_width ? ' checked' : '') + '> ' + (PB_I18N.section_full_width || 'Full width (edge-to-edge)') + '</label>';
        html += fieldSelect((PB_I18N.section_content_width || 'Content width'), 'data.content_width', d.content_width || 'default', [
          {v: 'narrow', l: PB_I18N.section_width_narrow || 'Narrow (768px)'},
          {v: 'default', l: PB_I18N.section_width_default || 'Default (page width)'}
        ]);
        html += '</div>';
        // --- Slideshow-specific ---
        html += '<div class="pb-panel-divider">';
        html += '<div class="pb-sidebar__title pb-mb-2">' + (PB_I18N.slideshow_animation || 'Animation') + '</div>';
        html += fieldSelect((PB_I18N.slideshow_animation || 'Animation'), 'data.animation', d.animation || 'slide', [
          {v: 'slide', l: (PB_I18N.animation_slide || 'Slide')}, {v: 'fade', l: (PB_I18N.animation_fade || 'Fade')}
        ]);
        html += fieldSelect((PB_I18N.aspect_ratio || 'Aspect ratio'), 'data.aspect_ratio', d.aspect_ratio || '16/9', [
          {v: '16/9', l: '16:9'}, {v: '4/3', l: '4:3'}, {v: '21/9', l: (PB_I18N.ratio_cinematic || '21:9 (cinematic)')}, {v: '1/1', l: '1:1'}, {v: 'auto', l: (PB_I18N.ratio_auto || 'Auto (content height)')}
        ]);
        html += '<label class="pb-toggle pb-mb-2"><input type="checkbox" data-key="data.autoplay"' + (d.autoplay !== false ? ' checked' : '') + '> ' + (PB_I18N.slideshow_autoplay || 'Autoplay') + '</label>';
        html += field((PB_I18N.slideshow_interval || 'Interval (seconds)'), 'number', 'data.interval', d.interval || 5);
        html += '<label class="pb-toggle pb-mb-2"><input type="checkbox" data-key="data.show_arrows"' + (d.show_arrows !== false ? ' checked' : '') + '> ' + (PB_I18N.slideshow_arrows || 'Show arrows') + '</label>';
        html += '<label class="pb-toggle pb-mb-2"><input type="checkbox" data-key="data.show_dots"' + (d.show_dots !== false ? ' checked' : '') + '> ' + (PB_I18N.slideshow_dots || 'Show dots') + '</label>';
        html += '</div>';
        // --- Slides ---
        html += '<div class="pb-panel-divider">';
        html += '<div class="pb-sidebar__title pb-mb-2">' + (PB_I18N.slideshow_slides || 'Slides') + ' (' + (d.slides||[]).length + ')</div>';
        (d.slides || []).forEach(function(slide, i) {
          html += '<div class="pb-item-card">';
          html += '<div class="pb-text-hint--label">Slide ' + (i+1) + '</div>';
          html += fieldImageUrl((PB_I18N.image_url || 'Image'), 'data.slides.' + i + '.image', slide.image || '', '');
          html += '<input type="hidden" data-key="data.slides.' + i + '.alt" value="' + esc(slide.alt || '') + '">';
          if (slide.image) {
            html += '<div class="pb-slide-preview-wrap"><img src="' + esc(slide.image) + '" class="pb-preview-img--sm" alt=""></div>';
            if (slide.alt) {
              html += '<div class="pb-text-hint--sm pb-slide-alt-hint">alt: ' + esc(slide.alt) + '</div>';
            }
          }
          html += '<button class="pb-btn pb-btn--danger pb-btn--sm" data-remove-item="slideshow" data-index="' + i + '">' + (PB_I18N.slideshow_remove_slide || 'Remove Slide') + '</button>';
          html += '</div>';
        });
        html += '<button class="pb-btn pb-btn--outline pb-btn--sm pb-mt-1" data-add-item="slideshow">+ ' + (PB_I18N.slideshow_add_slide || 'Add slide') + '</button>';
        html += '</div>';
        break;
      case 'form':
        if (d.form_id && (d._form_title_raw || d._fields)) {
          var formCardTitle = d._form_title_raw || (PB_I18N.form || 'Form') + ' #' + d.form_id;
          var formCardMeta = d._form_title || '';
          html += '<div class="pb-field"><label class="pb-field__label">' + PB_I18N.form_select + '</label>';
          html += '<div class="pb-panel-form-card">';
          html += '<div class="pb-panel-form-card__info">';
          html += '<div class="pb-panel-form-card__title">' + esc(formCardTitle) + '</div>';
          html += '<div class="pb-panel-form-card__meta">' + esc(formCardMeta) + '</div>';
          html += '</div>';
          html += '<div class="pb-panel-form-card__actions">';
          html += '<button type="button" class="pb-browse-btn" id="pbFormChangeBtn" title="' + esc(PB_I18N.form_select || 'Change') + '">📋</button>';
          html += '<button type="button" class="pb-icon-btn" id="pbFormClearBtn" title="' + esc(PB_I18N.remove || 'Remove') + '">✕</button>';
          html += '</div></div></div>';
        } else {
          html += '<div class="pb-field"><label class="pb-field__label">' + PB_I18N.form_select + '</label>';
          html += '<button type="button" class="pb-btn pb-btn--outline pb-btn--sm pb-btn--full" id="pbFormPickBtn">📋 ' + esc(PB_I18N.form_select || 'Select form') + '</button>';
          html += '</div>';
        }
        html += '<label class="pb-toggle"><input type="checkbox" data-key="data.show_title"' + (d.show_title !== false ? ' checked' : '') + '> ' + PB_I18N.form_show_title + '</label>';
        html += '<label class="pb-toggle"><input type="checkbox" data-key="data.show_description"' + (d.show_description !== false ? ' checked' : '') + '> ' + PB_I18N.form_show_description + '</label>';
        break;
      case 'ticker':
        html += '<p class="pb-text-hint pb-mb-2 pb-panel-count">' + (d.images||[]).length + ' ' + PB_I18N.ticker_images + '</p>';
        if ((d.images || []).length > 0) {
          html += '<div class="pb-gallery-grid pb-mb-2 pb-panel-grid-3">';
          (d.images || []).forEach(function(img, i) {
            html += '<div class="pb-video-wrap--sm">';
            if (img.url) {
              html += '<div class="pb-aspect-square"><img src="' + esc(img.url) + '" alt="' + esc(img.alt || '') + '" class="pb-cover-img"></div>';
            }
            html += '<button class="pb-gallery-remove pb-thumb-remove" data-ticker-remove="' + i + '" title="' + (PB_I18N.remove || 'Remove') + '">&times;</button>';
            html += '</div>';
          });
          html += '</div>';
        }
        html += '<button type="button" class="pb-ticker-pick-btn pb-dashed-add">📷 ' + (PB_I18N.gallery_add_from_library || 'Add from Library') + '</button>';
        html += fieldSelect(PB_I18N.ticker_speed, 'data.speed', d.speed || 'medium', [
          {v: 'slow', l: PB_I18N.ticker_slow}, {v: 'medium', l: PB_I18N.ticker_medium}, {v: 'fast', l: PB_I18N.ticker_fast}
        ]);
        html += fieldSelect((PB_I18N.ticker_direction || 'Direction'), 'data.direction', d.direction || 'rtl', [
          {v: 'rtl', l: '← ' + (PB_I18N.ticker_dir_rtl || 'Right to left')}, {v: 'ltr', l: '→ ' + (PB_I18N.ticker_dir_ltr || 'Left to right')}
        ]);
        html += fieldSelect(PB_I18N.ticker_height, 'data.height', d.height || 'md', [
          {v: 'sm', l: 'S'}, {v: 'md', l: 'M'}, {v: 'lg', l: 'L'}, {v: 'xl', l: 'XL'}
        ]);
        html += '<label class="pb-toggle"><input type="checkbox" data-key="data.pause_on_hover"' + (d.pause_on_hover !== false ? ' checked' : '') + '> ' + PB_I18N.ticker_pause_hover + '</label>';
        html += '<label class="pb-toggle"><input type="checkbox" data-key="data.grayscale"' + (d.grayscale !== false ? ' checked' : '') + '> ' + PB_I18N.ticker_grayscale + '</label>';
        break;
    }

    // Common settings
    html += '<div class="pb-panel-divider--lg">';
    html += '<div class="pb-sidebar__title pb-mb-2">' + PB_I18N.layout + '</div>';
    // Block Position — available for all blocks
    html += fieldSelect((PB_I18N.block_position || 'Block Position'), 'settings.alignment', s.alignment || 'center', [
      {v: 'left', l: PB_I18N.left}, {v: 'center', l: PB_I18N.center}, {v: 'right', l: PB_I18N.right}
    ]);
    // Text Alignment — hide for blocks that have no text content
    var noTextAlign = ['slideshow', 'button', 'image', 'divider', 'spacer', 'video', 'gallery', 'video_gallery', 'ticker', 'table'];
    if (noTextAlign.indexOf(block.type) === -1) {
      html += fieldSelect((PB_I18N.content_alignment || 'Text Alignment'), 'settings.content_alignment', s.content_alignment || '', [
        {v: '', l: (PB_I18N.alignment_auto || '— Auto')}, {v: 'left', l: PB_I18N.left}, {v: 'center', l: PB_I18N.center}, {v: 'right', l: PB_I18N.right}, {v: 'justify', l: PB_I18N.justify}
      ]);
    }
    html += '<div class="pb-field__row">';
    html += fieldSelect(PB_I18N.top_margin, 'settings.margin_top', s.margin_top || 'none', [
      {v: 'none', l: PB_I18N.none}, {v: 'sm', l: 'S'}, {v: 'md', l: 'M'}, {v: 'lg', l: 'L'}, {v: 'xl', l: 'XL'}
    ]);
    html += fieldSelect(PB_I18N.bottom_margin, 'settings.margin_bottom', s.margin_bottom || 'none', [
      {v: 'none', l: PB_I18N.none}, {v: 'sm', l: 'S'}, {v: 'md', l: 'M'}, {v: 'lg', l: 'L'}, {v: 'xl', l: 'XL'}
    ]);
    html += '</div>';
    // Padding — not for section/slideshow (they have their own)
    var noPadding = ['section', 'slideshow'];
    if (noPadding.indexOf(block.type) === -1) {
      html += fieldSelect((PB_I18N.padding || 'Padding'), 'settings.padding', s.padding || 'none', [
        {v: 'none', l: PB_I18N.none}, {v: 'sm', l: 'S'}, {v: 'md', l: 'M'}, {v: 'lg', l: 'L'}, {v: 'xl', l: 'XL'}
      ]);
    }
    // Visibility — show on all devices, desktop only, or mobile only
    html += fieldSelect((PB_I18N.visibility || 'Visibility'), 'settings.visibility', s.visibility || '', [
      {v: '', l: (PB_I18N.visibility_all || 'All devices')}, {v: 'desktop', l: (PB_I18N.visibility_desktop || 'Desktop only')}, {v: 'mobile', l: (PB_I18N.visibility_mobile || 'Mobile only')}
    ]);
    // Scroll animation — not for slideshow (has its own animation)
    if (block.type !== 'slideshow') {
      html += fieldSelect((PB_I18N.scroll_animation || 'Scroll animation'), 'settings.animation', s.animation || 'none', [
        {v: 'none', l: PB_I18N.none}, {v: 'fade-in', l: (PB_I18N.animation_fade_in || 'Fade in')}, {v: 'slide-up', l: (PB_I18N.animation_slide_up || 'Slide up')}
      ]);
    }
    html += field(PB_I18N.css_class, 'input', 'settings.css_class', s.css_class || '');
    html += field(PB_I18N.anchor_id, 'input', 'settings.anchor_id', s.anchor_id || '');
    html += '</div>';

    return html;
  }

  // Field builders
  function field(label, type, key, value, placeholder) {
    if (type === 'number') {
      return '<div class="pb-field"><label class="pb-field__label">' + label + '</label>' +
        '<div class="pb-stepper">' +
        '<input class="pb-field__input" type="number" data-key="' + key + '" value="' + esc(value) + '"' +
        (placeholder ? ' placeholder="' + esc(placeholder) + '"' : '') + '>' +
        '<div class="pb-stepper__btns">' +
        '<button type="button" class="pb-stepper__btn" data-step-up="' + key + '">▲</button>' +
        '<button type="button" class="pb-stepper__btn" data-step-down="' + key + '">▼</button>' +
        '</div></div></div>';
    }
    return '<div class="pb-field"><label class="pb-field__label">' + label + '</label>' +
      '<input class="pb-field__input" type="' + (type||'text') + '" data-key="' + key + '" value="' + esc(value) + '"' +
      (placeholder ? ' placeholder="' + esc(placeholder) + '"' : '') + '></div>';
  }

  function fieldColorAlpha(label, key, value, placeholder) {
    // Parse rgba or hex value
    var hex = '#000000';
    var alpha = 50;
    if (value) {
      var rgbaMatch = value.match(/rgba?\(\s*(\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\s*\)/);
      if (rgbaMatch) {
        var r = parseInt(rgbaMatch[1]), g = parseInt(rgbaMatch[2]), b = parseInt(rgbaMatch[3]);
        alpha = rgbaMatch[4] !== undefined ? Math.round(parseFloat(rgbaMatch[4]) * 100) : 100;
        hex = '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
      } else if (value.startsWith('#')) {
        hex = value;
        alpha = 100;
      }
    }
    return '<div class="pb-field"><label class="pb-field__label">' + label + '</label>' +
      '<div class="pb-color-alpha">' +
      '<div class="pb-color-alpha__row">' +
      '<input type="color" class="pb-color-alpha__swatch" data-ca-color="' + key + '" value="' + hex + '">' +
      '<input type="range" class="pb-color-alpha__slider" data-ca-alpha="' + key + '" min="0" max="100" value="' + alpha + '">' +
      '<span class="pb-color-alpha__label" data-ca-label="' + key + '">' + alpha + '%</span>' +
      '</div>' +
      '<input class="pb-field__input pb-text-hint" type="text" data-key="' + key + '" value="' + esc(value) + '" placeholder="' + esc(placeholder || 'rgba(0,0,0,0.5)') + '">' +
      '</div></div>';
  }

  var GRADIENT_PRESETS = [
    'linear-gradient(135deg, #1a1a2e, #16213e)',
    'linear-gradient(135deg, #0f0c29, #302b63, #24243e)',
    'linear-gradient(135deg, #2d1b69, #11998e)',
    'linear-gradient(135deg, #fc5c7d, #6a82fb)',
    'linear-gradient(135deg, #f12711, #f5af19)',
    'linear-gradient(135deg, #00b09b, #96c93d)',
    'linear-gradient(135deg, #141e30, #243b55)',
    'linear-gradient(135deg, #ff9a9e, #fecfef)',
    'linear-gradient(135deg, #a18cd1, #fbc2eb)',
    'linear-gradient(135deg, #667eea, #764ba2)'
  ];

  function fieldGradient(label, key, value) {
    var html = '<div class="pb-field"><label class="pb-field__label">' + label + '</label>';
    html += '<div class="pb-gradient-presets">';
    GRADIENT_PRESETS.forEach(function(g) {
      var active = value === g ? ' is-active' : '';
      html += '<button type="button" class="pb-gradient-preset' + active + '" data-gradient-pick="' + key + '" data-gradient-val="' + esc(g) + '" style="background:' + g + '"></button>';
    });
    html += '</div>';
    html += '<input class="pb-field__input pb-text-hint" type="text" data-key="' + key + '" value="' + esc(value) + '" placeholder="linear-gradient(135deg, #color1, #color2)">';
    html += '</div>';
    return html;
  }

  function fieldSelect(label, key, value, options) {
    var opts = options.map(function(o) {
      return '<option value="' + o.v + '"' + (String(o.v) === String(value) ? ' selected' : '') + '>' + o.l + '</option>';
    }).join('');
    return '<div class="pb-field"><label class="pb-field__label">' + label + '</label>' +
      '<select class="pb-field__select" data-key="' + key + '">' + opts + '</select></div>';
  }

  function fieldTextarea(label, key, value) {
    return '<div class="pb-field"><label class="pb-field__label">' + label + '</label>' +
      '<textarea class="pb-field__textarea" data-key="' + key + '">' + esc(value) + '</textarea></div>';
  }

  function fieldImageUrl(label, key, value, placeholder) {
    var hasValue = value && value.trim();
    return '<div class="pb-field"><label class="pb-field__label">' + label + '</label>' +
      '<div class="pb-field-image-row">' +
      '<input class="pb-field__input pb-flex-1 pb-input--sm" type="text" data-key="' + key + '" value="' + esc(value) + '"' +
      ' readonly style="cursor:default;opacity:' + (hasValue ? '1' : '0.5') + '"' +
      ' placeholder="' + esc(PB_I18N.choose_image || 'Select from library') + '">' +
      '<button type="button" class="pb-pick-image-btn pb-browse-btn" data-target-key="' + key + '" title="' + (PB_I18N.choose_image || 'Choose') + '">📷</button>' +
      (hasValue ? '<button type="button" class="pb-clear-image-btn pb-icon-btn" data-clear-key="' + key + '" title="' + (PB_I18N.remove || 'Remove') + '">✕</button>' : '') +
      '</div></div>';
  }

  // Image picker modal for Page Builder
  function pbPickImage(targetKey) {
    var modal = document.createElement('div');
    modal.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.7);z-index:10000;display:flex;align-items:center;justify-content:center';

    var box = document.createElement('div');
    box.style.cssText = 'background:var(--pb-surface);border-radius:12px;padding:24px;max-width:900px;max-height:80vh;overflow-y:auto;width:90%;color:var(--pb-text)';
    box.innerHTML =
      '<div class="pb-panel-header">' +
      '<h3 class="pb-heading-md">' + (PB_I18N.choose_image || 'Select Image') + '</h3>' +
      '<button onclick="this.closest(\'[style*=inset]\').remove()" class="pb-modal-close">&times;</button>' +
      '</div>' +
      '<div id="pbImageGallery_' + Date.now() + '" class="pb-gallery-grid">' +
      '<div class="pb-empty-msg">Loading...</div>' +
      '</div>';

    modal.appendChild(box);
    document.body.appendChild(modal);
    modal.addEventListener('click', function(e) { if (e.target === modal) modal.remove(); });

    var gallery = box.querySelector('[id^="pbImageGallery"]');
    fetch('/admin/media/api/list')
      .then(function(r) { return r.json(); })
      .then(function(images) {
        if (!images.length) {
          gallery.innerHTML = '<div class="pb-empty-msg">' +
            '<p>{{ t("media.no_images_picker") }}</p><a href="/admin/media/upload" target="_blank" class="pb-color-primary">{{ t("media.upload_first_link") }}</a></div>';
          return;
        }
        gallery.innerHTML = images.map(function(img) {
          return '<div class="pb-media-card" ' +
            'onmouseenter="this.style.borderColor=\'var(--pb-primary)\'" onmouseleave="this.style.borderColor=\'var(--pb-border)\'" ' +
            'data-pick-url="' + esc(img.url) + '" data-pick-alt="' + esc(img.alt_text || '') + '">' +
            '<img src="' + esc(img.url) + '" alt="' + esc(img.alt_text || '') + '" class="pb-cover-img--constrained">' +
            '<div class="pb-vg-title">' + esc(img.alt_text || img.filename || '') + '</div>' +
            '</div>';
        }).join('');

        gallery.querySelectorAll('[data-pick-url]').forEach(function(card) {
          card.addEventListener('click', function() {
            var url = card.dataset.pickUrl;
            var alt = card.dataset.pickAlt;
            // Set value in the panel input
            var input = $panelBody.querySelector('[data-key="' + targetKey + '"]');
            if (input) {
              input.value = url;
              input.dispatchEvent(new Event('input', { bubbles: true }));
            }
            // Also set alt if there's an alt field
            // Works for: data.alt (image block), data.slides.N.alt (slideshow)
            var altKey = targetKey.replace(/\.image$/, '.alt').replace(/\.url$/, '.alt');
            if (altKey === targetKey) altKey = 'data.alt'; // fallback for image block
            var altInput = $panelBody.querySelector('[data-key="' + altKey + '"]');
            if (altInput && alt) {
              altInput.value = alt;
              altInput.dispatchEvent(new Event('input', { bubbles: true }));
            }
            modal.remove();
            // Re-open panel to refresh preview and alt badge
            if (state.selectedId) {
              var selBlock = findBlockDeep(state.selectedId);
              if (selBlock) setTimeout(function() { openPanel(selBlock); }, 50);
            }
          });
        });
      })
      .catch(function(err) {
        gallery.innerHTML = '<div class="pb-empty-msg">Error loading images.</div>';
      });
  }

  // Gallery multi-select image picker
  function pbPickGalleryImages(block) {
    var selected = {};
    var modal = document.createElement('div');
    modal.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.7);z-index:10000;display:flex;align-items:center;justify-content:center';

    var box = document.createElement('div');
    box.style.cssText = 'background:var(--pb-surface);border-radius:12px;padding:24px;max-width:900px;max-height:80vh;overflow-y:auto;width:90%;color:var(--pb-text)';
    box.innerHTML =
      '<div class="pb-panel-header">' +
      '<h3 class="pb-heading-md">' + (PB_I18N.gallery_pick_title || 'Select Images') + ' <span id="pbGalPickCount" class="pb-text-hint pb-gal-pick-count">(0 selected)</span></h3>' +
      '<div class="pb-panel-header pb-panel-header--flush">' +
      '<button id="pbGalPickDone" class="pb-submit-primary">' + (PB_I18N.gallery_add_selected || 'Add selected') + '</button>' +
      '<button onclick="this.closest(\'[style*=inset]\').remove()" class="pb-modal-close">&times;</button>' +
      '</div></div>' +
      '<div id="pbGalGrid" class="pb-gallery-grid">' +
      '<div class="pb-empty-msg">Loading...</div>' +
      '</div>';

    modal.appendChild(box);
    document.body.appendChild(modal);
    modal.addEventListener('click', function(e) { if (e.target === modal) modal.remove(); });

    var countEl = box.querySelector('#pbGalPickCount');
    var doneBtn = box.querySelector('#pbGalPickDone');
    var grid = box.querySelector('#pbGalGrid');

    function updateCount() {
      var n = Object.keys(selected).length;
      countEl.textContent = '(' + n + ' selected)';
    }

    fetch('/admin/media/api/list')
      .then(function(r) { return r.json(); })
      .then(function(images) {
        if (!images.length) {
          grid.innerHTML = '<div class="pb-empty-msg">' +
            '<p>{{ t("media.no_images_picker") }}</p><a href="/admin/media/upload" target="_blank" class="pb-color-primary">{{ t("media.upload_first_link") }}</a></div>';
          return;
        }
        grid.innerHTML = images.map(function(img) {
          return '<div data-gal-url="' + esc(img.url) + '" data-gal-alt="' + esc(img.alt_text || '') + '" ' +
            'class="pb-gal-pick-card">' +
            '<img src="' + esc(img.url) + '" alt="' + esc(img.alt_text || '') + '" class="pb-cover-img--constrained">' +
            '<div class="pb-vg-title">' + esc(img.alt_text || '') + '</div>' +
            '<div class="pb-gal-check pb-media-check">✓</div>' +
            '</div>';
        }).join('');

        grid.querySelectorAll('[data-gal-url]').forEach(function(card) {
          card.addEventListener('click', function() {
            var url = card.dataset.galUrl;
            if (selected[url]) {
              delete selected[url];
              card.style.borderColor = 'var(--pb-border)';
              card.querySelector('.pb-gal-check').style.display = 'none';
            } else {
              selected[url] = { url: url, alt: card.dataset.galAlt || '', caption: '' };
              card.style.borderColor = 'var(--pb-primary)';
              card.querySelector('.pb-gal-check').style.display = 'block';
            }
            updateCount();
          });
        });
      })
      .catch(function() {
        grid.innerHTML = '<div class="pb-empty-msg">Error loading images.</div>';
      });

    doneBtn.addEventListener('click', function() {
      var newImages = Object.values(selected);
      if (!newImages.length) { modal.remove(); return; }
      // Append to existing images
      block.data.images = (block.data.images || []).concat(newImages);
      markDirty();
      openPanel(block);
      renderCanvas();
      modal.remove();
    });
  }

  // Video Gallery: add video via URL input modal with auto-title fetch
  function pbAddVideoToGallery(block) {
    var modal = document.createElement('div');
    modal.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.7);z-index:10000;display:flex;align-items:center;justify-content:center';

    var box = document.createElement('div');
    box.style.cssText = 'background:var(--pb-surface);border-radius:12px;padding:24px;max-width:500px;width:90%;color:var(--pb-text)';
    box.innerHTML =
      '<div class="pb-panel-header">' +
      '<h3 class="pb-heading-md">' + (PB_I18N.video_gallery_add_title || 'Add Video') + '</h3>' +
      '<button id="pbVgalClose" class="pb-modal-close">&times;</button>' +
      '</div>' +
      '<div class="pb-mb-3">' +
      '<input id="pbVgalUrl" type="text" placeholder="' + esc(PB_I18N.video_url || 'YouTube or Vimeo URL') + '" class="pb-panel-input">' +
      '<p class="pb-text-hint pb-mt-1">' + esc(PB_I18N.video_url_hint || 'e.g. https://youtube.com/watch?v=...') + '</p>' +
      '</div>' +
      '<div class="pb-mb-3">' +
      '<input id="pbVgalTitle" type="text" placeholder="' + esc(PB_I18N.video_title || 'Title (accessibility)') + '" class="pb-panel-input">' +
      '<p id="pbVgalTitleHint" class="pb-text-hint pb-mt-1 pb-d-none"></p>' +
      '</div>' +
      '<div id="pbVgalPreview" class="pb-d-none pb-mb-4 pb-video-wrap pb-aspect-video">' +
      '<img id="pbVgalThumb" class="pb-cover-img">' +
      '<div class="pb-overlay-center"><div class="pb-play-btn pb-play-btn--md">▶</div></div>' +
      '</div>' +
      '<button id="pbVgalAdd" disabled class="pb-submit-disabled">' + (PB_I18N.video_gallery_add_btn || 'Add') + '</button>';

    modal.appendChild(box);
    document.body.appendChild(modal);
    modal.addEventListener('click', function(e) { if (e.target === modal) modal.remove(); });
    box.querySelector('#pbVgalClose').addEventListener('click', function() { modal.remove(); });

    var urlInput = box.querySelector('#pbVgalUrl');
    var titleInput = box.querySelector('#pbVgalTitle');
    var titleHint = box.querySelector('#pbVgalTitleHint');
    var preview = box.querySelector('#pbVgalPreview');
    var thumb = box.querySelector('#pbVgalThumb');
    var addBtn = box.querySelector('#pbVgalAdd');
    var fetchTimer = null;
    var parsedVideo = null;

    function parseVideoUrl(val) {
      var yt = val.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/)([^&?\s]+)/);
      var vm = val.match(/vimeo\.com\/(\d+)/);
      if (yt) return { embed_id: yt[1], source: 'youtube' };
      if (vm) return { embed_id: vm[1], source: 'vimeo' };
      return null;
    }

    urlInput.addEventListener('input', function() {
      var val = urlInput.value.trim();
      parsedVideo = parseVideoUrl(val);

      if (parsedVideo) {
        addBtn.disabled = false;
        addBtn.style.opacity = '1';

        thumb.src = videoThumbUrl(parsedVideo.source, parsedVideo.embed_id);
        preview.style.display = 'block';

        // Auto-fetch title via our own server (no third-party requests)
        clearTimeout(fetchTimer);
        fetchTimer = setTimeout(function() {
          var metaUrl = '/api/video-meta?source=' + encodeURIComponent(parsedVideo.source)
                      + '&id=' + encodeURIComponent(parsedVideo.embed_id);
          fetch(metaUrl)
            .then(function(r) { return r.json(); })
            .then(function(data) {
              if (data && data.title && !titleInput.value.trim()) {
                titleInput.value = data.title;
                titleHint.textContent = '✓ ' + (PB_I18N.video_gallery_title_fetched || 'Title fetched automatically');
                titleHint.style.display = 'block';
              } else if (data && data.error) {
                titleHint.textContent = PB_I18N.video_meta_fetch_failed || 'Couldn\'t fetch video info';
                titleHint.style.display = 'block';
              }
            })
            .catch(function() {
              titleHint.textContent = PB_I18N.video_meta_fetch_failed || 'Couldn\'t fetch video info';
              titleHint.style.display = 'block';
            });
        }, 400);
      } else {
        addBtn.disabled = true;
        addBtn.style.opacity = '0.5';
        preview.style.display = 'none';
        titleHint.style.display = 'none';
      }
    });

    addBtn.addEventListener('click', function() {
      if (!parsedVideo) return;
      var video = {
        url: urlInput.value.trim(),
        embed_id: parsedVideo.embed_id,
        source: parsedVideo.source,
        title: titleInput.value.trim()
      };
      block.data.videos = (block.data.videos || []).concat([video]);
      markDirty();
      openPanel(block);
      renderCanvas();
      modal.remove();
    });

    setTimeout(function() { urlInput.focus(); }, 100);
  }

  // Single Video: pick video via URL input modal (same UX as video gallery add)
  function pbPickSingleVideo(block) {
    var modal = document.createElement('div');
    modal.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.7);z-index:10000;display:flex;align-items:center;justify-content:center';

    var box = document.createElement('div');
    box.style.cssText = 'background:var(--pb-surface);border-radius:12px;padding:24px;max-width:500px;width:90%;color:var(--pb-text)';
    box.innerHTML =
      '<div class="pb-panel-header">' +
      '<h3 class="pb-heading-md">' + (PB_I18N.video_gallery_add_title || 'Add Video') + '</h3>' +
      '<button id="pbSvClose" class="pb-modal-close">&times;</button>' +
      '</div>' +
      '<div class="pb-mb-3">' +
      '<input id="pbSvUrl" type="text" placeholder="' + esc(PB_I18N.video_url || 'YouTube or Vimeo URL') + '" class="pb-panel-input">' +
      '<p class="pb-text-hint pb-mt-1">' + esc(PB_I18N.video_url_hint || 'e.g. https://youtube.com/watch?v=...') + '</p>' +
      '</div>' +
      '<div class="pb-mb-3">' +
      '<input id="pbSvTitle" type="text" placeholder="' + esc(PB_I18N.video_title || 'Title (accessibility)') + '" class="pb-panel-input">' +
      '<p id="pbSvTitleHint" class="pb-text-hint pb-mt-1 pb-d-none"></p>' +
      '</div>' +
      '<div id="pbSvPreview" class="pb-d-none pb-mb-4 pb-video-wrap pb-aspect-video">' +
      '<img id="pbSvThumb" class="pb-cover-img">' +
      '<div class="pb-overlay-center"><div class="pb-play-btn pb-play-btn--md">▶</div></div>' +
      '</div>' +
      '<button id="pbSvAdd" disabled class="pb-submit-disabled">' + (PB_I18N.video_gallery_add_btn || 'Add') + '</button>';

    modal.appendChild(box);
    document.body.appendChild(modal);
    modal.addEventListener('click', function(e) { if (e.target === modal) modal.remove(); });
    box.querySelector('#pbSvClose').addEventListener('click', function() { modal.remove(); });

    var urlInput = box.querySelector('#pbSvUrl');
    var titleInput = box.querySelector('#pbSvTitle');
    var titleHint = box.querySelector('#pbSvTitleHint');
    var preview = box.querySelector('#pbSvPreview');
    var thumb = box.querySelector('#pbSvThumb');
    var addBtn = box.querySelector('#pbSvAdd');
    var fetchTimer = null;
    var parsedVideo = null;

    function parseVideoUrl(val) {
      var yt = val.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/)([^&?\s]+)/);
      var vm = val.match(/vimeo\.com\/(\d+)/);
      if (yt) return { embed_id: yt[1], source: 'youtube' };
      if (vm) return { embed_id: vm[1], source: 'vimeo' };
      return null;
    }

    urlInput.addEventListener('input', function() {
      var val = urlInput.value.trim();
      parsedVideo = parseVideoUrl(val);
      if (parsedVideo) {
        addBtn.disabled = false;
        addBtn.style.opacity = '1';
        thumb.src = videoThumbUrl(parsedVideo.source, parsedVideo.embed_id);
        preview.style.display = 'block';
        clearTimeout(fetchTimer);
        fetchTimer = setTimeout(function() {
          var metaUrl = '/api/video-meta?source=' + encodeURIComponent(parsedVideo.source)
                      + '&id=' + encodeURIComponent(parsedVideo.embed_id);
          fetch(metaUrl)
            .then(function(r) { return r.json(); })
            .then(function(data) {
              if (data && data.title && !titleInput.value.trim()) {
                titleInput.value = data.title;
                titleHint.textContent = '✓ ' + (PB_I18N.video_gallery_title_fetched || 'Title fetched automatically');
                titleHint.style.display = 'block';
              } else if (data && data.error) {
                titleHint.textContent = PB_I18N.video_meta_fetch_failed || 'Couldn\'t fetch video info';
                titleHint.style.display = 'block';
              }
            })
            .catch(function() {
              titleHint.textContent = PB_I18N.video_meta_fetch_failed || 'Couldn\'t fetch video info';
              titleHint.style.display = 'block';
            });
        }, 400);
      } else {
        addBtn.disabled = true;
        addBtn.style.opacity = '0.5';
        preview.style.display = 'none';
        titleHint.style.display = 'none';
      }
    });

    addBtn.addEventListener('click', function() {
      if (!parsedVideo) return;
      block.data.url = urlInput.value.trim();
      block.data.embed_id = parsedVideo.embed_id;
      block.data.source = parsedVideo.source;
      block.data.title = titleInput.value.trim();
      markDirty();
      openPanel(block);
      renderCanvas();
      modal.remove();
    });

    setTimeout(function() { urlInput.focus(); }, 100);
  }

  // ============================================================
  // FORM PICKER MODAL — select form from canvas
  // ============================================================
  function pbPickForm(block) {
    var _fpState = { forms: [], map: {}, lang: '' };
    var modal = document.createElement('div');
    modal.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.7);z-index:10000;display:flex;align-items:center;justify-content:center';

    var box = document.createElement('div');
    box.style.cssText = 'background:var(--pb-surface);border-radius:12px;padding:24px;max-width:560px;max-height:80vh;overflow-y:auto;width:90%;color:var(--pb-text)';

    var searchId = 'pbFS_' + Date.now();
    box.innerHTML =
      '<div class="pb-panel-header">' +
      '<h3 class="pb-heading-md">📋 ' + esc(PB_I18N.form_select || 'Select form') + '</h3>' +
      '<button class="pb-modal-close" data-fp-close>&times;</button>' +
      '</div>' +
      '<input type="text" id="' + searchId + '" class="pb-field__input pb-mb-2" placeholder="' + esc(PB_I18N.form_search_placeholder || 'Search forms...') + '">' +
      '<div class="pb-form-lang-tabs" data-fp-langs></div>' +
      '<div data-fp-list><div class="pb-empty-msg">' + esc(PB_I18N.loading || 'Loading...') + '</div></div>';

    modal.appendChild(box);
    document.body.appendChild(modal);
    modal.addEventListener('click', function(e) { if (e.target === modal) modal.remove(); });
    box.querySelector('[data-fp-close]').addEventListener('click', function() { modal.remove(); });

    var listEl = box.querySelector('[data-fp-list]');
    var searchEl = document.getElementById(searchId);
    var langTabsEl = box.querySelector('[data-fp-langs]');

    function selectForm(fId) {
      var f = _fpState.map[fId];
      if (!f) return;
      block.data.form_id = fId;
      block.data._form_title = f.title + ' (' + f.form_type + ', ' + f.fields_count + ' fields)';
      block.data._form_title_raw = f.title;
      block.data._fields = f.fields;
      block.data._settings = f.settings;
      block.data._description = f.description || '';
      markDirty();
      renderCanvas();
      openPanel(block);
      modal.remove();
    }

    function renderCards() {
      var query = searchEl ? (searchEl.value || '').toLowerCase().trim() : '';
      var lang = _fpState.lang;
      var filtered = _fpState.forms.filter(function(f) {
        if (lang && f.language !== lang) return false;
        if (query && f.title.toLowerCase().indexOf(query) === -1) return false;
        return true;
      });

      if (filtered.length === 0) {
        listEl.className = '';
        listEl.innerHTML = '<div class="pb-empty-msg"><p>' + esc(PB_I18N.form_no_forms || 'No forms found.') + '</p></div>';
        return;
      }

      listEl.className = 'pb-form-pick-list';
      listEl.innerHTML = filtered.map(function(f) {
        var isSelected = String(f.id) === String(block.data.form_id);
        var langBadge = f.language ? '<span class="pb-form-pick-card__lang">' + esc(f.language.toUpperCase()) + '</span>' : '';
        return '<div class="pb-form-pick-card' + (isSelected ? ' is-selected' : '') + '" data-fp-id="' + f.id + '">' +
          '<svg class="pb-form-pick-card__icon" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M7 7h10"/><path d="M7 12h10"/><path d="M7 17h4"/></svg>' +
          '<div class="pb-form-pick-card__info">' +
          '<div class="pb-form-pick-card__title">' + esc(f.title) + '</div>' +
          '<div class="pb-form-pick-card__meta">' + esc(f.form_type) + ' · ' + f.fields_count + ' ' + esc(PB_I18N.form_fields || 'fields') + '</div>' +
          '</div>' +
          langBadge +
          (isSelected ? '<span class="pb-form-pick-card__check">✓</span>' : '') +
          '</div>';
      }).join('');

      listEl.querySelectorAll('[data-fp-id]').forEach(function(card) {
        card.addEventListener('click', function() {
          selectForm(parseInt(card.dataset.fpId));
        });
      });
    }

    if (searchEl) {
      searchEl.addEventListener('input', renderCards);
      searchEl.addEventListener('keyup', renderCards);
    }

    fetch('/api/forms/list')
      .then(function(r) { return r.json(); })
      .then(function(data) {
        if (!data.forms || data.forms.length === 0) {
          langTabsEl.style.display = 'none';
          if (searchEl) searchEl.style.display = 'none';
          listEl.innerHTML = '<div class="pb-empty-msg">' +
            '<p>' + esc(PB_I18N.form_no_forms || 'No forms yet.') + '</p>' +
            '<a href="/admin/forms/new" target="_blank" class="pb-color-primary">' + esc(PB_I18N.form_new || 'Create a form') + '</a></div>';
          return;
        }

        _fpState.forms = data.forms;
        data.forms.forEach(function(f) { _fpState.map[f.id] = f; });

        var langSet = {};
        data.forms.forEach(function(f) { if (f.language) langSet[f.language] = true; });
        var languages = Object.keys(langSet).sort();

        if (languages.length > 1) {
          var tabsHtml = '<button class="pb-form-lang-tab is-active" data-fp-lang="">' + esc(PB_I18N.form_all_languages || 'All') + '</button>';
          languages.forEach(function(lang) {
            tabsHtml += '<button class="pb-form-lang-tab" data-fp-lang="' + esc(lang) + '">' + esc(lang.toUpperCase()) + '</button>';
          });
          langTabsEl.innerHTML = tabsHtml;
          langTabsEl.querySelectorAll('[data-fp-lang]').forEach(function(tab) {
            tab.addEventListener('click', function() {
              langTabsEl.querySelectorAll('[data-fp-lang]').forEach(function(t) { t.classList.remove('is-active'); });
              tab.classList.add('is-active');
              _fpState.lang = tab.dataset.fpLang;
              renderCards();
            });
          });
        } else {
          langTabsEl.style.display = 'none';
        }

        renderCards();
        if (searchEl) searchEl.focus();
      })
      .catch(function() {
        listEl.innerHTML = '<div class="pb-empty-msg">' + esc(PB_I18N.error_loading_forms || 'Error loading forms') + '</div>';
      });
  }

  // ============================================================
  // BIND PANEL EVENTS — update block data on input change
  // ============================================================
  function bindPanelEvents(block) {
    $panelBody.querySelectorAll('[data-key]').forEach(function(el) {
      var eventType = (el.tagName === 'SELECT' || el.type === 'checkbox') ? 'change' : 'input';
      el.addEventListener(eventType, function() {
        var key = el.dataset.key;
        var val = el.type === 'checkbox' ? el.checked : el.value;

        // Parse key path: "data.text" or "settings.alignment" or "data.items.0.title"
        var parts = key.split('.');
        var target = block;
        for (var i = 0; i < parts.length - 1; i++) {
          if (target[parts[i]] === undefined) target[parts[i]] = {};
          target = target[parts[i]];
        }

        var lastKey = parts[parts.length - 1];

        // Special parsing for some fields
        if (key === 'data.images_text' && block.type === 'gallery') {
          block.data.images = val.split('\n').filter(function(s) { return s.trim(); }).map(function(line) {
            var p = line.split('|');
            return { url: (p[0]||'').trim(), alt: (p[1]||'').trim(), caption: (p[2]||'').trim() };
          });
        } else if (key === 'data.url' && block.type === 'video') {
          target[lastKey] = val;
          // Auto-extract YouTube/Vimeo ID
          var yt = val.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/)([^&?\s]+)/);
          var vm = val.match(/vimeo\.com\/(\d+)/);
          if (yt) { block.data.embed_id = yt[1]; block.data.source = 'youtube'; }
          else if (vm) { block.data.embed_id = vm[1]; block.data.source = 'vimeo'; }
        } else if (key === 'data.level') {
          target[lastKey] = parseInt(val);
        } else if (key === 'data.columns' && (block.type === 'gallery' || block.type === 'video_gallery')) {
          target[lastKey] = parseInt(val);
        } else if (key === 'data.interval' && block.type === 'slideshow') {
          target[lastKey] = parseInt(val) || 5;
        } else {
          target[lastKey] = val;
        }

        // Handle columns layout change
        if (key === 'data.layout' && block.type === 'columns') {
          var colCount = { '1': 1, '1/2+1/2': 2, '1/3+1/3+1/3': 3, '2/3+1/3': 2, '1/3+2/3': 2, '1/4+1/4+1/4+1/4': 4 };
          var needed = colCount[val] || 2;
          while (block.data.columns.length < needed) block.data.columns.push({ blocks: [] });
          if (block.data.columns.length > needed) block.data.columns.length = needed;
        }

        // Re-render panel when section background type changes
        if (key === 'data.background_type' && block.type === 'section') {
          // Clear old value when switching types for clean UX
          block.data.background_value = '';
          if (val === 'none') block.data.background_overlay = '';
          openPanel(block);
        }
        if (key === 'data.background_type_dark' && block.type === 'section') {
          block.data.background_value_dark = '';
          if (val === 'none') block.data.background_overlay_dark = '';
          openPanel(block);
        }

        // Sync sidebar text alignment to canvas paragraphs for rich text blocks
        if (key === 'settings.content_alignment' && block.type === 'text') {
          var blockEl = $blocks.querySelector('[data-id="' + block.id + '"] [data-inline-html]');
          if (blockEl) {
            var paragraphs = blockEl.querySelectorAll('p, li, h1, h2, h3, h4, h5, h6');
            paragraphs.forEach(function(p) {
              if (val) {
                p.style.textAlign = val;
              } else {
                p.style.removeProperty('text-align');
              }
            });
            // Update model with new innerHTML
            block.data.html = blockEl.innerHTML;
          }
        }

        _isInlineEditing = false;
        markDirty();
        renderCanvas();
      });
    });

    // Sync color picker <-> text input (both share data-key="data.background_value")
    var bgInputs = $panelBody.querySelectorAll('[data-key="data.background_value"]');
    if (bgInputs.length === 2) {
      bgInputs[0].addEventListener('input', function() { bgInputs[1].value = bgInputs[0].value; });
      bgInputs[1].addEventListener('input', function() { bgInputs[0].value = bgInputs[1].value; });
    }
    var bgInputsDark = $panelBody.querySelectorAll('[data-key="data.background_value_dark"]');
    if (bgInputsDark.length === 2) {
      bgInputsDark[0].addEventListener('input', function() { bgInputsDark[1].value = bgInputsDark[0].value; });
      bgInputsDark[1].addEventListener('input', function() { bgInputsDark[0].value = bgInputsDark[1].value; });
    }

    // Image picker buttons
    $panelBody.querySelectorAll('.pb-pick-image-btn').forEach(function(btn) {
      btn.addEventListener('click', function() {
        pbPickImage(btn.dataset.targetKey);
      });
    });

    // Clear image button (✕) — removes URL and alt, re-renders panel
    $panelBody.querySelectorAll('.pb-clear-image-btn').forEach(function(btn) {
      btn.addEventListener('click', function() {
        var key = btn.dataset.clearKey;
        var input = $panelBody.querySelector('[data-key="' + key + '"]');
        if (input) {
          input.value = '';
          input.dispatchEvent(new Event('input', { bubbles: true }));
        }
        // Clear associated alt
        var altKey = key.replace(/\.image$/, '.alt').replace(/\.url$/, '.alt');
        if (altKey === key) altKey = 'data.alt';
        var altInput = $panelBody.querySelector('[data-key="' + altKey + '"]');
        if (altInput) {
          altInput.value = '';
          altInput.dispatchEvent(new Event('input', { bubbles: true }));
        }
        markDirty();
        openPanel(block);
        renderCanvas();
      });
    });

    // Gallery: add images from library
    var galleryPickBtn = $panelBody.querySelector('.pb-gallery-pick-btn');
    if (galleryPickBtn) {
      galleryPickBtn.addEventListener('click', function() {
        pbPickGalleryImages(block);
      });
    }

    // Gallery: remove individual image
    $panelBody.querySelectorAll('.pb-gallery-remove').forEach(function(btn) {
      btn.addEventListener('click', function(e) {
        e.stopPropagation();
        var idx = parseInt(btn.dataset.galleryRemove);
        if (block.data.images && idx >= 0 && idx < block.data.images.length) {
          block.data.images.splice(idx, 1);
          markDirty();
          openPanel(block);
          renderCanvas();
        }
      });
    });

    // Video Gallery: add video
    var vgalAddBtn = $panelBody.querySelector('.pb-vgal-add-btn');
    if (vgalAddBtn) {
      vgalAddBtn.addEventListener('click', function() {
        pbAddVideoToGallery(block);
      });
    }

    // Ticker: add images from library (reuses gallery picker)
    var tickerPickBtn = $panelBody.querySelector('.pb-ticker-pick-btn');
    if (tickerPickBtn) {
      tickerPickBtn.addEventListener('click', function() {
        pbPickGalleryImages(block);
      });
    }

    // Ticker: remove individual image
    $panelBody.querySelectorAll('[data-ticker-remove]').forEach(function(btn) {
      btn.addEventListener('click', function(e) {
        e.stopPropagation();
        var idx = parseInt(btn.dataset.tickerRemove);
        if (block.data.images && idx >= 0 && idx < block.data.images.length) {
          block.data.images.splice(idx, 1);
          markDirty();
          openPanel(block);
          renderCanvas();
        }
      });
    });

    // Video Gallery: remove individual video
    $panelBody.querySelectorAll('.pb-vgal-remove').forEach(function(btn) {
      btn.addEventListener('click', function(e) {
        e.stopPropagation();
        var idx = parseInt(btn.dataset.vgalRemove);
        if (block.data.videos && idx >= 0 && idx < block.data.videos.length) {
          block.data.videos.splice(idx, 1);
          markDirty();
          openPanel(block);
          renderCanvas();
        }
      });
    });

    // Single Video: pick video button
    var videoPickBtn = $panelBody.querySelector('.pb-video-pick-btn');
    if (videoPickBtn) {
      videoPickBtn.addEventListener('click', function() {
        pbPickSingleVideo(block);
      });
    }

    // Single Video: remove video button
    var videoClearBtn = $panelBody.querySelector('.pb-video-clear-btn');
    if (videoClearBtn) {
      videoClearBtn.addEventListener('click', function(e) {
        e.stopPropagation();
        block.data.url = '';
        block.data.embed_id = '';
        block.data.source = '';
        block.data.title = '';
        markDirty();
        openPanel(block);
        renderCanvas();
      });
    }

    // Add/remove items for accordion and tabs
    $panelBody.querySelectorAll('[data-add-item]').forEach(function(btn) {
      btn.addEventListener('click', function() {
        var type = btn.dataset.addItem;
        if (type === 'accordion') {
          block.data.items = block.data.items || [];
          block.data.items.push({ title: PB_I18N.default_accordion_title || '', content: '<p></p>' });
        } else if (type === 'tabs') {
          block.data.tabs = block.data.tabs || [];
          block.data.tabs.push({ title: (PB_I18N.default_tab || 'Tab') + ' ' + (block.data.tabs.length + 1), content: '<p></p>' });
        } else if (type === 'slideshow') {
          block.data.slides = block.data.slides || [];
          block.data.slides.push({ image: '', alt: '', blocks: [] });
        }
        markDirty();
        openPanel(block);
        renderCanvas();
      });
    });

    $panelBody.querySelectorAll('[data-remove-item]').forEach(function(btn) {
      btn.addEventListener('click', function() {
        if (!confirm(PB_I18N.delete_item_confirm || 'Delete this item?')) return;
        var type = btn.dataset.removeItem;
        var idx = parseInt(btn.dataset.index);
        if (type === 'accordion' && block.data.items) block.data.items.splice(idx, 1);
        if (type === 'tabs' && block.data.tabs) block.data.tabs.splice(idx, 1);
        if (type === 'slideshow' && block.data.slides) block.data.slides.splice(idx, 1);
        markDirty();
        openPanel(block);
        renderCanvas();
      });
    });

    // Form block: use picker modal (same as canvas click)
    if (block.type === 'form') {
      var formPickBtn = document.getElementById('pbFormPickBtn');
      var formChangeBtn = document.getElementById('pbFormChangeBtn');
      var formClearBtn = document.getElementById('pbFormClearBtn');
      if (formPickBtn) {
        formPickBtn.addEventListener('click', function() { pbPickForm(block); });
      }
      if (formChangeBtn) {
        formChangeBtn.addEventListener('click', function() { pbPickForm(block); });
      }
      if (formClearBtn) {
        formClearBtn.addEventListener('click', function() {
          block.data.form_id = '';
          block.data._form_title = '';
          block.data._form_title_raw = '';
          block.data._fields = null;
          block.data._settings = null;
          block.data._description = '';
          markDirty();
          renderCanvas();
          openPanel(block);
        });
      }
    }

    // ── Number stepper buttons ──
    $panelBody.querySelectorAll('[data-step-up]').forEach(function(btn) {
      btn.addEventListener('click', function(e) {
        e.preventDefault();
        var key = btn.dataset.stepUp;
        var input = $panelBody.querySelector('[data-key="' + key + '"]');
        if (input) { input.value = parseInt(input.value || 0) + 1; input.dispatchEvent(new Event('input', { bubbles: true })); }
      });
    });
    $panelBody.querySelectorAll('[data-step-down]').forEach(function(btn) {
      btn.addEventListener('click', function(e) {
        e.preventDefault();
        var key = btn.dataset.stepDown;
        var input = $panelBody.querySelector('[data-key="' + key + '"]');
        if (input) { var v = parseInt(input.value || 0) - 1; input.value = Math.max(0, v); input.dispatchEvent(new Event('input', { bubbles: true })); }
      });
    });

    // ── Color + Alpha picker ──
    $panelBody.querySelectorAll('[data-ca-color]').forEach(function(swatch) {
      var key = swatch.dataset.caColor;
      var slider = $panelBody.querySelector('[data-ca-alpha="' + key + '"]');
      var label = $panelBody.querySelector('[data-ca-label="' + key + '"]');
      var textInput = $panelBody.querySelector('[data-key="' + key + '"]');
      function syncColorAlpha() {
        var hex = swatch.value;
        var a = parseInt(slider.value) / 100;
        var r = parseInt(hex.substring(1,3), 16);
        var g = parseInt(hex.substring(3,5), 16);
        var b = parseInt(hex.substring(5,7), 16);
        var rgba = 'rgba(' + r + ',' + g + ',' + b + ',' + a.toFixed(2) + ')';
        textInput.value = rgba;
        if (label) label.textContent = slider.value + '%';
        slider.style.background = 'linear-gradient(to right, transparent, ' + hex + ')';
        textInput.dispatchEvent(new Event('input', { bubbles: true }));
      }
      swatch.addEventListener('input', syncColorAlpha);
      slider.addEventListener('input', syncColorAlpha);
      // Init slider background
      slider.style.background = 'linear-gradient(to right, transparent, ' + swatch.value + ')';
    });

    // ── Gradient presets ──
    $panelBody.querySelectorAll('[data-gradient-pick]').forEach(function(btn) {
      btn.addEventListener('click', function(e) {
        e.preventDefault();
        var key = btn.dataset.gradientPick;
        var val = btn.dataset.gradientVal;
        var input = $panelBody.querySelector('[data-key="' + key + '"]');
        if (input) { input.value = val; input.dispatchEvent(new Event('input', { bubbles: true })); }
        // Update active state
        $panelBody.querySelectorAll('[data-gradient-pick="' + key + '"]').forEach(function(b) { b.classList.remove('is-active'); });
        btn.classList.add('is-active');
      });
    });

    // ── Rich text editing: handled on canvas via .pb-rich-zone ──

    // ── Visual table editor ──
    if (block.type === 'table') {
      // Header edits
      $panelBody.querySelectorAll('[data-table-header]').forEach(function(input) {
        input.addEventListener('input', function() {
          var idx = parseInt(input.dataset.tableHeader);
          block.data.headers[idx] = input.value;
          markDirty();
          renderCanvas();
        });
      });
      // Cell edits
      $panelBody.querySelectorAll('[data-table-cell]').forEach(function(input) {
        input.addEventListener('input', function() {
          var ri = parseInt(input.dataset.row);
          var ci = parseInt(input.dataset.col);
          if (!block.data.rows[ri]) block.data.rows[ri] = [];
          block.data.rows[ri][ci] = input.value;
          markDirty();
          renderCanvas();
        });
      });
      // Table actions: add row/col
      $panelBody.querySelectorAll('[data-table-action]').forEach(function(btn) {
        btn.addEventListener('click', function() {
          var action = btn.dataset.tableAction;
          if (action === 'add-row') {
            var cols = block.data.headers.length;
            block.data.rows.push(new Array(cols).fill(''));
          } else if (action === 'add-col') {
            block.data.headers.push('');
            block.data.rows.forEach(function(row) { row.push(''); });
            if (block.data.column_alignments) block.data.column_alignments.push('');
          }
          markDirty();
          openPanel(block);
          renderCanvas();
        });
      });
      // Table: remove specific row
      $panelBody.querySelectorAll('[data-table-remove-row]').forEach(function(btn) {
        btn.addEventListener('click', function() {
          var ri = parseInt(btn.dataset.tableRemoveRow);
          if (block.data.rows.length <= 1) return;
          block.data.rows.splice(ri, 1);
          markDirty();
          openPanel(block);
          renderCanvas();
        });
      });
      // Table: insert row above
      $panelBody.querySelectorAll('[data-table-insert-row]').forEach(function(btn) {
        btn.addEventListener('click', function() {
          var ri = parseInt(btn.dataset.tableInsertRow);
          var cols = block.data.headers.length;
          block.data.rows.splice(ri, 0, new Array(cols).fill(''));
          markDirty();
          openPanel(block);
          renderCanvas();
        });
      });
      // Table: remove specific column
      $panelBody.querySelectorAll('[data-table-remove-col]').forEach(function(btn) {
        btn.addEventListener('click', function() {
          var ci = parseInt(btn.dataset.tableRemoveCol);
          if (block.data.headers.length <= 1) return;
          block.data.headers.splice(ci, 1);
          block.data.rows.forEach(function(row) { row.splice(ci, 1); });
          if (block.data.column_alignments) block.data.column_alignments.splice(ci, 1);
          markDirty();
          openPanel(block);
          renderCanvas();
        });
      });

      // Column alignment dropdowns
      $panelBody.querySelectorAll('[data-col-align]').forEach(function(sel) {
        sel.addEventListener('change', function() {
          var ci = parseInt(sel.dataset.colAlign);
          if (!block.data.column_alignments) block.data.column_alignments = [];
          while (block.data.column_alignments.length <= ci) block.data.column_alignments.push('');
          block.data.column_alignments[ci] = sel.value;
          markDirty();
          renderCanvas();
        });
      });
      // Column width inputs
      $panelBody.querySelectorAll('[data-col-width]').forEach(function(input) {
        input.addEventListener('input', function() {
          var ci = parseInt(input.dataset.colWidth);
          if (!block.data.column_widths) block.data.column_widths = [];
          while (block.data.column_widths.length <= ci) block.data.column_widths.push('');
          block.data.column_widths[ci] = input.value.replace(/[^0-9]/g, '');
          markDirty();
          renderCanvas();
          // Update sum display
          var sumEl = document.getElementById('pbColWidthSum');
          if (sumEl) {
            var total = block.data.column_widths.reduce(function(s, w) { return s + (parseInt(w) || 0); }, 0);
            if (total > 0) {
              sumEl.textContent = (PB_I18N.column_width_hint || '%') + ' · ' + (PB_I18N.total || 'Total') + ': ' + total + '%' + (total > 100 ? ' ⚠' : '');
              sumEl.style.color = total > 100 ? 'var(--pb-danger)' : '';
            } else {
              sumEl.textContent = PB_I18N.column_width_hint || '%';
              sumEl.style.color = '';
            }
          }
        });
      });
    }
  }

  // ============================================================
  // BLOCK OPERATIONS
  // ============================================================
  function findBlock(id) {
    return state.blocks.find(function(b) { return b.id === id; });
  }

  function findIndex(id) {
    return state.blocks.findIndex(function(b) { return b.id === id; });
  }

  function findBlockDeep(id) {
    for (var i = 0; i < state.blocks.length; i++) {
      var b = state.blocks[i];
      if (b.id === id) return b;
      if (b.type === 'columns' && b.data && b.data.columns) {
        for (var c = 0; c < b.data.columns.length; c++) {
          var cblocks = b.data.columns[c].blocks || [];
          for (var j = 0; j < cblocks.length; j++) { if (cblocks[j].id === id) return cblocks[j]; }
        }
      }
      if (b.type === 'section' && b.data && b.data.blocks) {
        for (var j = 0; j < b.data.blocks.length; j++) {
          if (b.data.blocks[j].id === id) return b.data.blocks[j];
          var sb = b.data.blocks[j];
          if (sb.type === 'columns' && sb.data && sb.data.columns) {
            for (var sc = 0; sc < sb.data.columns.length; sc++) {
              var scblocks = sb.data.columns[sc].blocks || [];
              for (var sk = 0; sk < scblocks.length; sk++) { if (scblocks[sk].id === id) return scblocks[sk]; }
            }
          }
        }
      }
      if (b.type === 'slideshow' && b.data && b.data.slides) {
        for (var s = 0; s < b.data.slides.length; s++) {
          var sblocks = (b.data.slides[s].blocks || []);
          for (var j = 0; j < sblocks.length; j++) { if (sblocks[j].id === id) return sblocks[j]; }
        }
      }
    }
    return null;
  }

  function addBlock(type, index) {
    console.log('[PageBuilder] Adding block:', type);
    var data = JSON.parse(JSON.stringify(BLOCK_DEFAULTS[type] || {}));
    var block = { id: uuid(), type: type, data: data, settings: { alignment: 'center' } };
    if (index === undefined || index < 0) {
      state.blocks.push(block);
    } else {
      state.blocks.splice(index, 0, block);
    }
    markDirty();
    renderCanvas();
    openPanel(block);
  }

  function deleteBlock(id) {
    var idx = findIndex(id);
    if (idx < 0) return;
    var block = state.blocks[idx];
    var label = BLOCK_LABELS[block.type] || block.type;
    if (!confirm((PB_I18N.delete_block_confirm || 'Delete this {type} block?').replace('{type}', label))) return;
    state.blocks.splice(idx, 1);
    if (state.selectedId === id) closePanel();
    markDirty();
    renderCanvas();
  }

  function duplicateBlock(id) {
    var idx = findIndex(id);
    if (idx >= 0) {
      var copy = JSON.parse(JSON.stringify(state.blocks[idx]));
      _regenerateIds(copy);
      state.blocks.splice(idx + 1, 0, copy);
      markDirty();
      renderCanvas();
      openPanel(copy);
    }
  }

  // Recursively regenerate IDs for a block and all nested blocks
  function _regenerateIds(block) {
    block.id = uuid();
    if (block.data) {
      // Section nested blocks
      if (block.data.blocks) {
        block.data.blocks.forEach(_regenerateIds);
      }
      // Column nested blocks
      if (block.data.columns) {
        block.data.columns.forEach(function(col) {
          (col.blocks || []).forEach(_regenerateIds);
        });
      }
      // Slideshow nested blocks
      if (block.data.slides) {
        block.data.slides.forEach(function(slide) {
          (slide.blocks || []).forEach(_regenerateIds);
        });
      }
    }
  }

  function moveBlock(id, dir) {
    var idx = findIndex(id);
    var newIdx = idx + dir;
    if (idx < 0 || newIdx < 0 || newIdx >= state.blocks.length) return;
    var b = state.blocks.splice(idx, 1)[0];
    state.blocks.splice(newIdx, 0, b);
    markDirty();
    renderCanvas();
  }

  // ============================================================
  // EVENT LISTENERS
  // ============================================================

  // Save button
  $save.addEventListener('click', saveBlocks);

  // Keyboard shortcut: Ctrl+S
  document.addEventListener('keydown', function(e) {
    if ((e.ctrlKey || e.metaKey) && e.key === 's') {
      e.preventDefault();
      saveBlocks();
    }
  });

  // Close panel
  document.getElementById('pbPanelClose').addEventListener('click', closePanel);

  // Canvas click delegation
  $blocks.addEventListener('click', function(e) {
    var target = e.target;

    // Skip if click is on nested column block elements (handled by bindColumnEvents)
    if (target.closest('.pb-col-block') || target.closest('.pb-col-add') || target.closest('.pb-col-zone__empty') || target.closest('.pb-col-picker')) return;

    // Skip inline editing elements (handled by bindInlineEditing)
    if (target.closest('[contenteditable]') || target.closest('[data-click-edit]') || target.closest('[data-inline-input]') || target.closest('[data-inline-textarea]') || target.closest('.pb-inline-table input') || target.closest('.pb-text-toolbar')) return;

    // Delete
    var del = target.closest('.pb-item__delete');
    if (del) { deleteBlock(del.closest('.pb-item').dataset.id); return; }

    // Duplicate
    var dup = target.closest('.pb-item__duplicate');
    if (dup) { duplicateBlock(dup.closest('.pb-item').dataset.id); return; }

    // Move up
    var up = target.closest('.pb-item__move-up');
    if (up) { moveBlock(up.closest('.pb-item').dataset.id, -1); return; }

    // Move down
    var down = target.closest('.pb-item__move-down');
    if (down) { moveBlock(down.closest('.pb-item').dataset.id, 1); return; }

    // Select block
    var item = target.closest('.pb-item');
    if (item) {
      var block = findBlock(item.dataset.id);
      if (block) openPanel(block);
    }
  });

  // Sidebar — add block on click
  document.querySelectorAll('.pb-block-btn').forEach(function(btn) {
    btn.addEventListener('click', function() {
      addBlock(btn.dataset.blockType);
    });
  });

  // Drag from sidebar to canvas (basic HTML5 drag & drop)
  var _dragSidebarType = null;
  document.querySelectorAll('.pb-block-btn').forEach(function(btn) {
    btn.draggable = true;
    btn.addEventListener('dragstart', function(e) {
      _dragSidebarType = btn.dataset.blockType;
      e.dataTransfer.setData('text/plain', btn.dataset.blockType);
      e.dataTransfer.setData('application/x-pb-source', 'sidebar');
      e.dataTransfer.effectAllowed = 'copy';
    });
    btn.addEventListener('dragend', function() {
      _dragSidebarType = null;
      // Clear any position indicators left on canvas items
      $blocks.querySelectorAll('.pb-item').forEach(function(el) {
        el.style.borderTop = '';
        el.style.borderBottom = '';
      });
    });
  });

  $canvas.addEventListener('dragover', function(e) {
    e.preventDefault();
    e.dataTransfer.dropEffect = 'copy';
    $canvas.classList.add('pb-canvas--drag-over');
  });

  $canvas.addEventListener('dragleave', function() {
    $canvas.classList.remove('pb-canvas--drag-over');
  });

  $canvas.addEventListener('drop', function(e) {
    e.preventDefault();
    $canvas.classList.remove('pb-canvas--drag-over');
    var type = e.dataTransfer.getData('text/plain');
    var source = e.dataTransfer.getData('application/x-pb-source');
    if (source === 'sidebar' && type && BLOCK_DEFAULTS[type]) {
      addBlock(type);
    }
  });

  // ============================================================
  // DRAG-TO-REORDER — reorder existing blocks by dragging handle
  // ============================================================
  var _dragBlockId = null;

  // Bind drag handles after each render
  function bindDragReorder() {
    $blocks.querySelectorAll('.pb-item').forEach(function(item) {
      var handle = item.querySelector('.pb-item__drag-handle');
      if (!handle) return;

      // Make item draggable only via handle
      handle.addEventListener('mousedown', function() { item.setAttribute('draggable', 'true'); });
      handle.addEventListener('mouseup', function() { item.removeAttribute('draggable'); });

      item.addEventListener('dragstart', function(e) {
        if (!item.getAttribute('draggable')) { e.preventDefault(); return; }
        _dragBlockId = item.dataset.id;
        e.dataTransfer.setData('application/x-pb-source', 'reorder');
        e.dataTransfer.setData('text/plain', '');
        e.dataTransfer.effectAllowed = 'move';
        item.style.opacity = '0.4';
      });

      item.addEventListener('dragend', function() {
        item.style.opacity = '';
        item.removeAttribute('draggable');
        _dragBlockId = null;
        // Clear all indicators
        $blocks.querySelectorAll('.pb-item').forEach(function(el) {
          el.style.borderTop = '';
          el.style.borderBottom = '';
        });
      });

      item.addEventListener('dragover', function(e) {
        var isDrag = _dragBlockId && _dragBlockId !== item.dataset.id;
        var isSidebar = !!_dragSidebarType;
        if (!isDrag && !isSidebar) return;
        e.preventDefault();
        e.stopPropagation();
        e.dataTransfer.dropEffect = isSidebar ? 'copy' : 'move';
        // Show indicator: top or bottom half
        var rect = item.getBoundingClientRect();
        var midY = rect.top + rect.height / 2;
        if (e.clientY < midY) {
          item.style.borderTop = '3px solid var(--pb-primary)';
          item.style.borderBottom = '';
        } else {
          item.style.borderTop = '';
          item.style.borderBottom = '3px solid var(--pb-primary)';
        }
      });

      item.addEventListener('dragleave', function() {
        item.style.borderTop = '';
        item.style.borderBottom = '';
      });

      item.addEventListener('drop', function(e) {
        e.preventDefault();
        e.stopPropagation();
        item.style.borderTop = '';
        item.style.borderBottom = '';

        var rect = item.getBoundingClientRect();
        var midY = rect.top + rect.height / 2;
        var insertAfter = e.clientY >= midY;
        var toIdx = findIndex(item.dataset.id);

        // Sidebar drop: insert new block at position
        if (_dragSidebarType && BLOCK_DEFAULTS[_dragSidebarType]) {
          var insertIdx = (toIdx >= 0 && insertAfter) ? toIdx + 1 : (toIdx >= 0 ? toIdx : undefined);
          addBlock(_dragSidebarType, insertIdx);
          _dragSidebarType = null;
          return;
        }

        // Reorder drop
        if (!_dragBlockId || _dragBlockId === item.dataset.id) return;

        var fromIdx = findIndex(_dragBlockId);
        if (fromIdx < 0 || toIdx < 0) return;

        var block = state.blocks.splice(fromIdx, 1)[0];
        var newIdx = findIndex(item.dataset.id);
        if (insertAfter) newIdx++;
        state.blocks.splice(newIdx, 0, block);

        _dragBlockId = null;
        markDirty();
        renderCanvas();
      });
    });
  }

  // Patch renderCanvas to bind drag reorder after each render
  var _origRenderCanvas = renderCanvas;
  var _isInlineEditing = false;  // Prevent re-render while user types inline
  renderCanvas = function() {
    if (_isInlineEditing) return;  // Don't destroy contenteditable while editing
    _origRenderCanvas();
    bindDragReorder();
    bindInserters();
    try { bindInlineEditing(); } catch(e) { console.warn('[PageBuilder] bindInlineEditing error:', e); }
  };

  // ============================================================
  // INLINE EDITING — edit blocks directly in canvas
  // ============================================================

  // Helper: set a deep key on a block (e.g. "data.text", "data.items.0")
  function _setDeepKey(block, keyPath, value) {
    var parts = keyPath.split('.');
    var target = block;
    for (var i = 0; i < parts.length - 1; i++) {
      if (target[parts[i]] === undefined) target[parts[i]] = {};
      target = target[parts[i]];
    }
    target[parts[parts.length - 1]] = value;
    markDirty();
  }

  // Helper: bind focus/blur on an inline element
  function _bindInlineFocusBlur(el) {
    el.addEventListener('focus', function(e) {
      e.stopPropagation();
      _isInlineEditing = true;
      // Open settings panel for this block
      var blockId = el.dataset.inlineBlock;
      if (blockId) {
        var block = findBlockDeep(blockId);
        if (block && state.selectedId !== blockId) {
          openPanel(block);
        }
      }
    });
    el.addEventListener('blur', function() {
      _isInlineEditing = false;
    });
  }

  // Helper: sync panel input if open
  function _syncPanel(key, value) {
    var panelInput = $panelBody.querySelector('[data-key="' + key + '"]');
    if (panelInput && panelInput.value !== value) {
      panelInput.value = value;
    }
  }

  // Helper: update toolbar button active states
  function _updateToolbarState(toolbar) {
    if (!toolbar) return;
    toolbar.querySelectorAll('.pb-text-toolbar__btn').forEach(function(btn) {
      var cmd = btn.dataset.cmd;
      var active = false;
      try { active = document.queryCommandState(cmd); } catch(e) {}
      btn.classList.toggle('is-active', active);
    });
  }

  function bindInlineEditing() {

    // ── 1. Plain text contenteditable: Heading, Quote, Button (data-inline-key) ──
    $blocks.querySelectorAll('[data-inline-key]').forEach(function(el) {
      _bindInlineFocusBlur(el);
      el.addEventListener('input', function() {
        var blockId = el.dataset.inlineBlock;
        var key = el.dataset.inlineKey;
        if (!blockId || !key) return;
        var block = findBlockDeep(blockId);
        if (block) {
          _setDeepKey(block, key, el.textContent);
          _syncPanel(key, el.textContent);
        }
      });
      el.addEventListener('keydown', function(e) {
        if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); el.blur(); }
      });
      el.addEventListener('paste', function(e) {
        e.preventDefault();
        var text = (e.clipboardData || window.clipboardData).getData('text/plain');
        document.execCommand('insertText', false, text);
      });
    });

    // ── 2. Rich text contenteditable + toolbar: Text, Tab content, Accordion content (data-inline-html) ──
    $blocks.querySelectorAll('[data-inline-html]').forEach(function(el) {
      _bindInlineFocusBlur(el);
      var zone = el.closest('.pb-rich-zone');
      var toolbar = zone ? zone.querySelector('.pb-text-toolbar') : null;

      // Toolbar: prevent mousedown from stealing focus
      if (toolbar) {
        toolbar.addEventListener('mousedown', function(e) { e.preventDefault(); });
        toolbar.addEventListener('click', function(e) {
          var btn = e.target.closest('.pb-text-toolbar__btn');
          if (!btn) return;
          var cmd = btn.dataset.cmd;
          if (cmd === 'createLink') {
            var url = prompt(PB_I18N.link_url || 'URL:');
            if (url) document.execCommand('createLink', false, url);
          } else {
            document.execCommand(cmd, false, null);
          }
          _updateToolbarState(toolbar);
        });
      }

      el.addEventListener('input', function() {
        var blockId = el.dataset.inlineBlock;
        var key = el.dataset.inlineHtml;
        if (!blockId || !key) return;
        var block = findBlockDeep(blockId);
        if (block) { _setDeepKey(block, key, el.innerHTML); }
        _updateToolbarState(toolbar);
      });
      el.addEventListener('keyup', function() { _updateToolbarState(toolbar); });
      el.addEventListener('mouseup', function() { _updateToolbarState(toolbar); });
      el.addEventListener('paste', function(e) {
        e.preventDefault();
        var html = (e.clipboardData || window.clipboardData).getData('text/html');
        if (html) {
          var clean = _sanitizeRichPaste(html);
          document.execCommand('insertHTML', false, clean);
        } else {
          var text = (e.clipboardData || window.clipboardData).getData('text/plain');
          document.execCommand('insertText', false, text);
        }
        // Sync model
        var blockId = el.dataset.inlineBlock;
        var key = el.dataset.inlineHtml;
        if (blockId && key) {
          var block = findBlockDeep(blockId);
          if (block) { _setDeepKey(block, key, el.innerHTML); }
        }
      });
    });

    // ── 3. Input fields: Video URL, Audio URL/Title (data-inline-input) ──
    $blocks.querySelectorAll('[data-inline-input]').forEach(function(el) {
      _bindInlineFocusBlur(el);
      el.addEventListener('input', function() {
        var blockId = el.dataset.inlineBlock;
        var key = el.dataset.inlineInput;
        if (!blockId || !key) return;
        var block = findBlockDeep(blockId);
        if (block) {
          _setDeepKey(block, key, el.value);
          _syncPanel(key, el.value);
          // Auto-extract YouTube/Vimeo ID for video blocks
          if (key === 'data.url' && block.type === 'video') {
            var yt = el.value.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/)([^&?\s]+)/);
            var vm = el.value.match(/vimeo\.com\/(\d+)/);
            if (yt) { block.data.embed_id = yt[1]; block.data.source = 'youtube'; }
            else if (vm) { block.data.embed_id = vm[1]; block.data.source = 'vimeo'; }
          }
        }
      });
    });

    // ── 4. Textareas: HTML block, Code block (data-inline-textarea) ──
    $blocks.querySelectorAll('[data-inline-textarea]').forEach(function(el) {
      _bindInlineFocusBlur(el);
      el.addEventListener('input', function() {
        var blockId = el.dataset.inlineBlock;
        var key = el.dataset.inlineTextarea;
        if (!blockId || !key) return;
        var block = findBlockDeep(blockId);
        if (block) {
          _setDeepKey(block, key, el.value);
          _syncPanel(key, el.value);
        }
      });
    });

    // ── 5. Accordion titles (data-inline-acc) ──
    $blocks.querySelectorAll('[data-inline-acc]').forEach(function(el) {
      _bindInlineFocusBlur(el);
      el.addEventListener('input', function() {
        var blockId = el.dataset.inlineBlock;
        var idx = parseInt(el.dataset.inlineAcc);
        if (!blockId || isNaN(idx)) return;
        var block = findBlockDeep(blockId);
        if (block && block.data.items && block.data.items[idx]) {
          block.data.items[idx].title = el.textContent;
          markDirty();
          _syncPanel('data.items.' + idx + '.title', el.textContent);
        }
      });
      el.addEventListener('keydown', function(e) {
        if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); el.blur(); }
      });
      el.addEventListener('paste', function(e) {
        e.preventDefault();
        var text = (e.clipboardData || window.clipboardData).getData('text/plain');
        document.execCommand('insertText', false, text);
      });
    });

    // ── 7. Tab titles (data-inline-tab) ──
    $blocks.querySelectorAll('[data-inline-tab]').forEach(function(el) {
      _bindInlineFocusBlur(el);
      el.addEventListener('input', function() {
        var blockId = el.dataset.inlineBlock;
        var idx = parseInt(el.dataset.inlineTab);
        if (!blockId || isNaN(idx)) return;
        var block = findBlockDeep(blockId);
        if (block && block.data.tabs && block.data.tabs[idx]) {
          block.data.tabs[idx].title = el.textContent;
          markDirty();
          _syncPanel('data.tabs.' + idx + '.title', el.textContent);
        }
      });
      el.addEventListener('keydown', function(e) {
        if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); el.blur(); }
      });
      el.addEventListener('paste', function(e) {
        e.preventDefault();
        var text = (e.clipboardData || window.clipboardData).getData('text/plain');
        document.execCommand('insertText', false, text);
      });
    });

    // ── 8. Table headers (data-inline-th) and cells (data-inline-td) ──
    $blocks.querySelectorAll('[data-inline-th]').forEach(function(el) {
      _bindInlineFocusBlur(el);
      el.addEventListener('input', function() {
        var blockId = el.dataset.inlineBlock;
        var idx = parseInt(el.dataset.inlineTh);
        if (!blockId || isNaN(idx)) return;
        var block = findBlockDeep(blockId);
        if (block && block.data.headers) {
          block.data.headers[idx] = _sanitizeTableCell(el.innerHTML);
          markDirty();
          // Sync sidebar input (plain text)
          var si = $panelBody.querySelector('[data-table-header="' + idx + '"]');
          if (si) si.value = el.textContent;
        }
      });
    });

    $blocks.querySelectorAll('[data-inline-td]').forEach(function(el) {
      _bindInlineFocusBlur(el);
      el.addEventListener('input', function() {
        var blockId = el.dataset.inlineBlock;
        var coords = (el.dataset.inlineTd || '').split(',');
        var ri = parseInt(coords[0]), ci = parseInt(coords[1]);
        if (!blockId || isNaN(ri) || isNaN(ci)) return;
        var block = findBlockDeep(blockId);
        if (block && block.data.rows && block.data.rows[ri]) {
          block.data.rows[ri][ci] = _sanitizeTableCell(el.innerHTML);
          markDirty();
          // Sync sidebar input (plain text)
          var si = $panelBody.querySelector('[data-table-cell][data-row="' + ri + '"][data-col="' + ci + '"]');
          if (si) si.value = el.textContent;
        }
      });
    });

    // ── 8b. Table paste handler — sanitize and keep basic formatting ──
    $blocks.querySelectorAll('[data-inline-th],[data-inline-td]').forEach(function(el) {
      el.addEventListener('paste', function(e) {
        e.preventDefault();
        var html = (e.clipboardData || window.clipboardData).getData('text/html');
        if (html) {
          var clean = _sanitizeTableCell(html);
          document.execCommand('insertHTML', false, clean);
        } else {
          var text = (e.clipboardData || window.clipboardData).getData('text/plain');
          document.execCommand('insertText', false, text);
        }
      });
    });

    // ── 8c. Table compact formatting toolbar ──
    var _lastTableCell = null;
    $blocks.querySelectorAll('[data-inline-th],[data-inline-td]').forEach(function(el) {
      el.addEventListener('focus', function() { _lastTableCell = el; });
    });
    $blocks.querySelectorAll('.pb-inline-table > .pb-text-toolbar--compact').forEach(function(toolbar) {
      toolbar.addEventListener('mousedown', function(e) { e.preventDefault(); });
      toolbar.addEventListener('click', function(e) {
        var btn = e.target.closest('.pb-text-toolbar__btn');
        if (!btn || !_lastTableCell) return;
        var cmd = btn.dataset.cmd;
        _lastTableCell.focus();
        if (cmd === 'createLink') {
          var url = prompt(PB_I18N.link_url || 'URL:');
          if (url) document.execCommand('createLink', false, url);
        } else {
          document.execCommand(cmd, false, null);
        }
        _updateToolbarState(toolbar);
      });
    });

    // ── 9. Table symbol picker ──
    $blocks.querySelectorAll('.pb-table-symbols').forEach(function(toolbar) {
      toolbar.addEventListener('mousedown', function(e) { e.preventDefault(); });
      toolbar.addEventListener('click', function(e) {
        var btn = e.target.closest('[data-insert-symbol]');
        if (!btn || !_lastTableCell) return;
        var sym = btn.dataset.insertSymbol;
        _lastTableCell.focus();
        document.execCommand('insertText', false, sym);
      });
    });

    // ── 10. Table canvas row inserter ──
    $blocks.querySelectorAll('[data-table-canvas-insert]').forEach(function(row) {
      row.querySelector('.pb-inserter__btn').addEventListener('click', function(e) {
        e.stopPropagation();
        var blockId = row.dataset.tableCanvasInsert;
        var idx = parseInt(row.dataset.tableCanvasIdx);
        var block = findBlockDeep(blockId);
        if (!block) return;
        var cols = block.data.headers.length;
        block.data.rows.splice(idx, 0, new Array(cols).fill(''));
        markDirty();
        renderCanvas();
        if (state.selectedId === blockId) openPanel(block);
      });
    });

    // ── 11. Table canvas row delete ──
    $blocks.querySelectorAll('[data-table-canvas-delete]').forEach(function(btn) {
      btn.addEventListener('click', function(e) {
        e.stopPropagation();
        var blockId = btn.dataset.tableCanvasDelete;
        var idx = parseInt(btn.dataset.tableCanvasDeleteIdx);
        var block = findBlockDeep(blockId);
        if (!block || !block.data.rows || block.data.rows.length <= 1) return;
        block.data.rows.splice(idx, 1);
        markDirty();
        renderCanvas();
        if (state.selectedId === blockId) openPanel(block);
      });
    });
  }

  // Warn before leaving with unsaved changes
  window.addEventListener('beforeunload', function(e) {
    if (state.isDirty) {
      e.preventDefault();
      e.returnValue = '';
    }
  });

  // ============================================================
  // INIT — Load blocks
  // ============================================================
  console.log('[PageBuilder] Initializing for page', PAGE_ID);
  console.log('[PageBuilder] PB_I18N loaded:', typeof PB_I18N !== 'undefined');
  console.log('[PageBuilder] Block buttons found:', document.querySelectorAll('.pb-block-btn').length);

  // Expose state for AI integration
  window._pbState = state;
  window._pbSetBlocks = function(blocks) {
    state.blocks = blocks;
    markDirty();
    renderCanvas();
    showToast((PB_I18N.ai_blocks_loaded || 'AI blocks loaded — {count} blocks').replace('{count}', blocks.length), 'success');
  };

  loadBlocks();

})();
</script>

<!-- ====== AI ASSISTANT FOR BUILDER ====== -->
<div class="pb-ai-overlay" id="pbAIOverlay">
  <div class="pb-ai-modal">
    <div class="pb-ai-modal__header">
      <h3>🤖 AI — {{ t('ai.page_builder') }}</h3>
      <button class="pb-ai-modal__close" onclick="closeBuilderAI()">&times;</button>
    </div>
    <div class="pb-ai-modal__body">
      <div class="pb-ai-chat" id="pbAIChat"></div>
      <div class="pb-ai-input-row">
        <textarea class="pb-ai-textarea" id="pbAIInput" rows="2" placeholder="{{ t('builder.ai_placeholder') }}"
          onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();sendBuilderAI()}"></textarea>
        <button class="pb-ai-send" id="pbAISend" onclick="sendBuilderAI()">{{ t('ai.send') }}</button>
      </div>
      <div class="pb-ai-actions" id="pbAIActions">
        <button class="pb-ai-apply" id="pbAIApply" onclick="applyBuilderAI()">✅ {{ t('builder.ai_apply_blocks') }}</button>
        <button class="pb-ai-cancel" onclick="closeBuilderAI()">{{ t('common.cancel') }}</button>
      </div>
    </div>
  </div>
</div>

<script>
(function() {
  var overlay = document.getElementById('pbAIOverlay');
  var chat = document.getElementById('pbAIChat');
  var input = document.getElementById('pbAIInput');
  var sendBtn = document.getElementById('pbAISend');
  var actionsDiv = document.getElementById('pbAIActions');
  var aiState = { messages: [], lastParsed: null, busy: false };

  // Auto-resize textarea
  input.addEventListener('input', function() {
    this.style.height = 'auto';
    this.style.height = Math.min(this.scrollHeight, 300) + 'px';
  });

  function escHtml(s) { var d = document.createElement('div'); d.textContent = s || ''; return d.innerHTML; }

  function addMsg(role, text) {
    var div = document.createElement('div');
    div.className = 'pb-ai-msg pb-ai-msg--' + role;
    div.innerHTML = text;
    chat.appendChild(div);
    chat.scrollTop = chat.scrollHeight;
    aiState.messages.push({ role: role, text: text });
    return div;
  }

  window.openBuilderAI = function() {
    aiState = { messages: [], lastParsed: null, busy: false };
    chat.innerHTML = '';
    actionsDiv.style.display = 'none';
    input.value = '';
    input.style.height = 'auto';
    input.disabled = false;
    sendBtn.disabled = false;
    addMsg('ai', PB_I18N.ai_welcome || 'Tell me what page you\'d like to build! Describe the purpose, sections, and content. The more detail you give, the better the result.');
    overlay.classList.add('is-open');
    setTimeout(function() { input.focus(); }, 100);
  };

  window.closeBuilderAI = function() {
    overlay.classList.remove('is-open');
  };

  window.sendBuilderAI = function() {
    if (aiState.busy) return;
    var text = input.value.trim();
    if (!text) return;
    addMsg('user', escHtml(text));
    input.value = '';
    input.style.height = 'auto';
    input.disabled = true;
    sendBtn.disabled = true;
    aiState.busy = true;

    var loading = addMsg('ai', PB_I18N.ai_thinking || 'Thinking...');
    loading.classList.add('pb-ai-msg--loading');

    // Build conversation history
    var history = aiState.messages.filter(function(m) {
      return m.role === 'user' || m.role === 'ai';
    }).slice(0, -1).map(function(m) {
      return (m.role === 'user' ? 'User' : 'Assistant') + ': ' + m.text;
    }).join('\n');

    fetch('/admin/ai/generate', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        task: 'builder_blocks',
        prompt: text,
        content_type: 'page',
        language: '{{ page.get_language_name() if page.get_language_name else "English" }}',
        title: '{{ page.title|e }}',
        conversation_history: history
      })
    })
    .then(function(r) { return r.json(); })
    .then(function(data) {
      loading.remove();
      aiState.messages.pop();

      if (data.error) {
        addMsg('system', '❌ ' + escHtml(data.error));
        return;
      }

      try {
        var jsonStr = data.result;
        var match = jsonStr.match(/```(?:json)?\s*([\s\S]*?)```/);
        if (match) jsonStr = match[1];
        var parsed = JSON.parse(jsonStr.trim());
        aiState.lastParsed = parsed;

        if (parsed.ai_message) {
          addMsg('ai', escHtml(parsed.ai_message));
        }

        var blockCount = (parsed.blocks || []).length;
        if (blockCount > 0) {
          addMsg('ai', (PB_I18N.ai_generated || '✅ Generated <strong>{count} blocks</strong>. Click "Apply" to load them into the canvas.').replace('{count}', blockCount));
          actionsDiv.style.display = 'flex';
        } else {
          addMsg('ai', PB_I18N.ai_no_blocks || 'No blocks generated. Try being more specific about the page structure.');
        }
      } catch (e) {
        // AI responded with text, not JSON — show as conversation
        addMsg('ai', escHtml(data.result).replace(/\n/g, '<br>'));
      }
    })
    .catch(function(err) {
      loading.remove();
      aiState.messages.pop();
      addMsg('system', '❌ ' + escHtml(err.message));
    })
    .finally(function() {
      input.disabled = false;
      sendBtn.disabled = false;
      aiState.busy = false;
      input.focus();
    });
  };

  window.applyBuilderAI = function() {
    if (!aiState.lastParsed || !aiState.lastParsed.blocks) return;
    var newBlocks = aiState.lastParsed.blocks;

    // Ensure all blocks have IDs
    newBlocks.forEach(function(b) {
      if (!b.id) b.id = 'b' + Math.random().toString(36).substr(2, 9);
      if (!b.settings) b.settings = {};
      // Fix nested column blocks
      if (b.type === 'columns' && b.data && b.data.columns) {
        b.data.columns.forEach(function(col) {
          (col.blocks || []).forEach(function(cb) {
            if (!cb.id) cb.id = 'b' + Math.random().toString(36).substr(2, 9);
            if (!cb.settings) cb.settings = {};
          });
        });
      }
      // Fix nested section blocks
      if (b.type === 'section' && b.data && b.data.blocks) {
        b.data.blocks.forEach(function(sb) {
          if (!sb.id) sb.id = 'b' + Math.random().toString(36).substr(2, 9);
          if (!sb.settings) sb.settings = {};
        });
      }
      // Fix nested slideshow blocks (hero mode — blocks inside slides)
      if (b.type === 'slideshow' && b.data && b.data.slides) {
        b.data.slides.forEach(function(slide) {
          (slide.blocks || []).forEach(function(sb) {
            if (!sb.id) sb.id = 'b' + Math.random().toString(36).substr(2, 9);
            if (!sb.settings) sb.settings = {};
          });
        });
      }
    });

    if (window._pbSetBlocks) {
      window._pbSetBlocks(newBlocks);
    }
    closeBuilderAI();
  };

  // Close on overlay click
  overlay.addEventListener('click', function(e) { if (e.target === overlay) closeBuilderAI(); });
  document.addEventListener('keydown', function(e) { if (e.key === 'Escape' && overlay.classList.contains('is-open')) closeBuilderAI(); });
})();
</script>

</body>
</html>
