// Scenes & Containers Isolated Stage (read-only scaffold)
// Authoring: 1280x720, Roku scales on-device
// Build 53: Converted to ES6 module for Smart Reflow support

// ============================================================================
// IMPORTS - Smart Reflow Engine for cross-platform layout adaptation
// ============================================================================
import { 
    calculateReflowLayout, 
    needsReflow, 
    getReflowLayoutMode,
    ensureReflowMetadata 
} from '../modules/reflow-engine.js';

const rootId = 'cc-scenes-stage-root';

// Cache for image dimensions to avoid reloading
const imageDimensionsCache = new Map();

// Build 52: Custom Font Mapping for Scenes Stage Preview
// Maps Roku-bundled font names to proper CSS font stacks
function mapFontFamilyToCSS(fontFamily) {
    const fontMap = {
        'Lexend': '"Lexend", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
        'Oxygen': '"Oxygen", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
        'ShareTechMono': '"Share Tech Mono", "Courier New", Consolas, monospace',
        'SpaceGrotesk': '"Space Grotesk", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
        'system-ui,Arial,Helvetica,sans-serif': 'system-ui, Arial, Helvetica, sans-serif'
    };
    return fontMap[fontFamily] || fontFamily || 'system-ui, Arial, Helvetica, sans-serif';
}

/**
 * Get image dimensions from URL
 * Returns { width, height } or null if failed
 */
async function getImageDimensions(url) {
  if (!url) return null;
  
  // Check cache first
  if (imageDimensionsCache.has(url)) {
    return imageDimensionsCache.get(url);
  }
  
  return new Promise((resolve) => {
    const img = new Image();
    img.onload = () => {
      const dims = { width: img.naturalWidth, height: img.naturalHeight };
      imageDimensionsCache.set(url, dims);
      resolve(dims);
    };
    img.onerror = () => resolve(null);
    img.src = url;
  });
}

function el(tag, attrs = {}, children = []) {
  const n = document.createElement(tag);
  Object.entries(attrs).forEach(([k, v]) => {
    if (v === undefined || v === null) return; // Skip undefined/null attributes
    if (k === 'style' && typeof v === 'object') Object.assign(n.style, v);
    else if (k.startsWith('on') && typeof v === 'function') n[k] = v;
    else if (k === 'selected' && v) n.selected = true; // Handle selected as property
    else if (k === 'checked' && v) n.checked = true; // Handle checked as property
    else n.setAttribute(k, v);
  });
  (Array.isArray(children) ? children : [children]).filter(Boolean).forEach(c => {
    if (typeof c === 'string') n.appendChild(document.createTextNode(c));
    else n.appendChild(c);
  });
  return n;
}

function stageChrome() {
  return el('div', { id: 'cc-scenes-stage', class: 'cc-scenes-stage' }, [
    // Status is rendered outside of the visual editor entirely
    el('div', { class: 'cc-stage-status-outside', 'data-host': 'status' }, [
      // Toolbar: Scene label + single manager button
      el('div', { style: { display: 'flex', gap: '8px', justifyContent: 'space-between', alignItems: 'center', width: '100%' } }, [
        el('div', { style: { display: 'flex', gap: '12px', alignItems: 'center' } }, [
          el('div', { 'data-host': 'status-scene' }),
          el('div', { 'data-host': 'status-pill' })
        ]),
        el('div', { style: { display: 'flex', gap: '8px', alignItems: 'center' } }, [
          // Return to Live button - hidden by default, shown in preview mode
          el('button', { 
            class: 'button', 
            id: 'cc-return-to-live-btn', 
            title: 'Return to live active scene',
            style: { display: 'none', background: '#22c55e', color: '#fff', borderColor: '#16a34a' }
          }, '↩ Return to Live'),
          el('button', { class: 'button primary', id: 'cc-scene-manager-btn', title: 'Manage Scenes, Background, Branding & Containers' }, '⚙️ Scene Manager')
        ])
      ])
    ]),
    el('div', { class: 'cc-stage-outer' }, [
      el('div', { class: 'cc-stage-viewport' }, [
        el('div', { class: 'cc-stage-canvas' }, [
          el('div', { class: 'cc-stage-background', 'data-host': 'background' }),
          el('div', { class: 'cc-stage-branding', 'data-host': 'branding' }),
          el('div', { class: 'cc-stage-containers', 'data-host': 'containers' })
        ]),
        // Keep overlay for empty-state actions inside viewport if needed
        el('div', { class: 'cc-stage-overlay', 'data-host': 'overlay' })
      ])
    ]),
    // Assignments panel host (outside canvas)
    el('div', { class: 'cc-assignments-host', 'data-host': 'assignments' }),
    // Lightweight modal host
    el('div', { class: 'cc-modal-host', 'data-host': 'modal', style: { display: 'none' } })
  ]);
}

async function fetchJSON(url) {
  const res = await fetch(url, {
    headers: { 'X-WP-Nonce': (window.castconductorScenesAjax || {}).nonce },
    credentials: 'same-origin'
  });
  if (!res.ok) {
    throw new Error(`HTTP ${res.status} for ${url}`);
  }
  return res.json();
}

async function postJSON(url, body = {}) {
  const res = await fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-WP-Nonce': (window.castconductorScenesAjax || {}).nonce
    },
    body: JSON.stringify(body),
    credentials: 'same-origin'
  });
  if (!res.ok) {
    throw new Error(`HTTP ${res.status} for ${url}`);
  }
  return res.json();
}

async function deleteJSON(url) {
  const res = await fetch(url, {
    method: 'DELETE',
    headers: {
      'Content-Type': 'application/json',
      'X-WP-Nonce': (window.castconductorScenesAjax || {}).nonce
    },
    credentials: 'same-origin'
  });
  if (!res.ok) {
    throw new Error(`HTTP ${res.status} for ${url}`);
  }
  return res.json();
}

// ─────────────────────────────────────────────────────────────────────────────
// Scenes API functions
// ─────────────────────────────────────────────────────────────────────────────

const API_NS = 'castconductor/v5/';

async function listScenes() {
  const base = (window.castconductorScenesAjax || {}).rest_url || '';
  const resp = await fetchJSON(`${base}${API_NS}scenes`);
  return unwrapApi(resp);
}

async function activateSceneById(id) {
  const base = (window.castconductorScenesAjax || {}).rest_url || '';
  return postJSON(`${base}${API_NS}scenes/${id}/activate`);
}

async function createAndActivateScene(name) {
  const base = (window.castconductorScenesAjax || {}).rest_url || '';
  const created = await postJSON(`${base}${API_NS}scenes`, {
    name,
    description: '',
    rotation_enabled: false,
    rotation_interval: 60,
    branding: {},
    background: {},
    metadata: {}
  });
  const newId = created?.data?.id || created?.id;
  if (newId) {
    await activateSceneById(newId);
  }
  return created;
}

/**
 * Delete a scene with triple confirmation for safety.
 * @param {number} id - Scene ID to delete
 * @param {string} sceneName - Scene name for confirmation prompts
 * @returns {Promise<boolean>} - True if deleted, false if cancelled
 */
async function deleteSceneWithConfirmation(id, sceneName) {
  // Triple confirmation for destructive action
  const confirm1 = window.confirm(`⚠️ DELETE SCENE: "${sceneName}"\n\nThis will permanently delete this scene and all its container assignments.\n\nAre you sure you want to continue?`);
  if (!confirm1) return false;
  
  const confirm2 = window.confirm(`⚠️ SECOND CONFIRMATION\n\nDeleting "${sceneName}" cannot be undone.\n\nClick OK to proceed with deletion.`);
  if (!confirm2) return false;
  
  const typed = window.prompt(`⚠️ FINAL CONFIRMATION\n\nType "DELETE" (all caps) to permanently delete "${sceneName}":`);
  if (typed !== 'DELETE') {
    alert('Deletion cancelled. You must type DELETE exactly.');
    return false;
  }
  
  const base = (window.castconductorScenesAjax || {}).rest_url || '';
  await deleteJSON(`${base}${API_NS}scenes/${id}`);
  return true;
}

function unwrapApi(data) {
  if (data && typeof data === 'object' && Object.prototype.hasOwnProperty.call(data, 'data')) {
    return data.data;
  }
  return data;
}

function parseJSONSafe(v, fallback = null) {
  try { return typeof v === 'string' ? JSON.parse(v) : (v || fallback); } catch { return fallback; }
}

function toRect(row) {
  // Expect row.overrides to potentially include { rect: { x,y,width,height } }
  const overrides = parseJSONSafe(row?.overrides, {});
  const r = overrides?.rect || row?.rect || {};
  // Reasonable fallback if none present
  const w = Math.max(1, r.width ?? 640);
  const h = Math.max(1, r.height ?? 360);
  const x = Math.max(0, Math.min(1280 - w, r.x ?? ((1280 - w) >> 1)));
  const y = Math.max(0, Math.min(720 - h, r.y ?? ((720 - h) >> 1)));
  return { x, y, width: w, height: h };
}

// Build a base rect from a master container.
// V5 GEOMETRY UPDATE: Native 1280x720 pass-through. No scaling.
function toRectFromMaster(containerRow) {
  const w = Number(containerRow?.width || containerRow?.w || 1280);
  const h = Number(containerRow?.height || containerRow?.h || 720);
  const x = Number(containerRow?.x_position || containerRow?.x || 0);
  const y = Number(containerRow?.y_position || containerRow?.y || 0);

  // Always treat as 1280x720 native
  return clampRect({ x, y, width: w, height: h });
}

// Clamp rect to stage bounds with minimum size
function clampRect(r) {
  const minW = 40, minH = 30;
  let { x, y, width: w, height: h } = r;
  w = Math.max(minW, Math.min(1280, w));
  h = Math.max(minH, Math.min(720, h));
  x = Math.max(0, Math.min(1280 - w, x));
  y = Math.max(0, Math.min(720 - h, y));
  return { x, y, width: w, height: h };
}

// ═══════════════════════════════════════════════════════════════════════════════
// SCENE RENDERING (EDIT/LIVE MODE FOUNDATION)
// These functions enable rendering ANY scene, not just the active one
// ═══════════════════════════════════════════════════════════════════════════════

// Track what's currently displayed vs what's active
let currentDisplayMode = 'live'; // 'live' = active scene, 'preview' = editing scene
let previewingSceneId = null;

/**
 * Load a specific scene by ID (not necessarily the active one)
 * Used for preview mode
 */
async function loadSceneById(sceneId) {
  if (!sceneId) return { scene: null, containers: [] };
  
  const base = (window.castconductorScenesAjax || {}).rest_url || '';
  
  try {
    // Fetch scene data and its containers in parallel
    const [sceneResult, overridesResult, mastersResult] = await Promise.all([
      fetchJSON(base + `castconductor/v5/scenes/${sceneId}`).then(unwrapApi).catch(() => null),
      fetchJSON(base + `castconductor/v5/scenes/${sceneId}/containers`).then(unwrapApi).catch(() => []),
      fetchJSON(base + 'castconductor/v5/containers').then(unwrapApi).catch(() => [])
    ]);
    
    if (!sceneResult) return { scene: null, containers: [] };
    
    const scene = {
      id: sceneResult.id || sceneResult.ID,
      name: sceneResult.name,
      background: parseJSONSafe(sceneResult.background, {}),
      branding: parseJSONSafe(sceneResult.branding, {})
    };
    
    // Build containers from overrides + masters (same logic as loadSceneAndContainers)
    const overridesById = new Map();
    (overridesResult || []).forEach(row => {
      const cid = row?.container_id ?? row?.id;
      if (cid) overridesById.set(Number(cid), row);
    });
    
    // CRITICAL FIX: Only include containers that are explicitly assigned to this scene
    const assignedMasters = (mastersResult || []).filter(m => {
      const id = Number(m.container_id || m.id);
      return overridesById.has(id);
    });
    
    const containers = assignedMasters.map(m => {
      const id = Number(m.container_id || m.id);
      const override = overridesById.get(id);
      const rectOverride = override?.overrides?.rect || override?.rect;
      let rect;
      if (rectOverride) {
        rect = clampRect({
          x: Number(rectOverride.x ?? rectOverride.left ?? m.x_position ?? m.x ?? 0),
          y: Number(rectOverride.y ?? rectOverride.top ?? m.y_position ?? m.y ?? 0),
          width: Number(rectOverride.width ?? rectOverride.w ?? m.width ?? m.w ?? 1280),
          height: Number(rectOverride.height ?? rectOverride.h ?? m.height ?? m.h ?? 720)
        });
      } else {
        rect = toRectFromMaster(m);
      }
      // Master containers use 'position' as the key (lower_third, left_half, etc)
      const containerKey = m.position || m.key || '';
      return {
        container_id: id,
        key: containerKey,
        name: m.name || containerKey || '',
        rect,
        enabled: Number(override?.enabled ?? m.enabled ?? 1) === 1,
        layout: parseJSONSafe(m.layout, {}),
        overrides: parseJSONSafe(override?.overrides || {}, {}),
        zones: parseJSONSafe(override?.zones || m.zones, {}),
      };
    });
    
    return { scene, containers };
  } catch (e) {
    console.error('[CC] loadSceneById failed:', e);
    return { scene: null, containers: [] };
  }
}

/**
 * Render a scene on the stage (background, branding, containers)
 * Can be any scene - used for both Live and Preview modes
 */
async function renderSceneOnStage(root, scene, containers, options = {}) {
  const { showPreviewBadge = false, onContainerChange = null, onSelect = null } = options;
  
  // Apply background and branding
  applyBackground(root, scene);
  applyBranding(root, scene);
  
  // Update status label based on mode
  const statusHost = root.querySelector('[data-host="status-scene"]');
  if (statusHost) {
    statusHost.innerHTML = '';
    if (showPreviewBadge && scene?.id) {
      const badge = el('div', { class: 'cc-status-scene preview-mode' }, [
        el('span', { style: { marginRight: '8px', color: '#fbbf24' } }, '👁️ PREVIEW:'),
        el('span', {}, scene.name || `Scene #${scene.id}`)
      ]);
      statusHost.appendChild(badge);
    } else if (scene?.id) {
      const label = el('div', { class: 'cc-status-scene' }, `Active Scene: ${scene.name || ('#' + scene.id)}`);
      statusHost.appendChild(label);
    }
  }
  
  // Render containers - ALWAYS clear and re-render to avoid stale containers
  const containerHost = root.querySelector('[data-host="containers"]');
  if (containerHost) {
    containerHost.innerHTML = ''; // Clear any stale containers first
  }
  
  if (onContainerChange && onSelect) {
    renderContainers(root, containers || [], onContainerChange, onSelect);
  }
  
  // Render previews (immediate = true on initial render)
  await renderContainerPreviews(root, scene, containers || [], true);
}

async function loadSceneAndContainers() {
  const base = (window.castconductorScenesAjax || {}).rest_url || '';
  console.time('[CC PERF] loadSceneAndContainers total');

  // OPTIMIZED: Single call to get active scene (canvas-editor route auto-creates default)
  // This eliminates the 3-endpoint waterfall that was causing 5-10 second delays
  console.time('[CC PERF] getActiveScene');
  let scene = null;
  let sceneId = null;
  
  try {
    // Use canvas-editor route - it has ensure_default_scene logic built in
    const activeFull = unwrapApi(await fetchJSON(base + 'castconductor/v5/canvas-editor/scenes/active'));
    if (activeFull && (activeFull.id || activeFull.ID)) {
      sceneId = activeFull.id || activeFull.ID;
      scene = {
        id: sceneId,
        name: activeFull.name,
        background: parseJSONSafe(activeFull.background, {}),
        branding: parseJSONSafe(activeFull.branding, {})
      };
    }
  } catch (e) {
    // Only fallback if the canvas-editor endpoint truly fails (not 404 which means no scene)
    if (!String(e.message || '').includes('404')) {
      console.warn('[CC] canvas-editor/scenes/active failed, trying fallback:', e.message);
      try {
        const a1 = unwrapApi(await fetchJSON(base + 'castconductor/v5/scenes/active'));
        if (a1 && (a1.id || a1.ID)) {
          sceneId = a1.id || a1.ID;
          scene = {
            id: sceneId,
            name: a1.name,
            background: parseJSONSafe(a1.background, {}),
            branding: parseJSONSafe(a1.branding, {})
          };
        }
      } catch (_) { /* no active scene */ }
    }
  }
  console.timeEnd('[CC PERF] getActiveScene');

  // OPTIMIZED: Parallel fetch of containers and scene overrides
  console.time('[CC PERF] fetchContainersAndOverrides');
  let overridesArr = [];
  let masters = [];
  
  if (sceneId) {
    // Fetch both in parallel instead of sequentially
    const [overridesResult, mastersResult] = await Promise.all([
      fetchJSON(base + `castconductor/v5/scenes/${sceneId}/containers`).then(unwrapApi).catch(() => []),
      fetchJSON(base + 'castconductor/v5/containers').then(unwrapApi).catch(() => [])
    ]);
    overridesArr = overridesResult || [];
    masters = mastersResult || [];
  } else {
    // No scene - just fetch master containers
    masters = unwrapApi(await fetchJSON(base + 'castconductor/v5/containers').catch(() => [])) || [];
  }
  console.timeEnd('[CC PERF] fetchContainersAndOverrides');

  console.log('[CC DEBUG] loadSceneAndContainers - raw overridesArr from API:', JSON.stringify(overridesArr, null, 2));
  const overridesById = new Map();
  (overridesArr || []).forEach(row => {
    const cid = row?.container_id ?? row?.id;
    if (cid) {
      console.log('[CC DEBUG] loadSceneAndContainers - override for container', cid, ':', JSON.stringify(row, null, 2));
      overridesById.set(Number(cid), row);
    }
  });
  
  // CRITICAL FIX: Only include containers that are explicitly assigned to this scene
  // Filter masters to only those with scene assignments (overrides)
  const assignedMasters = (masters || []).filter(m => {
    const id = Number(m.container_id || m.id);
    return overridesById.has(id);
  });
  
  console.log('[CC DEBUG] loadSceneAndContainers - total masters:', (masters || []).length, 'assigned to scene:', assignedMasters.length);
  
  const containers = assignedMasters.map(m => {
    const id = Number(m.container_id || m.id);
    const override = overridesById.get(id);
    console.log('[CC DEBUG] loadSceneAndContainers - container', id, 'override:', override);
    console.log('[CC DEBUG] loadSceneAndContainers - container', id, 'override?.zones:', override?.zones);
    console.log('[CC DEBUG] loadSceneAndContainers - container', id, 'm.zones:', m.zones);
    const rectOverride = override?.overrides?.rect || override?.rect;
    let rect;
    if (rectOverride) {
      rect = clampRect({
        x: Number(rectOverride.x ?? rectOverride.left ?? m.x_position ?? m.x ?? 0),
        y: Number(rectOverride.y ?? rectOverride.top ?? m.y_position ?? m.y ?? 0),
        width: Number(rectOverride.width ?? rectOverride.w ?? m.width ?? m.w ?? 1280),
        height: Number(rectOverride.height ?? rectOverride.h ?? m.height ?? m.h ?? 720)
      });
    } else {
      rect = toRectFromMaster(m);
    }
    const finalZones = parseJSONSafe(override?.zones || m.zones, {});
    console.log('[CC DEBUG] loadSceneAndContainers - container', id, 'finalZones after parseJSONSafe:', finalZones);
    // Master containers use 'position' as the key (lower_third, left_half, etc)
    const containerKey = m.position || m.key || '';
    return {
      container_id: id,
      key: containerKey,
      name: m.name || containerKey || '',
      rect,
      enabled: Number(override?.enabled ?? m.enabled ?? 1) === 1,
      layout: parseJSONSafe(m.layout, {}),
      overrides: parseJSONSafe(override?.overrides || {}, {}),
      zones: finalZones,
    };
  });
  console.timeEnd('[CC PERF] loadSceneAndContainers total');
  return { scene, containers };
}
// Background renderer (scene-level)
function applyBackground(root, scene) {
  const host = root.querySelector('[data-host="background"]');
  if (!host) return;
  host.style.position = 'absolute';
  host.style.top = '0';
  host.style.left = '0';
  host.style.width = '1280px';
  host.style.height = '720px';
  host.innerHTML = '';
  const bg = scene?.background || scene?.background_overrides || {};
  host.style.background = 'none';
  host.style.backgroundImage = 'none';
  host.style.backgroundColor = 'transparent';

  // 1) Layered background (first image layer wins)
  if (Array.isArray(bg.layers)) {
    const layer = bg.layers.find(l => l && (l.kind === 'image' || l.kind === 'static-image' || l.type === 'image'));
    if (layer && layer.image && (layer.image.src || layer.image.url)) {
      const src = layer.image.src || layer.image.url;
      host.style.backgroundImage = `url(${src})`;
      host.style.backgroundSize = (layer.image.fit === 'contain' ? 'contain' : (layer.image.fit === 'fill' ? '100% 100%' : 'cover'));
      host.style.backgroundRepeat = 'no-repeat';
      host.style.backgroundPosition = layer.image.position || 'center';
      return;
    }
  }
  // 2) Legacy object background.image.src
  if (bg?.image && (bg.image.src || bg.image.url)) {
    const src = bg.image.src || bg.image.url;
    host.style.backgroundImage = `url(${src})`;
    host.style.backgroundSize = (bg.image.fit === 'contain' ? 'contain' : (bg.image.fit === 'fill' ? '100% 100%' : 'cover'));
    host.style.backgroundRepeat = 'no-repeat';
    host.style.backgroundPosition = bg.image.position || 'center';
    return;
  }
  // 3) Simple list of sources
  if (bg?.type === 'image' && Array.isArray(bg.sources) && bg.sources.length) {
    host.style.backgroundImage = `url(${bg.sources[0]})`;
    host.style.backgroundSize = 'cover';
    host.style.backgroundRepeat = 'no-repeat';
    host.style.backgroundPosition = 'center';
    return;
  }
  // 4) Color fallback
  if (bg?.color) {
    host.style.backgroundImage = 'none';
    host.style.backgroundColor = bg.color;
  }
}

// Branding overlay (scene-level)
// 49-zone logo position mapping (1280x720 canvas)
// Grid: 7 columns x 7 rows = 49 zones
// Zone 25 (row 4, col 4) = TRUE CENTER
const LOGO_POSITION_MAP = {
  // Row 1 (top)
  'zone-1':  { x: 160,  y: 90  },   // Col 1, Row 1
  'zone-2':  { x: 320,  y: 90  },   // Col 2, Row 1
  'zone-3':  { x: 480,  y: 90  },   // Col 3, Row 1
  'zone-4':  { x: 640,  y: 90  },   // Col 4, Row 1 (top center)
  'zone-5':  { x: 800,  y: 90  },   // Col 5, Row 1
  'zone-6':  { x: 960,  y: 90  },   // Col 6, Row 1
  'zone-7':  { x: 1120, y: 90  },   // Col 7, Row 1
  // Row 2
  'zone-8':  { x: 160,  y: 180 },   // Col 1, Row 2
  'zone-9':  { x: 320,  y: 180 },   // Col 2, Row 2
  'zone-10': { x: 480,  y: 180 },   // Col 3, Row 2
  'zone-11': { x: 640,  y: 180 },   // Col 4, Row 2
  'zone-12': { x: 800,  y: 180 },   // Col 5, Row 2
  'zone-13': { x: 960,  y: 180 },   // Col 6, Row 2
  'zone-14': { x: 1120, y: 180 },   // Col 7, Row 2
  // Row 3
  'zone-15': { x: 160,  y: 270 },   // Col 1, Row 3
  'zone-16': { x: 320,  y: 270 },   // Col 2, Row 3
  'zone-17': { x: 480,  y: 270 },   // Col 3, Row 3
  'zone-18': { x: 640,  y: 270 },   // Col 4, Row 3
  'zone-19': { x: 800,  y: 270 },   // Col 5, Row 3
  'zone-20': { x: 960,  y: 270 },   // Col 6, Row 3
  'zone-21': { x: 1120, y: 270 },   // Col 7, Row 3
  // Row 4 (center row - zone-25 is TRUE CENTER)
  'zone-22': { x: 160,  y: 360 },   // Col 1, Row 4
  'zone-23': { x: 320,  y: 360 },   // Col 2, Row 4
  'zone-24': { x: 480,  y: 360 },   // Col 3, Row 4
  'zone-25': { x: 640,  y: 360 },   // Col 4, Row 4 - TRUE CENTER
  'zone-26': { x: 800,  y: 360 },   // Col 5, Row 4
  'zone-27': { x: 960,  y: 360 },   // Col 6, Row 4
  'zone-28': { x: 1120, y: 360 },   // Col 7, Row 4
  // Row 5
  'zone-29': { x: 160,  y: 450 },   // Col 1, Row 5
  'zone-30': { x: 320,  y: 450 },   // Col 2, Row 5
  'zone-31': { x: 480,  y: 450 },   // Col 3, Row 5
  'zone-32': { x: 640,  y: 450 },   // Col 4, Row 5
  'zone-33': { x: 800,  y: 450 },   // Col 5, Row 5
  'zone-34': { x: 960,  y: 450 },   // Col 6, Row 5
  'zone-35': { x: 1120, y: 450 },   // Col 7, Row 5
  // Row 6
  'zone-36': { x: 160,  y: 540 },   // Col 1, Row 6
  'zone-37': { x: 320,  y: 540 },   // Col 2, Row 6
  'zone-38': { x: 480,  y: 540 },   // Col 3, Row 6
  'zone-39': { x: 640,  y: 540 },   // Col 4, Row 6
  'zone-40': { x: 800,  y: 540 },   // Col 5, Row 6
  'zone-41': { x: 960,  y: 540 },   // Col 6, Row 6
  'zone-42': { x: 1120, y: 540 },   // Col 7, Row 6
  // Row 7 (bottom)
  'zone-43': { x: 160,  y: 630 },   // Col 1, Row 7
  'zone-44': { x: 320,  y: 630 },   // Col 2, Row 7
  'zone-45': { x: 480,  y: 630 },   // Col 3, Row 7
  'zone-46': { x: 640,  y: 630 },   // Col 4, Row 7 (bottom center)
  'zone-47': { x: 800,  y: 630 },   // Col 5, Row 7
  'zone-48': { x: 960,  y: 630 },   // Col 6, Row 7
  'zone-49': { x: 1120, y: 630 },   // Col 7, Row 7
  // Semantic aliases
  'center':        { x: 640,  y: 360 },   // = zone-25
  'top-center':    { x: 640,  y: 90  },   // = zone-4
  'bottom-center': { x: 640,  y: 630 },   // = zone-46
  'left-center':   { x: 160,  y: 360 },   // = zone-22
  'right-center':  { x: 1120, y: 360 },   // = zone-28
  // Legacy 5x5 compatibility aliases (map to nearest 7x7 zone)
  'zone-13-legacy': { x: 640,  y: 360 },  // Old center → new zone-25
};

function applyBranding(root, scene) {
  const host = root.querySelector('[data-host="branding"]');
  if (!host) return;
  host.style.position = 'absolute';
  host.style.top = '0';
  host.style.left = '0';
  host.style.width = '1280px';
  host.style.height = '720px';
  host.innerHTML = '';
  const b = scene?.branding || scene?.branding_overrides || {};
  let src = null;
  if (b.animated_center_logo && (b.animated_center_logo.url || b.animated_center_logo.src)) src = b.animated_center_logo.url || b.animated_center_logo.src;
  if (!src) {
    if (typeof b.logo === 'string') src = b.logo;
    else if (b.logo && (b.logo.src || b.logo.url)) src = b.logo.src || b.logo.url;
  }
  if (!src && b.center && (b.center.logo?.src || b.center.logo?.url)) src = b.center.logo.src || b.center.logo.url;
  if (src) {
    const scale = b.logo_scale ?? 70; // Default 70%
    const position = b.logo_position || 'center';
    const pos = LOGO_POSITION_MAP[position] || LOGO_POSITION_MAP['center'];
    const isAnimated = position === 'center';
    
    const img = el('img', { 
      src, 
      alt: 'branding', 
      style: { 
        position: 'absolute', 
        left: `${pos.x}px`, 
        top: `${pos.y}px`, 
        transform: 'translate(-50%, -50%)', 
        maxWidth: `${scale}%`, 
        height: 'auto'
      } 
    });
    
    // Add floating animation class for center position
    if (isAnimated) {
      img.classList.add('cc-logo-animated');
    }
    
    host.appendChild(img);
  }
}

// Container drag/resize interactions
function attachInteractions(root, box, onChangeRect, onSelect) {
  const id = parseInt(box.getAttribute('data-id'), 10);
  let drag = null;
  const setActive = () => {
    const host = root.querySelector('[data-host="containers"]');
    host?.querySelectorAll('.cc-container-box.cc-active').forEach(elm => { if (elm !== box) elm.classList.remove('cc-active'); });
    box.classList.add('cc-active');
    if (typeof onSelect === 'function') onSelect(id);
  };
  const onMouseMove = (e) => {
    if (!drag) return;
    e.preventDefault();
    if (drag.mode === 'move') {
      const nx = drag.startRect.x + (e.clientX - drag.startX);
      const ny = drag.startRect.y + (e.clientY - drag.startY);
      const r = clampRect({ x: nx, y: ny, width: drag.startRect.width, height: drag.startRect.height });
      applyRect(box, r);
      drag.current = r;
    } else if (drag.mode.startsWith('resize')) {
      const dx = (e.clientX - drag.startX);
      const dy = (e.clientY - drag.startY);
      let { x, y, width: w, height: h } = drag.startRect;
      if (drag.mode === 'resize-nw') { x += dx; y += dy; w -= dx; h -= dy; }
      if (drag.mode === 'resize-ne') { y += dy; w += dx; h -= dy; }
      if (drag.mode === 'resize-sw') { x += dx; w -= dx; h += dy; }
      if (drag.mode === 'resize-se') { w += dx; h += dy; }
      const r = clampRect({ x, y, width: w, height: h });
      applyRect(box, r);
      drag.current = r;
    }
  };
  const onMouseUp = async () => {
    if (!drag) return;
    document.removeEventListener('mousemove', onMouseMove);
    document.removeEventListener('mouseup', onMouseUp);
    const finalRect = drag.current;
    drag = null;
    if (finalRect && typeof onChangeRect === 'function') {
      try {
        setStageStatus(root, 'saving', 'Saving…');
        await onChangeRect(id, finalRect);
        setStageStatus(root, 'saved', 'Saved');
        setTimeout(() => setStageStatus(root, 'clear'), 900);
      } catch (e) {
        console.error('[ScenesStage] persist failed', e);
        setStageStatus(root, 'error', 'Save failed');
      }
    }
  };
  box.addEventListener('mousedown', (e) => {
    const target = e.target;
    const rect = getRectFromStyle(box);
    if (target instanceof HTMLElement && target.dataset.resize) {
      drag = { mode: 'resize-' + target.dataset.resize, startX: e.clientX, startY: e.clientY, startRect: rect, current: rect };
    } else {
      drag = { mode: 'move', startX: e.clientX, startY: e.clientY, startRect: rect, current: rect };
    }
    setActive();
    document.addEventListener('mousemove', onMouseMove);
    document.addEventListener('mouseup', onMouseUp);
  });
}

function getRectFromStyle(box) {
  const x = parseInt(box.style.left || '0', 10) || 0;
  const y = parseInt(box.style.top || '0', 10) || 0;
  const w = parseInt(box.style.width || '0', 10) || 0;
  const h = parseInt(box.style.height || '0', 10) || 0;
  return { x, y, width: w, height: h };
}

function applyRect(box, r) {
  box.style.left = r.x + 'px';
  box.style.top = r.y + 'px';
  box.style.width = r.width + 'px';
  box.style.height = r.height + 'px';
}

async function persistRect(sceneId, containerId, rect) {
  const base = (window.castconductorScenesAjax || {}).rest_url || '';
  const url = base + `castconductor/v5/scenes/${sceneId}/containers`;
  // Deprecated in favor of persistContainer to avoid wiping zones
  const payload = { containers: [{ container_id: containerId, overrides: { rect }, zones: {} }] };
  const res = await fetch(url, {
    method: 'PUT',
    headers: {
      'Content-Type': 'application/json',
      'X-WP-Nonce': (window.castconductorScenesAjax || {}).nonce
    },
    body: JSON.stringify(payload)
  });
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return res.json();
}

async function persistContainer(sceneId, container) {
  const base = (window.castconductorScenesAjax || {}).rest_url || '';
  const url = base + `castconductor/v5/scenes/${sceneId}/containers`;
  const overrides = { ...(container.overrides || {}), rect: container.rect };
  const zones = container.zones || {};
  console.log('[CC DEBUG] persistContainer - container.zones before save:', JSON.stringify(zones, null, 2));
  const payload = { containers: [{ container_id: container.container_id, overrides, zones, enabled: container.enabled ? 1 : 0 }] };
  console.log('[CC DEBUG] persistContainer - full payload:', JSON.stringify(payload, null, 2));
  const res = await fetch(url, {
    method: 'PUT',
    headers: {
      'Content-Type': 'application/json',
      'X-WP-Nonce': (window.castconductorScenesAjax || {}).nonce
    },
    body: JSON.stringify(payload)
  });
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  const result = await res.json();
  console.log('[CC DEBUG] persistContainer - API response:', JSON.stringify(result, null, 2));
  return result;
}

async function listBlocks() {
  const base = (window.castconductorScenesAjax || {}).rest_url || '';
  const data = await fetchJSON(base + 'castconductor/v5/content-blocks?per_page=1000&page=1');
  // normalize list
  const rows = Array.isArray(data) ? data : (data?.data || []);
  return rows.map(r => ({ id: r.id || r.ID || r.block_id || 0, name: r.name || r.title || `Block #${r.id}` }));
}

function renderAssignmentsPanel(root, scene, containers, selectedId) {
  const host = root.querySelector('[data-host="assignments"]');
  if (!host) return;
  host.innerHTML = '';
  const wrap = el('div', { class: 'cc-assignments-panel' });
  if (!scene?.id) {
    wrap.appendChild(el('div', { class: 'cc-assignments-hint' }, 'No active scene. Create or activate a scene above.'));
    host.appendChild(wrap);
    return;
  }
  const container = containers.find(c => c.container_id === selectedId) || null;
  if (!container) {
    wrap.appendChild(el('div', { class: 'cc-assignments-hint' }, 'Select a container to assign content blocks.'));
    host.appendChild(wrap);
    return;
  }

  const title = el('div', { class: 'cc-assignments-title' }, `Assignments for ${container.key || container.name || ('Container ' + container.container_id)}`);
  const list = el('ul', { class: 'cc-assignments-list' });

  // Zones support: derive known zones from override layout first, then master layout
  const knownZones = (() => {
    const z = container?.overrides?.layout?.zones || container?.layout?.zones;
    if (Array.isArray(z)) return z.map(zz => ({ id: String(zz.id || zz.key || zz.name), name: zz.name || zz.id || zz.key, rect: zz.rect || null }));
    return [];
  })();
  // Normalize existing assignments into a map by zone
  console.log('[CC DEBUG] renderAssignmentsPanel - container.zones:', JSON.stringify(container.zones, null, 2));
  console.log('[CC DEBUG] renderAssignmentsPanel - knownZones:', knownZones);
  const assignmentsByZone = new Map();
  if (knownZones.length) {
    knownZones.forEach(z => assignmentsByZone.set(String(z.id), []));
    // New structure: zones = { zoneId: { assignments: [...] } }
    const zobj = container.zones && typeof container.zones === 'object' ? container.zones : {};
    console.log('[CC DEBUG] renderAssignmentsPanel - zobj:', zobj);
    console.log('[CC DEBUG] renderAssignmentsPanel - zobj keys:', Object.keys(zobj));
    Object.keys(zobj || {}).forEach(k => {
      const key = String(k);
      const arr = Array.isArray(zobj[key]?.assignments) ? zobj[key].assignments : [];
      console.log('[CC DEBUG] renderAssignmentsPanel - zone', key, 'assignments:', arr);
      // Normalize only to known zone keys; if unknown, create a bucket so we don't lose data
      const targetKey = assignmentsByZone.has(key) ? key : key;
      assignmentsByZone.set(String(targetKey), arr.map(a => ({ block_id: a.block_id, name: a.name || '', weight: Number(a.weight || a.rotation_percentage || 0), interval: Number(a.interval || 0) })));
    });
    // Legacy flat list
    if (!assignmentsByZone.size && Array.isArray(container.zones?.assignments)) {
      assignmentsByZone.set(String(knownZones[0].id), container.zones.assignments.map(a => ({ block_id: a.block_id, weight: Number(a.weight || 0), interval: Number(a.interval || 0) })));
    }
  }
  // Fallback: single implicit zone "default"
  if (!assignmentsByZone.size) {
    const key = 'default';
    const arr = Array.isArray(container.zones?.assignments) ? container.zones.assignments : [];
    assignmentsByZone.set(key, arr.map(a => ({ block_id: a.block_id, weight: Number(a.weight || 0), interval: Number(a.interval || 0) })));
  }
  console.log('[CC DEBUG] renderAssignmentsPanel - final assignmentsByZone:', Array.from(assignmentsByZone.entries()));

  // UI: zone selector if multiple zones
  const controlsTop = el('div', { style: { display: 'flex', gap: '8px', alignItems: 'center', marginBottom: '8px' } });
  const zoneSelect = el('select', { class: 'cc-zone-select' });
  const zoneKeys = Array.from(assignmentsByZone.keys());
  zoneKeys.forEach(k => zoneSelect.appendChild(el('option', { value: k }, String((knownZones.find(z => String(z.id) === k)?.name) || k))));
  let activeZone = zoneKeys[0];
  controlsTop.appendChild(el('label', {}, ['Zone: ', zoneSelect]));
  const zoneMaxBtn = el('button', { class: 'button', title: 'Set this zone to 100% (others 0%)' }, 'Set Zone 100%');
  controlsTop.appendChild(zoneMaxBtn);
  // Zone management controls (add/remove/presets)
  const addZoneBtn = el('button', { class: 'button', title: 'Add a new zone to this container' }, 'Add Zone');
  const removeZoneBtn = el('button', { class: 'button', title: 'Remove current zone' }, 'Remove Zone');
  const presetWrap = el('span', { style: { display: 'inline-flex', gap: '6px', marginLeft: '8px' } }, [
    el('button', { class: 'button', title: 'Halves (Left/Right)', id: 'cc-preset-halves' }, 'Halves'),
    el('button', { class: 'button', title: 'Thirds (Left/Center/Right)', id: 'cc-preset-thirds' }, 'Thirds'),
    el('button', { class: 'button', title: 'Quarters (Q1..Q4)', id: 'cc-preset-quarters' }, 'Quarters')
  ]);
  controlsTop.appendChild(addZoneBtn);
  controlsTop.appendChild(removeZoneBtn);
  controlsTop.appendChild(presetWrap);

  // Helper: draw zone overlays in selected container
  function renderZoneOverlays() {
    const box = root.querySelector(`.cc-container-box[data-id="${container.container_id}"]`);
    const layer = box?.querySelector('.cc-zones-overlay');
    if (!box || !layer) return;
    layer.innerHTML = '';
    const rect = getRectFromStyle(box);
    const zdefs = (container.overrides?.layout?.zones || container.layout?.zones || []).map(z => ({ id: String(z.id || z.key || z.name), name: z.name || z.id || z.key, rect: z.rect }));
    zdefs.forEach(z => {
      const zr = z.rect || { x: 0, y: 0, w: rect.width, h: rect.height };
      const div = el('div', {
        class: 'cc-zone-box', title: z.name || z.id, style: {
          position: 'absolute',
          left: Math.round(zr.x || 0) + 'px',
          top: Math.round(zr.y || 0) + 'px',
          width: Math.max(1, Math.round(zr.w || rect.width)) + 'px',
          height: Math.max(1, Math.round(zr.h || rect.height)) + 'px',
          border: '1px dashed rgba(59,130,246,0.8)',
          background: 'rgba(59,130,246,0.10)'
        }
      });
      layer.appendChild(div);
    });
  }

  const renderList = () => {
    list.innerHTML = '';
    const assignments = assignmentsByZone.get(activeZone) || [];
    let total = 0;
    assignments.forEach(a => { total += Number(a.weight || 0); });
    assignments.forEach((a, idx) => {
      const li = el('li', { class: 'cc-assignments-item' });
      li.setAttribute('draggable', 'true');
      li.style.cursor = 'move';
      const handle = el('span', { class: 'drag-handle', title: 'Drag to reorder', style: { marginRight: '6px', userSelect: 'none' } }, '⋮⋮');
      // Show name with block ID below for debugging
      const nameText = a.name ? a.name : `Block #${a.block_id}`;
      const idText = `#${a.block_id}`;
      const name = el('span', { class: 'name', style: { display: 'flex', flexDirection: 'column', lineHeight: '1.2' } }, [
        el('span', { style: { fontWeight: '500' } }, nameText),
        el('span', { style: { fontSize: '11px', color: '#888' } }, idText)
      ]);
      const weight = el('input', { type: 'number', min: '0', max: '100', step: '1', value: String(a.weight ?? 0), class: 'weight', placeholder: 'Weight %', 'aria-label': 'Weight percent' });
      weight.addEventListener('input', () => {
        a.weight = Math.max(0, Math.min(100, parseInt(weight.value || '0', 10)));
        renderSummary();
      });
      const interval = el('input', { type: 'number', min: '0', step: '1', value: String(a.interval ?? 0), class: 'interval', title: 'Interval (seconds) for this block', placeholder: 'Interval (sec)', 'aria-label': 'Interval seconds' });
      const remove = el('button', { class: 'button small danger', title: 'Remove' }, '✕');
      // Up/Down buttons removed; drag handle is the sole reordering affordance
      remove.addEventListener('click', () => { assignments.splice(idx, 1); renderList(); renderSummary(); });
      // Drag+drop reordering
      li.addEventListener('dragstart', (ev) => { ev.dataTransfer.setData('text/plain', String(idx)); });
      li.addEventListener('dragover', (ev) => { ev.preventDefault(); });
      li.addEventListener('drop', (ev) => { ev.preventDefault(); const from = parseInt(ev.dataTransfer.getData('text/plain'), 10); const to = idx; if (!isNaN(from) && from !== to) { const item = assignments.splice(from, 1)[0]; assignments.splice(to, 0, item); renderList(); renderSummary(); } });

      li.appendChild(handle);
      li.appendChild(name);
      li.appendChild(weight);
      li.appendChild(interval);
      li.appendChild(remove);
      list.appendChild(li);
    });
    renderSummary();
  };

  const summary = el('div', { class: 'cc-assignments-summary' });
  function renderSummary() {
    const assignments = assignmentsByZone.get(activeZone) || [];
    const total = assignments.reduce((s, a) => s + Number(a.weight || 0), 0);
    summary.innerHTML = `Total rotation weight: ${total}%` + (total > 100 ? ' (exceeds 100%)' : '');
    summary.className = 'cc-assignments-summary' + (total > 100 ? ' error' : '');
    saveBtn.disabled = total > 100;
  }

  const controls = el('div', { class: 'cc-assignments-controls' });
  const selectorWrap = el('div', { class: 'cc-assignments-add' });
  const select = el('select', { class: 'cc-assign-select' }, [el('option', { value: '' }, '-- Select content block --')]);
  const weightInput = el('input', { type: 'number', min: '0', max: '100', step: '1', value: '25', class: 'cc-assign-weight', placeholder: 'Weight %', 'aria-label': 'Weight percent' });
  const intervalInput = el('input', { type: 'number', min: '0', step: '1', value: '0', class: 'cc-assign-interval', title: 'Interval (seconds)', placeholder: 'Interval (sec)', 'aria-label': 'Interval seconds' });
  const addBtn = el('button', { class: 'button' }, 'Add');
  selectorWrap.appendChild(select);
  selectorWrap.appendChild(weightInput);
  selectorWrap.appendChild(intervalInput);
  selectorWrap.appendChild(addBtn);
  controls.appendChild(selectorWrap);

  const saveBtn = el('button', { class: 'button primary' }, 'Save Assignments');
  controls.appendChild(saveBtn);

  wrap.appendChild(controlsTop);
  wrap.appendChild(title);
  wrap.appendChild(list);
  wrap.appendChild(summary);
  wrap.appendChild(controls);
  host.appendChild(wrap);

  // Initial zone overlays
  renderZoneOverlays();

  // Load blocks into select
  (async () => {
    try {
      setStageStatus(root, 'saving', 'Loading blocks…');
      const blocks = await listBlocks();
      blocks.forEach(b => select.appendChild(el('option', { value: String(b.id) }, `${b.name} (#${b.id})`)));
      setStageStatus(root, 'clear');
    } catch (e) {
      console.error(e);
      setStageStatus(root, 'error', 'Failed to load blocks');
    }
  })();

  addBtn.addEventListener('click', () => {
    const id = parseInt(select.value, 10);
    const nameOpt = select.options[select.selectedIndex]?.textContent || '';
    const wt = Math.max(0, Math.min(100, parseInt(weightInput.value || '0', 10)));
    const iv = Math.max(0, parseInt(intervalInput.value || '0', 10));
    if (!id) return;
    const arr = assignmentsByZone.get(activeZone) || [];
    arr.push({ block_id: id, name: nameOpt.replace(/\s*\(#\d+\)$/, ''), weight: wt, interval: iv });
    assignmentsByZone.set(activeZone, arr);
    renderList(); renderSummary();
  });

  saveBtn.addEventListener('click', async () => {
    try {
      setStageStatus(root, 'saving', 'Saving assignments…');
      // Build zones payload (preserve zone keys exactly as shown in UI)
      const zonesPayload = {};
      for (const zKey of assignmentsByZone.keys()) {
        const arr = assignmentsByZone.get(zKey) || [];
        zonesPayload[zKey] = { assignments: arr.map((a, i) => ({ block_id: a.block_id, name: a.name || '', weight: Number(a.weight || 0), interval: Number(a.interval || 0), order: i + 1 })) };
      }
      console.log('[CC DEBUG] saveBtn - zonesPayload:', zonesPayload);
      container.zones = zonesPayload;
      console.log('[CC DEBUG] saveBtn - container.zones after update:', container.zones);
      // Persist layout (preserve any rects we computed)
      const current = (container.overrides?.layout?.zones || container.layout?.zones || []);
      const order = Array.from(assignmentsByZone.keys());
      const layout = {
        zones: order.map((k, i) => {
          const match = current.find(z => String(z.id || z.key || z.name) === String(k));
          return { id: k, name: match?.name || k, order: i + 1, rect: match?.rect };
        })
      };
      container.overrides = { ...(container.overrides || {}), layout };
      console.log('[CC DEBUG] saveBtn - about to persist...');
      await persistContainer(scene.id, container);
      console.log('[CC DEBUG] saveBtn - persist complete, refreshing UI...');
      setStageStatus(root, 'saved', 'Assignments saved');
      setTimeout(() => setStageStatus(root, 'clear'), 900);

      // Refresh assignments panel to show the saved assignments in the UI
      renderAssignmentsPanel(root, scene, containers, selectedId);

      // Refresh live previews after saving
      try {
        await renderContainerPreviews(root, scene, containers);
        console.log('[CC DEBUG] saveBtn - previews refreshed successfully');
      } catch (previewErr) {
        console.error('[CC ERROR] Failed to refresh previews after save:', previewErr);
        // Don't fail the whole save if preview refresh fails
      }
    } catch (e) {
      console.error('[CC ERROR] Save failed:', e);
      setStageStatus(root, 'error', 'Failed to save assignments');
    }
  });

  zoneSelect.addEventListener('change', () => { activeZone = zoneSelect.value; renderList(); renderSummary(); });
  zoneMaxBtn.addEventListener('click', () => {
    const arr = assignmentsByZone.get(activeZone) || [];
    if (!arr.length) return;
    arr.forEach((a, i) => { a.weight = (i === 0 ? 100 : 0); });
    renderList(); renderSummary();
  });

  // Add/remove zone handlers
  addZoneBtn.addEventListener('click', () => {
    let i = 1; let key;
    do { key = 'zone_' + i++; } while (assignmentsByZone.has(key));
    assignmentsByZone.set(key, []);
    const opt = el('option', { value: key }, key);
    zoneSelect.appendChild(opt);
    zoneSelect.value = key;
    activeZone = key;
    renderList(); renderSummary();
    const zlist = (container.overrides?.layout?.zones || container.layout?.zones || []).slice();
    zlist.push({ id: key, name: key, rect: { x: 0, y: 0, w: container.rect.width, h: container.rect.height } });
    container.overrides = { ...(container.overrides || {}), layout: { zones: zlist } };
    renderZoneOverlays();
  });
  removeZoneBtn.addEventListener('click', () => {
    const key = activeZone;
    if (!key) return;
    assignmentsByZone.delete(key);
    const opt = Array.from(zoneSelect.options).find(o => o.value === key);
    if (opt) opt.remove();
    activeZone = zoneSelect.options[0]?.value || '';
    renderList(); renderSummary();
    const zlist = (container.overrides?.layout?.zones || container.layout?.zones || []).filter(z => String(z.id) !== String(key));
    container.overrides = { ...(container.overrides || {}), layout: { zones: zlist } };
    renderZoneOverlays();
  });
  // Presets: replace zones with standard sets
  const applyPreset = (keys) => {
    assignmentsByZone.clear();
    keys.forEach(k => assignmentsByZone.set(k, []));
    zoneSelect.innerHTML = '';
    keys.forEach(k => zoneSelect.appendChild(el('option', { value: k }, k.replace(/_/g, ' '))));
    activeZone = keys[0];
    // Compute rects in container space
    const W = container.rect.width, H = container.rect.height;
    let zones = [];
    if (keys.length === 2) {
      zones = [
        { id: keys[0], name: keys[0], rect: { x: 0, y: 0, w: Math.round(W / 2), h: H } },
        { id: keys[1], name: keys[1], rect: { x: Math.round(W / 2), y: 0, w: W - Math.round(W / 2), h: H } }
      ];
    } else if (keys.length === 3) {
      const w3 = Math.round(W / 3);
      zones = [
        { id: keys[0], name: keys[0], rect: { x: 0, y: 0, w: w3, h: H } },
        { id: keys[1], name: keys[1], rect: { x: w3, y: 0, w: w3, h: H } },
        { id: keys[2], name: keys[2], rect: { x: 2 * w3, y: 0, w: W - 2 * w3, h: H } }
      ];
    } else if (keys.length === 4) {
      const w2 = Math.round(W / 2), h2 = Math.round(H / 2);
      zones = [
        { id: keys[0], name: keys[0], rect: { x: 0, y: 0, w: w2, h: h2 } },
        { id: keys[1], name: keys[1], rect: { x: w2, y: 0, w: W - w2, h: h2 } },
        { id: keys[2], name: keys[2], rect: { x: 0, y: h2, w: w2, h: H - h2 } },
        { id: keys[3], name: keys[3], rect: { x: w2, y: h2, w: W - w2, h: H - h2 } }
      ];
    } else {
      zones = keys.map(k => ({ id: k, name: k, rect: { x: 0, y: 0, w: W, h: H } }));
    }
    container.overrides = { ...(container.overrides || {}), layout: { zones } };
    renderList(); renderSummary(); renderZoneOverlays();
  };
  presetWrap.querySelector('#cc-preset-halves')?.addEventListener('click', () => applyPreset(['left', 'right']));
  presetWrap.querySelector('#cc-preset-thirds')?.addEventListener('click', () => applyPreset(['left_third', 'center_third', 'right_third']));
  presetWrap.querySelector('#cc-preset-quarters')?.addEventListener('click', () => applyPreset(['q1', 'q2', 'q3', 'q4']));

  renderList();
}

// Helper to apply config styles via JS as a fallback/enforcement
function applyConfigStyles(wrapper, config) {
  if (!wrapper || !config) return;

  // Check for Server-Side Layer Rendering (SSLR)
  // If the server has already rendered layers with inline styles, we should NOT interfere.
  // The presence of .cc-layers-renderer indicates SSLR is active.
  if (wrapper.querySelector('.cc-layers-renderer')) {
    console.log('[CC DEBUG] applyConfigStyles - SSLR detected, skipping client-side style enforcement to avoid conflicts.');
    return;
  }

  console.log('[CC DEBUG] applyConfigStyles - config:', JSON.stringify(config));

  // Layout / Padding
  const pad = config.layout?.padding || config.padding || {};
  if (pad.top !== undefined) wrapper.style.paddingTop = pad.top + 'px';
  if (pad.right !== undefined) wrapper.style.paddingRight = pad.right + 'px';
  if (pad.bottom !== undefined) wrapper.style.paddingBottom = pad.bottom + 'px';
  if (pad.left !== undefined) wrapper.style.paddingLeft = pad.left + 'px';

  // Background: apply overlay/gradient/color if configured (editor-only visual parity)
  // This ensures background renders even if server CSS is overridden or fails
  const bg = config.background || {}; 
  const ov = config.overlay || {};
  const layers = Array.isArray(bg.layers) ? bg.layers : [];
  const bgImages = [];
  let hasOverlayLayer = false; // Track if overlay layer is present to avoid double-darkening
  
  layers.forEach(layer => {
    if (!layer || layer.enabled === false) return;
    if (layer.kind === 'overlay' && ov && ov.color) {
      hasOverlayLayer = true;
      // Fix: Use actual overlay color, not hardcoded black
      const col = String(ov.color);
      const alpha = (ov.opacity != null ? Number(ov.opacity) : 0);
      
      // Convert hex to rgba if needed
      let rgba = col;
      if (/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(col)) {
          const hex = col.replace('#', '');
          const r = hex.length === 3 ? parseInt(hex[0] + hex[0], 16) : parseInt(hex.substring(0, 2), 16);
          const g = hex.length === 3 ? parseInt(hex[1] + hex[1], 16) : parseInt(hex.substring(2, 4), 16);
          const b = hex.length === 3 ? parseInt(hex[2] + hex[2], 16) : parseInt(hex.substring(4, 6), 16);
          rgba = `rgba(${r}, ${g}, ${b}, ${Math.max(0, Math.min(1, alpha))})`;
      }
      
      bgImages.push(`linear-gradient(0deg, ${rgba}, ${rgba})`);
    } else if (layer.kind === 'gradient' && Array.isArray(bg.gradient_colors) && bg.gradient_colors.length >= 2) {
      const dir = bg.gradient_direction || '135deg';
      bgImages.push(`linear-gradient(${dir}, ${bg.gradient_colors.join(', ')})`);
    } else if (layer.kind === 'image' && layer.image && layer.image.src) {
       bgImages.push(`url("${layer.image.src}")`);
    }
  });
  
  if (bgImages.length) {
      // IMPORTANT: When overlay layer is present, do NOT add base color - causes double-darkening!
      // The overlay IS the opacity control - it renders over the scene branding/background.
      if (!hasOverlayLayer && bg.color && String(bg.color).toLowerCase() !== 'transparent') {
          const col = String(bg.color);
          const alpha = (bg.opacity != null ? Number(bg.opacity) : 1.0);
          let rgba = col;
          if (/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(col)) {
              const hex = col.replace('#', '');
              const r = hex.length === 3 ? parseInt(hex[0] + hex[0], 16) : parseInt(hex.substring(0, 2), 16);
              const g = hex.length === 3 ? parseInt(hex[1] + hex[1], 16) : parseInt(hex.substring(2, 4), 16);
              const b = hex.length === 3 ? parseInt(hex[2] + hex[2], 16) : parseInt(hex.substring(4, 6), 16);
              rgba = `rgba(${r}, ${g}, ${b}, ${Math.max(0, Math.min(1, alpha))})`;
          }
          bgImages.push(`linear-gradient(0deg, ${rgba}, ${rgba})`);
      }
      wrapper.style.backgroundImage = bgImages.join(',');
      wrapper.style.backgroundSize = 'cover';
      wrapper.style.backgroundPosition = 'center';
  } else if (bg.color && String(bg.color).toLowerCase() !== 'transparent') {
    // Honor optional bg.opacity when present
    const col = String(bg.color);
    const alpha = (bg.opacity != null ? Number(bg.opacity) : null);
    if (alpha != null && /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(col)) {
      const hex = col.replace('#', '');
      const r = hex.length === 3 ? parseInt(hex[0] + hex[0], 16) : parseInt(hex.substring(0, 2), 16);
      const g = hex.length === 3 ? parseInt(hex[1] + hex[1], 16) : parseInt(hex.substring(2, 4), 16);
      const b = hex.length === 3 ? parseInt(hex[2] + hex[2], 16) : parseInt(hex.substring(4, 6), 16);
      wrapper.style.backgroundColor = `rgba(${r}, ${g}, ${b}, ${Math.max(0, Math.min(1, alpha))})`;
    } else {
      wrapper.style.backgroundColor = col;
    }
  }
  if (bg.border_radius) wrapper.style.borderRadius = (bg.border_radius | 0) + 'px';

  // Typography - Apply to wrapper for inheritance
  // Use defaults if missing to ensure parity with server-side defaults
  const typo = config.typography || {};

  // REMOVED defaults to comply with "Do not apply arbitrary styling" directive.
  // If config is missing, we should not invent styles.
  const defaults = {
    color: undefined,
    font_family: undefined,
    font_size: undefined,
    font_weight: undefined,
    line_height: undefined,
    text_align: undefined
  };

  const color = typo.color || defaults.color;
  const fontFamily = typo.font_family || defaults.font_family;
  const fontSize = typo.font_size || defaults.font_size;
  const fontWeight = typo.font_weight || defaults.font_weight;
  const lineHeight = typo.line_height || defaults.line_height;
  const textAlign = typo.text_align || defaults.text_align;

  console.log('[CC DEBUG] applyConfigStyles - resolved typography:', JSON.stringify({ color, fontFamily, fontSize, fontWeight }));

  if (color) wrapper.style.color = color;
  if (fontFamily) wrapper.style.fontFamily = mapFontFamilyToCSS(fontFamily);
  if (fontSize) wrapper.style.fontSize = fontSize + 'px';
  if (fontWeight) wrapper.style.fontWeight = fontWeight;
  if (lineHeight) wrapper.style.lineHeight = lineHeight;
  if (textAlign) wrapper.style.textAlign = textAlign;

  // Typography - Force apply to known text children to override browser defaults or specific CSS
  // This ensures that even if the server CSS fails to load or is overridden, the config styles win.
  // Expanded selectors to catch more variations and use !important
  const textSelectors = [
    'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
    'p', 'span', 'a', 'div[class*="text"]',
    '.cc-track-title', '.cc-track-artist', '.cc-track-album',
    '.track-title', '.track-artist', '.track-album',
    '[class*="title"]', '[class*="artist"]', '[class*="album"]', '[class*="name"]', '[class*="info"]',
    '.text-content', '.ticker-line'
  ];

  textSelectors.forEach(sel => {
    const elements = wrapper.querySelectorAll(sel);
    if (elements.length > 0) {
      elements.forEach(el => {
        if (color) el.style.setProperty('color', color, 'important');
        if (fontFamily) el.style.setProperty('font-family', mapFontFamilyToCSS(fontFamily), 'important');
        if (fontWeight) el.style.setProperty('font-weight', fontWeight, 'important');
        // We intentionally do NOT force font-size on children as they often have relative sizing (em/rem)
        // BUT for the main text container, we might want to?
        // If it's .text-content, maybe?
        if (el.classList.contains('text-content')) {
          if (lineHeight) el.style.setProperty('line-height', lineHeight, 'important');
          if (textAlign) el.style.setProperty('text-align', textAlign, 'important');
        }
      });
    }
  });

  // LAYER-BASED STYLING OVERRIDES (Parity with Content Block Editor)
  // If token layers exist, map their styles to the corresponding legacy elements.
  // This ensures that designs authored via layers (Yellow/48px) are respected in the Scenes Stage.
  if (Array.isArray(config.layers)) {
    config.layers.forEach(layer => {
      if (!layer || layer.kind !== 'token-text' || !layer.style) return;

      const style = layer.style;
      let selector = '';

      // Map tokens to legacy classes
      if ((layer.templateText || '').includes('{{track.artist}}')) {
        selector = '.artist';
      } else if ((layer.templateText || '').includes('{{track.title}}')) {
        selector = '.title';
      } else if ((layer.templateText || '').includes('{{sponsor.title}}')) {
        selector = '.title';
      } else if ((layer.templateText || '').includes('{{sponsor.content}}')) {
        selector = '.content';
      } else if ((layer.templateText || '').includes('{{shoutout.name}}')) {
        selector = '.name';
      } else if ((layer.templateText || '').includes('{{shoutout.message}}')) {
        selector = '.message';
      } else if ((layer.templateText || '').includes('{{shoutout.location}}')) {
        selector = '.location';
      } else if ((layer.templateText || '').includes('{{promo.title}}')) {
        selector = '.title';
      } else if ((layer.templateText || '').includes('{{promo.content}}')) {
        selector = '.content';
      }

      if (selector) {
        const targets = wrapper.querySelectorAll(selector);
        targets.forEach(el => {
          if (style.color) el.style.setProperty('color', style.color, 'important');
          if (style.font_size) el.style.setProperty('font-size', style.font_size + 'px', 'important');
          if (style.font_weight) el.style.setProperty('font-weight', style.font_weight, 'important');
          if (style.font_family) el.style.setProperty('font-family', mapFontFamilyToCSS(style.font_family), 'important');
        });
      }
    });
  }

}

function renderContainers(root, containers, onChangeRect, onSelect) {
  const host = root.querySelector('[data-host="containers"]');
  if (!host) return;
  host.innerHTML = '';
  (containers || []).forEach(c => {
    const rect = c.rect || { x: 0, y: 0, width: 100, height: 100 };
    const box = el('div', {
      class: 'cc-container-box',
      'data-id': c.container_id,
      style: {
        position: 'absolute',
        left: rect.x + 'px',
        top: rect.y + 'px',
        width: rect.width + 'px',
        height: rect.height + 'px',
        zIndex: c.z_index || 10,
        opacity: c.enabled ? '1' : '0.5'
      }
    }, [
      el('div', { class: 'cc-container-border' }, [
        el('div', { class: 'cc-container-label' }, c.name || 'Container'),
        el('div', { class: 'cc-handle nw', 'data-resize': 'nw' }),
        el('div', { class: 'cc-handle ne', 'data-resize': 'ne' }),
        el('div', { class: 'cc-handle sw', 'data-resize': 'sw' }),
        el('div', { class: 'cc-handle se', 'data-resize': 'se' }),
        el('div', { class: 'cc-preview-host' }),
        el('div', { class: 'cc-zones-overlay' })
      ])
    ]);
    host.appendChild(box);
    attachInteractions(root, box, onChangeRect, onSelect);
  });
}

async function boot() {
  console.time('[CC PERF] boot() total');
  const mount = document.getElementById(rootId);
  if (!mount) return;
  mount.innerHTML = '';
  const chrome = stageChrome();
  mount.appendChild(chrome);
  
  // Hook unified Scene Manager button
  const managerBtn = chrome.querySelector('#cc-scene-manager-btn');
  const returnToLiveBtn = chrome.querySelector('#cc-return-to-live-btn');
  
  try {
    console.time('[CC PERF] loadSceneAndContainers');
    const { scene, containers } = await loadSceneAndContainers();
    console.timeEnd('[CC PERF] loadSceneAndContainers');
    let selectedId = null;
    applyBackground(chrome, scene);
    applyBranding(chrome, scene);
    setActiveSceneLabel(chrome, scene);
    
    // Wire up "Return to Live" button in toolbar
    returnToLiveBtn?.addEventListener('click', async () => {
      if (currentDisplayMode !== 'preview') return;
      
      setStageStatus(chrome, 'saving', 'Returning to live scene…');
      currentDisplayMode = 'live';
      previewingSceneId = null;
      returnToLiveBtn.style.display = 'none';
      
      // Re-render the active scene
      await renderSceneOnStage(chrome, scene, containers, { 
        showPreviewBadge: false,
        onContainerChange: async (containerId, rect) => {
          if (!scene?.id) return;
          const c = (containers || []).find(x => x.container_id === containerId);
          if (c) { c.rect = rect; c.overrides = { ...(c.overrides || {}), rect: rect }; }
          await persistContainer(scene.id, c || { container_id: containerId, rect, zones: {} });
          await renderContainerPreviews(chrome, scene, containers || []);
        },
        onSelect: (id) => { selectedId = id; renderAssignmentsPanel(chrome, scene, containers || [], selectedId); }
      });
      setActiveSceneLabel(chrome, scene);
      setStageStatus(chrome, 'saved', 'Returned to live scene');
      setTimeout(() => setStageStatus(chrome, 'clear'), 900);
    });
    
    // Wire up unified Scene Manager modal
    managerBtn?.addEventListener('click', () => showSceneManagerModal(chrome, scene, containers));
    
    const onSelect = (id) => { selectedId = id; renderAssignmentsPanel(chrome, scene, containers || [], selectedId); };
    renderContainers(chrome, containers || [], async (containerId, rect) => {
      if (!scene?.id) return;
      const c = (containers || []).find(x => x.container_id === containerId);
      if (c) { c.rect = rect; c.overrides = { ...(c.overrides || {}), rect: rect }; }
      await persistContainer(scene.id, c || { container_id: containerId, rect, zones: {} });
      // refresh previews if enabled
      await renderContainerPreviews(chrome, scene, containers || []);
    }, onSelect);
    renderAssignmentsPanel(chrome, scene, containers || [], selectedId);
    // initial previews (always-on)
    console.time('[CC PERF] renderContainerPreviews (initial)');
    await renderContainerPreviews(chrome, scene, containers || []);
    console.timeEnd('[CC PERF] renderContainerPreviews (initial)');
    console.timeEnd('[CC PERF] boot() total');
    if (!scene) {
      // Empty state: no active scene
      setStageStatus(chrome, 'info', 'No active scene found.');
      renderEmptyState(chrome);
    }
  } catch (err) {
    console.timeEnd('[CC PERF] boot() total');
    if (String(err?.message || '').includes('404')) {
      setStageStatus(chrome, 'info', 'No active scene found.');
      renderEmptyState(chrome);
    } else {
      mount.appendChild(el('pre', { class: 'cc-error' }, `Failed to load scene: ${err.message}`));
    }
  }
}

document.addEventListener('DOMContentLoaded', boot);

// Live Preview toggle + rendering
let previewTimers = new Map();
let previewDebounceTimer = null;
function clearPreviews() {
  previewTimers.forEach(id => clearInterval(id));
  previewTimers.clear();
  if (previewDebounceTimer) {
    clearTimeout(previewDebounceTimer);
    previewDebounceTimer = null;
  }
}

async function renderContainerPreviews(root, scene, containers, immediate = false) {
  // Debounce rapid re-renders (unless immediate is requested)
  if (!immediate && previewDebounceTimer) {
    clearTimeout(previewDebounceTimer);
  }
  
  return new Promise((resolve) => {
    const doRender = async () => {
      // Always-on previews: clear and (re)render
      clearPreviews();
      if (!Array.isArray(containers) || !containers.length) {
        resolve();
        return;
      }
      // Parallelize preview fetches for faster rendering (instead of sequential await)
      // Each container's preview is independent, so we can fetch them concurrently
      console.time('[CC PERF] renderContainerPreviews Promise.all');
      console.log('[CC PERF] Starting parallel preview requests for', containers.length, 'containers');
      await Promise.all(containers.map(c => injectContainerPreview(root, c)));
      console.timeEnd('[CC PERF] renderContainerPreviews Promise.all');
      resolve();
    };
    
    if (immediate) {
      doRender();
    } else {
      previewDebounceTimer = setTimeout(doRender, 150); // 150ms debounce
    }
  });
}
function clearInjectedPreviews(root) {
  root.querySelectorAll('.cc-container-box .cc-preview-host').forEach(n => n.remove());
}
async function injectContainerPreview(root, container) {
  try {
    const box = root.querySelector(`.cc-container-box[data-id="${container.container_id}"]`);
    if (!box) return;
    // Determine zone host: if zones defined, target the first active zone; else full container
    const zdefs = (container.overrides?.layout?.zones || container.layout?.zones || []);
    let host = box.querySelector('.cc-preview-host');
    if (!host) {
      const rect = getRectFromStyle(box);
      host = el('div', { class: 'cc-preview-host', style: { position: 'absolute', left: '0', top: '0', width: rect.width + 'px', height: rect.height + 'px', overflow: 'visible', background: 'transparent' } });
      box.appendChild(host);
    } else {
      const rect = getRectFromStyle(box);
      host.style.width = rect.width + 'px';
      host.style.height = rect.height + 'px';
    }

    // MULTI-ZONE SUPPORT: If container has multiple zones with assignments, render ALL of them
    const zonesObj = container.zones && typeof container.zones === 'object' ? container.zones : {};
    const containerRect = getRectFromStyle(box);
    
    if (Array.isArray(zdefs) && zdefs.length > 1) {
      // Create zone sub-hosts
      const existing = new Map();
      host.querySelectorAll('.cc-zone-host').forEach(n => { if (n.dataset.zoneId) existing.set(n.dataset.zoneId, n); });
      
      zdefs.forEach(z => {
        const zid = String(z.id ?? z.key ?? z.name);
        const zr = z.rect || {};
        const zx = Number(zr.x ?? zr.left ?? 0);
        const zy = Number(zr.y ?? zr.top ?? 0);
        const zw = Number((zr.w ?? zr.width ?? containerRect.width));
        const zH = Number((zr.h ?? zr.height ?? containerRect.height));
        let zoneHost = existing.get(zid);
        if (!zoneHost) {
          zoneHost = el('div', { class: 'cc-zone-host', 'data-zone-id': zid, style: { position: 'absolute', overflow: 'hidden', background: 'transparent' } });
          host.appendChild(zoneHost);
        }
        zoneHost.style.left = Math.round(zx) + 'px';
        zoneHost.style.top = Math.round(zy) + 'px';
        zoneHost.style.width = Math.max(1, Math.round(zw)) + 'px';
        zoneHost.style.height = Math.max(1, Math.round(zH)) + 'px';
      });

      // Render ALL zones with assignments in parallel
      const zonePromises = zdefs.map(async (zdef) => {
        const zoneKey = String(zdef.id ?? zdef.key ?? zdef.name);
        const zoneAssignments = zonesObj[zoneKey]?.assignments || [];
        if (!zoneAssignments.length) {
          console.log('[CC DEBUG] Multi-zone: zone', zoneKey, 'has no assignments, skipping');
          return;
        }
        
        const zoneHost = host.querySelector(`.cc-zone-host[data-zone-id="${zoneKey}"]`);
        if (!zoneHost) {
          console.log('[CC DEBUG] Multi-zone: zone host not found for', zoneKey);
          return;
        }
        
        // Get zone dimensions
        const zr = zdef.rect || {};
        let frameWidth = Math.max(1, Math.round(Number(zr.w ?? zr.width ?? containerRect.width)));
        let frameHeight = Math.max(1, Math.round(Number(zr.h ?? zr.height ?? containerRect.height)));
        const zx = Number(zr.x ?? zr.left ?? 0);
        const zy = Number(zr.y ?? zr.top ?? 0);
        const frameX = containerRect.x + zx;
        const frameY = containerRect.y + zy;
        
        // Pick block for this zone
        const mappedAssignments = zoneAssignments.map(a => ({ 
          block_id: Number(a.block_id), 
          weight: Number(a.weight || a.rotation_percentage || 0), 
          interval: Number(a.interval || 0) 
        }));
        const pick = pickNextByWeights(`${container.container_id}_${zoneKey}`, mappedAssignments);
        
        if (!pick) {
          console.log('[CC DEBUG] Multi-zone: no block picked for zone', zoneKey);
          zoneHost.innerHTML = '';
          return;
        }
        
        console.log('[CC DEBUG] Multi-zone: requesting preview for zone', zoneKey, 'block', pick.block_id, 'frame:', { width: frameWidth, height: frameHeight, x: frameX, y: frameY });
        const htmlcss = await requestPreview(pick.block_id, { width: frameWidth, height: frameHeight, x: frameX, y: frameY, zoneId: zoneKey });
        console.log('[CC DEBUG] Multi-zone: preview response for zone', zoneKey, '- html length:', htmlcss.html?.length || 0);
        
        // Inject into zone's shadow DOM
        const shadow = ensureShadowRoot(zoneHost);
        if (!htmlcss.html) {
          setShadowContent(shadow, '<div class="cc-preview-empty">No live data</div>', emptyPreviewCSS());
        } else {
          setShadowContent(shadow, htmlcss.html, htmlcss.css || '');
          const wrapper = shadow.querySelector?.('.castconductor-preview-block');
          if (wrapper && htmlcss.config) {
            applyConfigStyles(wrapper, htmlcss.config);
          }
        }
      });
      
      await Promise.all(zonePromises);
      return; // Multi-zone rendering complete
    }

    // SINGLE-ZONE / LEGACY: Original single-zone logic
    let targetHost = host;
    let frameWidth = containerRect.width;
    let frameHeight = containerRect.height;
    let frameX = containerRect.x;
    let frameY = containerRect.y;
    let frameZoneId = null;
    if (Array.isArray(zdefs) && zdefs.length) {
      // Create or update zone sub-hosts
      const existing = new Map();
      host.querySelectorAll('.cc-zone-host').forEach(n => { if (n.dataset.zoneId) existing.set(n.dataset.zoneId, n); });
      // IMPORTANT: use logical (style) rects, not getBoundingClientRect(), since the stage canvas is CSS scaled for viewport.
      const rect = getRectFromStyle(box);
      zdefs.forEach(z => {
        const zid = String(z.id ?? z.key ?? z.name);
        const zr = z.rect || {};
        const zx = Number(zr.x ?? zr.left ?? 0);
        const zy = Number(zr.y ?? zr.top ?? 0);
        const zw = Number((zr.w ?? zr.width ?? rect.width));
        const zH = Number((zr.h ?? zr.height ?? rect.height));
        let zoneHost = existing.get(zid);
        if (!zoneHost) {
          zoneHost = el('div', { class: 'cc-zone-host', 'data-zone-id': zid, style: { position: 'absolute', overflow: 'hidden', background: 'transparent' } });
          host.appendChild(zoneHost);
        }
        zoneHost.style.left = Math.round(zx) + 'px';
        zoneHost.style.top = Math.round(zy) + 'px';
        zoneHost.style.width = Math.max(1, Math.round(zw)) + 'px';
        zoneHost.style.height = Math.max(1, Math.round(zH)) + 'px';
      });
      // Pick first zone that actually has assignments
      const zoneWithAssignments = zdefs.find(z => {
        const key = String(z.id ?? z.key ?? z.name);
        return Array.isArray(zonesObj?.[key]?.assignments) && zonesObj[key].assignments.length > 0;
      }) || zdefs[0];
      const activeZoneKey = String(zoneWithAssignments?.id ?? zoneWithAssignments?.key ?? zoneWithAssignments?.name);
      const zh = host.querySelector(`.cc-zone-host[data-zone-id="${activeZoneKey}"]`);
      if (zh) targetHost = zh;
      // frame dims = target zone rect
      frameWidth = Math.max(1, Math.round(Number(zoneWithAssignments?.rect?.w ?? zoneWithAssignments?.rect?.width ?? rect.width)));
      frameHeight = Math.max(1, Math.round(Number(zoneWithAssignments?.rect?.h ?? zoneWithAssignments?.rect?.height ?? rect.height)));
      
      // Update frameX/Y for zone offset
      const zx = Number(zoneWithAssignments?.rect?.x ?? zoneWithAssignments?.rect?.left ?? 0);
      const zy = Number(zoneWithAssignments?.rect?.y ?? zoneWithAssignments?.rect?.top ?? 0);
      frameX += zx;
      frameY += zy;

      frameZoneId = activeZoneKey;

      // Canonical Thirds Rule: For standard Lower/Upper Thirds (1280x240), we want 1:1 parity with the Canvas Editor.
      // The Canvas Editor requests previews WITHOUT frame dimensions, forcing the server to use the authored 1280x240 size.
      // If we send frame dims, the server emits "width:100%; height:100%", which breaks layout/padding parity.
      // So, for these specific containers, we intentionally OMIT the frame dims in the request.
      const pos = String(container.key || container.name || '').toLowerCase().replace(/\s+/g, '_');
      const isThird = pos === 'lower_third' || pos === 'upper_third';
      // Check if the container rect matches the canonical 1280x240 (approx)
      const isCanonicalSize = (frameWidth >= 1240 && frameWidth <= 1320) && (frameHeight >= 220 && frameHeight <= 260);

      console.log('[CC DEBUG] Canonical Thirds check - container:', container.container_id, 'pos:', pos, 'isThird:', isThird, 'frameWidth:', frameWidth, 'frameHeight:', frameHeight, 'isCanonicalSize:', isCanonicalSize);

      if (isThird && isCanonicalSize) {
        console.log('[CC DEBUG] Canonical Thirds Rule APPLIED - omitting frame dimensions');
        // Pass undefined to requestPreview so it doesn't send frame params
        frameWidth = undefined;
        frameHeight = undefined;
      } else {
        console.log('[CC DEBUG] Canonical Thirds Rule NOT applied - sending frame dimensions');
      }
      frameZoneId = activeZoneKey;
      // Visualize active zone lightly for debugging
      host.querySelectorAll('.cc-zone-host').forEach(n => { n.style.outline = 'none'; });
      if (targetHost) { targetHost.style.outline = '1px dashed rgba(255,255,255,0.25)'; }
    }
    // pick next block via deterministic schedule
    const assignments = extractAssignments(container) || [];
    console.log('[CC DEBUG] injectContainerPreview - container', container.container_id, 'assignments:', assignments);
    const pick = pickNextByWeights(container.container_id, assignments);
    console.log('[CC DEBUG] injectContainerPreview - picked block:', pick);
    if (!pick) {
      console.log('[CC DEBUG] injectContainerPreview - no block picked, clearing preview');
      targetHost.innerHTML = '';
      return;
    }
    console.log('[CC DEBUG] injectContainerPreview - requesting preview for block', pick.block_id, 'frame:', { width: frameWidth, height: frameHeight, x: frameX, y: frameY, zoneId: frameZoneId });
    const htmlcss = await requestPreview(pick.block_id, { width: frameWidth, height: frameHeight, x: frameX, y: frameY, zoneId: frameZoneId });
    console.log('[CC DEBUG] injectContainerPreview - preview response - html length:', htmlcss.html?.length || 0, 'css length:', htmlcss.css?.length || 0);
    // Shadow DOM isolation per preview to prevent CSS bleed across blocks
    const shadow = ensureShadowRoot(targetHost);
    if (!htmlcss.html) {
      console.debug('[ScenesStage] Empty preview HTML', { containerId: container.container_id, frameWidth, frameHeight, frameZoneId });
      setShadowContent(shadow, '<div class="cc-preview-empty">No live data for this block</div>', emptyPreviewCSS());
    } else {
      console.log('[CC DEBUG] injectContainerPreview - injecting HTML into shadow DOM for container', container.container_id);

      // Parity Fix: If Canonical Thirds Rule was applied, inject CSS to constrain artwork size
      // This mimics the Canvas Editor's behavior where artwork is constrained by the block height
      let finalCss = htmlcss.css || '';
      if (frameWidth === undefined && frameHeight === undefined) {
        finalCss += `
           /* Scenes Stage Parity Override for Lower/Upper Thirds */
           .castconductor-preview-block .artwork-container {
             height: 200px !important;
             width: 200px !important;
             flex: 0 0 200px !important;
             max-height: 100% !important;
             aspect-ratio: 1 / 1 !important;
           }
           .castconductor-preview-block .artwork-container img {
             width: 100% !important;
             height: 100% !important;
             object-fit: cover !important;
           }
         `;
      }

      setShadowContent(shadow, htmlcss.html, finalCss);

      // Force artwork size via JS to be absolutely sure (Nuclear Option)
      if (frameWidth === undefined && frameHeight === undefined) {
        // Force wrapper layout to ensure flex behavior even if CSS fails
        const wrapper = (shadow.querySelector ? shadow.querySelector('.castconductor-preview-block') : shadow.querySelector('.castconductor-preview-block'));
        if (wrapper) {
          wrapper.style.display = 'flex';
          wrapper.style.alignItems = 'center';
          wrapper.style.gap = '16px';
          wrapper.style.width = '100%';
          wrapper.style.height = '100%';
          wrapper.style.boxSizing = 'border-box';
        }

        const artwork = (shadow.querySelector ? shadow.querySelector('.artwork-container') : shadow.querySelector('.artwork-container'));
        if (artwork) {
          artwork.style.cssText = 'width: 200px !important; height: 200px !important; flex: 0 0 200px !important; max-height: 100% !important; aspect-ratio: 1/1 !important; overflow: hidden !important;';
          const img = artwork.querySelector('img');
          if (img) {
            img.style.cssText = 'width: 100% !important; height: 100% !important; object-fit: cover !important; display: block !important;';
          }
        }
      }

      // Always apply config styles to ensure typography is correct, regardless of container size
      const wrapper = (shadow.querySelector ? shadow.querySelector('.castconductor-preview-block') : shadow.querySelector('.castconductor-preview-block'));
      if (wrapper && htmlcss.config) {
        applyConfigStyles(wrapper, htmlcss.config);
      }

      // Scale the fixed-size preview to fit the container if needed.
      // IMPORTANT: If we used the Canonical Thirds Rule (frameWidth/frameHeight undefined),
      // the server returned fixed-size 1280x240 HTML. We should NOT scale it - let it render naturally.
      const shouldScale = frameWidth !== undefined && frameHeight !== undefined;

      if (!shouldScale) {
        console.log('[CC DEBUG] Skipping scaling - Canonical Thirds Rule was applied, using fixed 1280x240 from server');
      } else {
        // Since we sent frame dims, the server emitted responsive CSS (width:100%, height:100%).
        // We must now scale it down if the stage is smaller (e.g. 960px wide).
        try {
          const root = shadow.host || targetHost;
          const inner = (shadow.querySelector ? shadow.querySelector('.castconductor-preview-block') : root.querySelector('.castconductor-preview-block'));
          const outerRect = targetHost.getBoundingClientRect();

          console.log('[CC DEBUG] Scaling check - container:', container.container_id, 'inner element found:', !!inner, 'outerRect:', { width: outerRect?.width, height: outerRect?.height });

          if (!inner || !outerRect || outerRect.width <= 0 || outerRect.height <= 0) {
            console.warn('[CC DEBUG] Scaling skipped - missing inner block or invalid outer rect');
            throw new Error('No inner preview block or invalid outer rect');
          }

          // Reset any prior transform before measuring.
          inner.style.transform = 'none';
          inner.style.transformOrigin = 'top left';

          const ir = inner.getBoundingClientRect();
          const iw = Math.max(1, Math.round(ir.width));
          const ih = Math.max(1, Math.round(ir.height));
          const ow = outerRect.width;
          const oh = outerRect.height;
          const sx = ow / iw;
          const sy = oh / ih;
          let s = Math.min(sx, sy);

          console.log('[CC DEBUG] Scaling - inner dims:', { width: iw, height: ih }, 'outer dims:', { width: ow, height: oh }, 'sx:', sx, 'sy:', sy, 'final scale:', s);

          // If we are a third, we expect the inner block to be ~1280x240.
          // If the stage is scaled (e.g. 0.75x), outerRect will be ~960x180.
          // s should be ~0.75.
          // If the stage is full size (1280), s should be 1.
          // We trust the calculated s.


          if (s !== 1) {
            console.log('[CC DEBUG] Applying scale transform:', s);
            inner.style.transform = `scale(${s})`;
          } else {
            console.log('[CC DEBUG] No scaling needed (s === 1)');
          }
        } catch (err) {
          console.error('[CC DEBUG] Scaling failed:', err);
        }
      }
    }
    // timer to cycle if 2+
    const enabled = assignments.filter(a => (a.weight || 0) > 0);
    if (enabled.length >= 2) {
      const intervalSec = Math.max(5, Number(enabled[0].interval || 15));
      const t = setInterval(async () => {
        const pick2 = pickNextByWeights(container.container_id, assignments);
        if (!pick2) return;
        const htmlcss2 = await requestPreview(pick2.block_id, { width: frameWidth, height: frameHeight, zoneId: frameZoneId });
        const shadow2 = ensureShadowRoot(targetHost);
        if (htmlcss2.html && htmlcss2.html.length) {
          setShadowContent(shadow2, htmlcss2.html, htmlcss2.css || '');
          // Force artwork size via JS for cycled blocks too
          if (frameWidth === undefined && frameHeight === undefined) {
            // Force wrapper layout
            const wrapper = (shadow2.querySelector ? shadow2.querySelector('.castconductor-preview-block') : shadow2.querySelector('.castconductor-preview-block'));
            if (wrapper) {
              wrapper.style.display = 'flex';
              wrapper.style.alignItems = 'center';
              wrapper.style.gap = '16px';
              wrapper.style.width = '100%';
              wrapper.style.height = '100%';
              wrapper.style.boxSizing = 'border-box';
            }

            const artwork = (shadow2.querySelector ? shadow2.querySelector('.artwork-container') : shadow2.querySelector('.artwork-container'));
            if (artwork) {
              artwork.style.cssText = 'width: 200px !important; height: 200px !important; flex: 0 0 200px !important; max-height: 100% !important; aspect-ratio: 1/1 !important; overflow: hidden !important;';
              const img = artwork.querySelector('img');
              if (img) {
                img.style.cssText = 'width: 100% !important; height: 100% !important; object-fit: cover !important; display: block !important;';
              }
            }
          }

          // Always apply config styles
          const wrapper = (shadow2.querySelector ? shadow2.querySelector('.castconductor-preview-block') : shadow2.querySelector('.castconductor-preview-block'));
          if (wrapper && htmlcss2.config) {
            applyConfigStyles(wrapper, htmlcss2.config);
          }
        } else {
          setShadowContent(shadow2, '<div class="cc-preview-empty">No live data for this block</div>', emptyPreviewCSS());
        }
      }, intervalSec * 1000);
      previewTimers.set(container.container_id, t);
    }
  } catch (e) { /* silent */ }
}

// Shadow DOM helpers to isolate per-preview CSS and markup
function ensureShadowRoot(host) {
  if (host.shadowRoot) return host.shadowRoot;
  try {
    return host.attachShadow({ mode: 'open' });
  } catch (_) {
    // Fallback: if Shadow DOM is not supported (unlikely), return the host itself and allow legacy injection
    return host;
  }
}
function setShadowContent(shadow, html, css) {
  // Clear existing content safely
  while (shadow.firstChild) {
    shadow.removeChild(shadow.firstChild);
  }

  const cssText = css || '';
  const importRules = [];
  const otherCss = cssText.replace(/@import\s+[^;]+;/g, (match) => {
    importRules.push(match);
    return '';
  });

  console.log('[CC DEBUG] setShadowContent - css length:', cssText.length, 'imports:', importRules.length, 'otherCss length:', otherCss.length);

  // Create style element programmatically
  const styleEl = document.createElement('style');
  let styleContent = '';
  if (importRules.length > 0) {
    styleContent += importRules.join('\n') + '\n';
  }
  styleContent += '/* Scenes preview isolation base */\n';
  styleContent += ':host { display: block; box-sizing: border-box; }\n';
  styleContent += '.cc-preview-empty { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; color: #9ca3af; font: 12px system-ui,sans-serif; }\n';
  styleContent += '/* Author CSS */\n';
  styleContent += otherCss;

  styleEl.textContent = styleContent;
  shadow.appendChild(styleEl);

  // Inject HTML
  if (html) {
    const temp = document.createElement('div');
    temp.innerHTML = html;
    while (temp.firstChild) {
      shadow.appendChild(temp.firstChild);
    }
  }
}
function emptyPreviewCSS() {
  return '';
}
function extractAssignments(container) {
  console.log('[CC DEBUG] extractAssignments - container.zones:', container.zones);
  const z = container.zones && typeof container.zones === 'object' ? container.zones : {};
  console.log('[CC DEBUG] extractAssignments - parsed zones object:', z);
  // Prefer the active zone (first with assignments) to avoid mixing across zones
  const zdefs = (container.overrides?.layout?.zones || container.layout?.zones || []);
  console.log('[CC DEBUG] extractAssignments - zone definitions:', zdefs);
  let activeKey = null;
  for (const zdef of zdefs) {
    const key = String(zdef?.id ?? zdef?.key ?? zdef?.name);
    console.log('[CC DEBUG] extractAssignments - checking zone key:', key, 'has assignments:', Array.isArray(z?.[key]?.assignments) && z[key].assignments.length);
    if (Array.isArray(z?.[key]?.assignments) && z[key].assignments.length) { activeKey = key; break; }
  }
  // Fallback to any zone key with assignments
  if (!activeKey) {
    console.log('[CC DEBUG] extractAssignments - no active key from zdefs, trying Object.keys fallback');
    for (const key of Object.keys(z)) {
      console.log('[CC DEBUG] extractAssignments - fallback checking key:', key);
      if (Array.isArray(z[key]?.assignments) && z[key].assignments.length) { activeKey = String(key); break; }
    }
  }
  // Legacy flat structure fallback
  if (!activeKey && Array.isArray(container.zones?.assignments)) {
    console.log('[CC DEBUG] extractAssignments - using legacy flat structure');
    return container.zones.assignments.map(a => ({ block_id: Number(a.block_id), weight: Number(a.weight || 0), interval: Number(a.interval || 0) }));
  }
  const arr = activeKey ? (Array.isArray(z?.[activeKey]?.assignments) ? z[activeKey].assignments : []) : [];
  console.log('[CC DEBUG] extractAssignments - activeKey:', activeKey, 'assignments:', arr);
  return arr.map(a => ({ block_id: Number(a.block_id), weight: Number(a.weight || a.rotation_percentage || 0), interval: Number(a.interval || 0) }));
}
const scheduleState = new Map();
function pickNextByWeights(key, assignments) {
  const enabled = (assignments || []).filter(a => a.block_id && (a.weight || 0) > 0);
  if (!enabled.length) return null;
  // normalize to deterministic round-robin by GCD
  const gcd = (a, b) => b === 0 ? a : gcd(b, a % b);
  const g = enabled.reduce((acc, a) => gcd(acc, Math.max(1, Math.round(a.weight))), Math.max(1, Math.round(enabled[0].weight)));
  const seq = [];
  enabled.forEach(a => { const c = Math.max(1, Math.round(a.weight / g)); for (let i = 0; i < c; i++) seq.push(a.block_id); });
  // greedy spacing
  const spaced = [];
  const counts = {}; seq.forEach(id => { counts[id] = (counts[id] || 0) + 1; });
  while (Object.keys(counts).length) {
    const nextId = Object.keys(counts).sort((A, B) => counts[B] - counts[A]).find(id => spaced[spaced.length - 1] !== Number(id));
    if (!nextId) { spaced.push(Number(Object.keys(counts)[0])); counts[Object.keys(counts)[0]]--; if (counts[Object.keys(counts)[0]] <= 0) delete counts[Object.keys(counts)[0]]; continue; }
    spaced.push(Number(nextId)); counts[nextId]--; if (counts[nextId] <= 0) delete counts[nextId];
  }
  const state = scheduleState.get(key) || { idx: 0 };
  const id = spaced[state.idx % spaced.length];
  state.idx++;
  scheduleState.set(key, state);
  return { block_id: id };
}
async function requestPreview(blockId, frame) {
  const startTime = performance.now();
  const base = (window.castconductorScenesAjax || {}).rest_url || '';
  const url = base + 'castconductor/v5/canvas-editor/preview';

  const frameObj = {};
  if (frame) {
    if (frame.width) frameObj.width = Number(frame.width);
    if (frame.height) frameObj.height = Number(frame.height);
    if (frame.x !== undefined) frameObj.x = Number(frame.x);
    if (frame.y !== undefined) frameObj.y = Number(frame.y);
    if (frame.zoneId) frameObj.zone_id = frame.zoneId;
  }
  const hasFrame = Object.keys(frameObj).length > 0;

  let res;
  try {
    res = await fetch(url, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': (window.castconductorScenesAjax || {}).nonce },
      body: JSON.stringify({
        block_id: blockId,
        frame: hasFrame ? frameObj : undefined
      })
    });
  } catch (err) {
    console.error('[ScenesStage] Preview fetch failed', { url, blockId, error: err, elapsed: (performance.now() - startTime).toFixed(0) + 'ms' });
    return { html: '', css: '' };
  }
  if (!res.ok) {
    let body = '';
    try { body = await res.text(); } catch (_) { }
    console.error('[ScenesStage] Preview HTTP error', { url, status: res.status, blockId, body, elapsed: (performance.now() - startTime).toFixed(0) + 'ms' });
    return { html: '', css: '' };
  }

  let json;
  try {
    json = await res.json();
  } catch (jsonErr) {
    console.error('[ScenesStage] Preview JSON parse failed', { blockId, error: jsonErr });
    return { html: '', css: '' };
  }

  const elapsed = (performance.now() - startTime).toFixed(0);
  // Unwrap server shape { success, preview: { html, css, ... } }
  if (json && typeof json === 'object' && json.preview) {
    const out = { html: json.preview.html || '', css: json.preview.css || '', config: json.preview.config, real_data: json.preview.real_data };
    console.debug(`[CC PERF] requestPreview block_id=${blockId} completed in ${elapsed}ms`, { htmlLen: out.html.length, cssLen: out.css.length, frame: hasFrame ? frameObj : 'none' });
    if (!out.html || out.html.length === 0) {
      console.warn('[ScenesStage] Empty preview HTML from server', { blockId, hasRealData: !!out.real_data, contentType: json.preview.content_type, frame });
    }
    return out;
  }
  console.debug(`[CC PERF] requestPreview block_id=${blockId} completed in ${elapsed}ms (legacy format)`);
  return { html: json.html || '', css: json.css || '', config: json.config, real_data: json.real_data };
}

// Minimal styled renderer for token layers (legacy fallback – primary path uses server HTML/CSS)
// NOTE: This renderer intentionally skips artwork; real blocks use server preview HTML/CSS instead.
function renderStyledPreview(host, config, data, frame) {
  if (!host) return;
  host.innerHTML = '';
  const w = Number(frame?.width) || Number(config?.layout?.width) || host.clientWidth || 640;
  const h = Number(frame?.height) || Number(config?.layout?.height) || host.clientHeight || 360;
  const originX = Number(config?.layout?.position?.x ?? config?.layout?.x_position ?? 0);
  const originY = Number(config?.layout?.position?.y ?? config?.layout?.y_position ?? 0);
  const wrap = document.createElement('div');
  wrap.className = 'castconductor-preview-block';
  wrap.style.position = 'relative';
  wrap.style.width = w + 'px';
  wrap.style.height = h + 'px';
  // Background: apply overlay/gradient/color if configured (editor-only visual parity)
  const bg = config?.background || {}; const ov = config?.overlay || {};
  const layers = Array.isArray(bg.layers) ? bg.layers : [];
  const bgImages = [];
  let hasOverlay = false;
  layers.forEach(layer => {
    if (!layer || layer.enabled === false) return;
    if (layer.kind === 'overlay' && ov?.color) {
      const alpha = (ov.opacity != null ? Number(ov.opacity) : 0).toFixed(2);
      bgImages.push(`linear-gradient(0deg, rgba(0,0,0,${alpha}), rgba(0,0,0,${alpha}))`);
      hasOverlay = true;
    } else if (layer.kind === 'gradient' && Array.isArray(bg.gradient_colors) && bg.gradient_colors.length >= 2) {
      const dir = bg.gradient_direction || '135deg';
      bgImages.push(`linear-gradient(${dir}, ${bg.gradient_colors.join(', ')})`);
    }
  });
  if (bgImages.length) wrap.style.backgroundImage = bgImages.join(',');
  // If overlay is present, avoid painting an opaque base so the scene background shows through
  if (!hasOverlay && bg.color && String(bg.color).toLowerCase() !== 'transparent') {
    // Honor optional bg.opacity when present
    const col = String(bg.color);
    const alpha = (bg.opacity != null ? Number(bg.opacity) : null);
    let rgba = col;
    if (/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(col)) {
      const hex = col.replace('#', '');
      const r = hex.length === 3 ? parseInt(hex[0] + hex[0], 16) : parseInt(hex.substring(0, 2), 16);
      const g = hex.length === 3 ? parseInt(hex[1] + hex[1], 16) : parseInt(hex.substring(2, 4), 16);
      const b = hex.length === 3 ? parseInt(hex[2] + hex[2], 16) : parseInt(hex.substring(4, 6), 16);
      rgba = `rgba(${r}, ${g}, ${b}, ${Math.max(0, Math.min(1, alpha))})`;
    } else {
      wrap.style.backgroundColor = col;
    }
  }
  if (bg.border_radius) wrap.style.borderRadius = (bg.border_radius | 0) + 'px';

  const font = config?.typography || {};
  wrap.style.fontFamily = mapFontFamilyToCSS(font.font_family || 'system-ui,Arial,Helvetica,sans-serif');
  wrap.style.color = font.color || '#fff';

  // Helper: template replace for all supported tokens across block types
  const resolveTemplate = (tpl) => {
    if (!tpl) return '';
    const s = String(tpl);
    return s
      // Track Info
      .replace(/\{\{\s*track\.artist\s*\}\}/g, data.artist || '')
      .replace(/\{\{\s*track\.title\s*\}\}/g, data.title || '')
      // Location & Time (nested prefix tokens)
      .replace(/\{\{\s*location\.time\s*\}\}/g, data.time || '')
      .replace(/\{\{\s*location\.city\s*\}\}/g, data.city || '')
      .replace(/\{\{\s*location\.state\s*\}\}/g, data.state || '')
      .replace(/\{\{\s*location\.date\s*\}\}/g, data.date || '')
      .replace(/\{\{\s*location\s*\}\}/g, data.location || '')
      .replace(/\{\{\s*time\s*\}\}/g, data.time || '')
      .replace(/\{\{\s*date\s*\}\}/g, data.date || '')
      // Weather
      .replace(/\{\{\s*weather\.temperature\s*\}\}/g, data.temperature || '')
      .replace(/\{\{\s*weather\.condition\s*\}\}/g, data.condition || '')
      .replace(/\{\{\s*weather\.unit\s*\}\}/g, data.unit || '°F')
      .replace(/\{\{\s*weather\.location\s*\}\}/g, data.location || '')
      // Shoutouts
      .replace(/\{\{\s*shoutout\.name\s*\}\}/g, data.name || '')
      .replace(/\{\{\s*shoutout\.location\s*\}\}/g, data.location || '')
      .replace(/\{\{\s*shoutout\.message\s*\}\}/g, data.message || '')
      // Promos
      .replace(/\{\{\s*promo\.title\s*\}\}/g, data.title || '')
      .replace(/\{\{\s*promo\.content\s*\}\}/g, data.content || '')
      // Sponsors
      .replace(/\{\{\s*sponsor\.title\s*\}\}/g, data.title || '')
      .replace(/\{\{\s*sponsor\.content\s*\}\}/g, data.content || '')
      // Generic fallbacks
      .replace(/\{\{\s*title\s*\}\}/g, data.title || '')
      .replace(/\{\{\s*content\s*\}\}/g, data.content || '')
      ;
  };

  // Artwork is rendered exclusively by the server preview pipeline for parity.

  const layersOut = document.createElement('div');
  layersOut.className = 'text-content';
  layersOut.style.position = 'absolute';
  layersOut.style.left = '0'; layersOut.style.top = '0';
  layersOut.style.right = '0'; layersOut.style.bottom = '0';

  // =========================================================================
  // SMART REFLOW: Calculate adapted layer positions for target container size
  // =========================================================================
  const authoredSize = {
    width: Number(config?.layout?.width) || 1280,
    height: Number(config?.layout?.height) || 240
  };
  const targetSize = { width: w, height: h };
  
  // Calculate reflowed layout if container size differs from authored size
  let layersToRender = config.layers || [];
  if (needsReflow(authoredSize, targetSize)) {
    const layoutMode = getReflowLayoutMode(authoredSize, targetSize);
    console.debug('[SmartReflow] Applying reflow:', { authoredSize, targetSize, layoutMode });
    layersToRender = calculateReflowLayout(layersToRender, authoredSize, targetSize);
  } else {
    // No reflow needed - add _reflow with original positions for consistent rendering
    layersToRender = layersToRender.map(layer => ({
      ...layer,
      _reflow: {
        x: layer.x || 0,
        y: layer.y || 0,
        width: layer.width || 100,
        height: layer.height || 100
      }
    }));
  }

  layersToRender.forEach(layer => {
    if (!layer) return;
    
    // Skip hidden layers (from reflow)
    if (layer._reflow?.hidden) return;
    
    // Use reflowed positions/sizes, falling back to original
    const rf = layer._reflow || {};
    const layerX = rf.x ?? layer.x ?? 0;
    const layerY = rf.y ?? layer.y ?? 0;
    const layerW = rf.width ?? layer.width ?? 100;
    const layerH = rf.height ?? layer.height ?? 100;
    const layerFontSize = rf.fontSize ?? layer.style?.font_size;
    
    if (layer.kind === 'token-text') {
      const d = document.createElement('div');
      d.textContent = resolveTemplate(layer.templateText || '');
      d.style.position = 'absolute';
      d.style.left = Math.round(layerX) + 'px';
      d.style.top = Math.round(layerY) + 'px';
      d.style.width = layerW + 'px';
      d.style.height = (layerH || 'auto');
      const style = layer.style || {};
      if (layerFontSize) d.style.fontSize = Number(layerFontSize) + 'px';
      if (style.font_family) d.style.fontFamily = mapFontFamilyToCSS(style.font_family);
      if (style.color) d.style.color = style.color;
      if (style.font_weight !== undefined && style.font_weight !== '') d.style.fontWeight = String(style.font_weight);
      if (style.text_align) d.style.textAlign = style.text_align;
      if (style.line_height) d.style.lineHeight = String(style.line_height);
      layersOut.appendChild(d);
    } else if (layer.kind === 'token-image') {
      const di = document.createElement('img');
      // Map token-image to live artwork URLs by type
      let url = '';
      if (layer.token === 'track.artwork') url = data.artwork_url || '';
      else if (layer.token === 'promo.artwork') url = data.artwork_url || '';
      else if (layer.token === 'sponsor.artwork') url = data.artwork_url || '';
      else url = layer.src || '';
      if (!url) return;
      di.src = url; 
      di.style.position = 'absolute';
      di.style.left = Math.round(layerX) + 'px';
      di.style.top = Math.round(layerY) + 'px';
      di.style.width = layerW + 'px';
      di.style.height = layerH + 'px';
      di.style.objectFit = 'cover';
      layersOut.appendChild(di);
    }
  });

  wrap.appendChild(layersOut);
  host.appendChild(wrap);
}

// ═══════════════════════════════════════════════════════════════════════════════
// UNIFIED SCENE MANAGER MODAL
// Combines: Scenes List, Background/Branding, Containers, Scheduling into one tabbed modal
// ═══════════════════════════════════════════════════════════════════════════════

function showSceneManagerModal(root, currentScene, currentContainers) {
  const host = root.querySelector('[data-host="modal"]');
  if (!host) return;
  host.innerHTML = '';
  
  // Track current scene data (may change if user switches scenes)
  let scene = currentScene; // The ACTIVE scene (what's displayed on stage)
  let containers = currentContainers || [];
  
  // Track which scene is being EDITED (separate from active scene)
  // This allows editing any scene without affecting what's displayed
  // IMPORTANT: If we're in preview mode, start by editing the previewed scene!
  let editingScene = currentScene; // Default to active scene
  
  // If we're previewing a specific scene, start editing THAT scene
  // This fixes the bug where closing and re-opening modal loses the editing context
  const initEditingScene = async () => {
    if (currentDisplayMode === 'preview' && previewingSceneId) {
      const base = (window.castconductorScenesAjax || {}).rest_url || '';
      try {
        const resp = await fetchJSON(base + `castconductor/v5/scenes/${previewingSceneId}`);
        const sceneData = unwrapApi(resp);
        editingScene = {
          ...sceneData,
          id: sceneData.id || sceneData.ID,
          name: sceneData.name,
          background: parseJSONSafe(sceneData.background, {}),
          branding: parseJSONSafe(sceneData.branding, {})
        };
        updateSceneLabel();
        buildScenesTab();
        buildAppearanceTab();
        buildContainersTab();
        buildOverlaysTab();
        buildSchedulingTab();
      } catch (e) {
        console.warn('[SceneManager] Failed to load previewing scene:', e);
        // Fall back to active scene
      }
    }
  };
  
  // Track pending container changes (containers explicitly assigned to this scene)
  // This is separate from master containers - only containers in scene_containers table
  let pendingContainers = []; // Will be populated from scene-specific data
  
  // Dark modal overlay
  const overlay = el('div', { class: 'cc-modal-overlay' });
  
  // Dark modal card - wider for tabs
  const card = el('div', { class: 'cc-modal-card cc-scene-manager', style: { minWidth: '720px', maxWidth: '860px' } });
  
  // Header with scene name and close button
  const header = el('div', { class: 'cc-modal-header' }, [
    el('div', { style: { display: 'flex', alignItems: 'center', gap: '12px' } }, [
      el('h2', { class: 'cc-modal-title' }, 'Scene Manager'),
      el('span', { class: 'cc-scene-badge active', id: 'cc-mgr-scene-name' }, scene?.name || 'No Scene')
    ]),
    el('button', { class: 'cc-modal-close', 'aria-label': 'Close' }, '×')
  ]);
  
  // Tab bar - Scenes, Appearance, Containers, Scheduling (Overlays moved to dedicated Menu page)
  const tabs = el('div', { class: 'cc-mgr-tabs' }, [
    el('button', { class: 'cc-mgr-tab active', 'data-tab': 'scenes' }, '📋 Scenes'),
    el('button', { class: 'cc-mgr-tab', 'data-tab': 'appearance' }, '🎨 Appearance'),
    el('button', { class: 'cc-mgr-tab', 'data-tab': 'containers' }, '📦 Containers'),
    el('button', { class: 'cc-mgr-tab', 'data-tab': 'scheduling' }, '📅 Scheduling')
  ]);
  
  // Tab content areas (Overlays tab removed - now on dedicated Menu page)
  const scenesPanel = el('div', { class: 'cc-mgr-panel', 'data-panel': 'scenes' });
  const appearancePanel = el('div', { class: 'cc-mgr-panel', 'data-panel': 'appearance', style: { display: 'none' } });
  const containersPanel = el('div', { class: 'cc-mgr-panel', 'data-panel': 'containers', style: { display: 'none' } });
  const schedulingPanel = el('div', { class: 'cc-mgr-panel', 'data-panel': 'scheduling', style: { display: 'none' } });
  // overlaysPanel kept as stub to prevent errors from legacy buildOverlaysTab calls
  const overlaysPanel = el('div', { class: 'cc-mgr-panel', 'data-panel': 'overlays', style: { display: 'none' } });
  
  const body = el('div', { class: 'cc-modal-body' }, [scenesPanel, appearancePanel, containersPanel, schedulingPanel]);
  
  // Close button footer
  const actions = el('div', { class: 'cc-modal-actions' }, [
    el('button', { class: 'cc-modal-btn', id: 'cc-mgr-close' }, 'Close')
  ]);
  
  card.appendChild(header);
  card.appendChild(tabs);
  card.appendChild(body);
  card.appendChild(actions);
  overlay.appendChild(card);
  host.appendChild(overlay);
  host.style.display = 'block';
  
  // Update the toolbar "Return to Live" button visibility
  const updateReturnToLiveBtn = () => {
    const btn = root.querySelector('#cc-return-to-live-btn');
    if (btn) {
      btn.style.display = currentDisplayMode === 'preview' ? 'inline-block' : 'none';
    }
  };
  
  const hide = async () => { 
    // DON'T return to live mode when closing - let preview persist!
    // User can click "Return to Live" button in toolbar when ready
    updateReturnToLiveBtn();
    host.style.display = 'none'; 
    host.innerHTML = ''; 
  };
  
  // Close handlers
  header.querySelector('.cc-modal-close').addEventListener('click', hide);
  actions.querySelector('#cc-mgr-close').addEventListener('click', hide);
  overlay.addEventListener('click', (e) => { if (e.target === overlay) hide(); });
  
  // Tab switching
  tabs.querySelectorAll('.cc-mgr-tab').forEach(tab => {
    tab.addEventListener('click', () => {
      tabs.querySelectorAll('.cc-mgr-tab').forEach(t => t.classList.remove('active'));
      tab.classList.add('active');
      const panelName = tab.dataset.tab;
      body.querySelectorAll('.cc-mgr-panel').forEach(p => p.style.display = 'none');
      body.querySelector(`[data-panel="${panelName}"]`).style.display = 'block';
    });
  });
  
  // Update scene name badge - shows which scene is being EDITED (not necessarily active)
  const updateSceneLabel = () => {
    const badge = card.querySelector('#cc-mgr-scene-name');
    if (badge) {
      const isActive = editingScene?.id === scene?.id;
      badge.textContent = editingScene?.name || 'No Scene';
      badge.className = `cc-scene-badge ${isActive ? 'active' : 'editing'}`;
    }
  };
  
  // ─────────────────────────────────────────────────────────────────────────────
  // SCENES TAB - Now with Edit button to select scene for editing
  // ─────────────────────────────────────────────────────────────────────────────
  const buildScenesTab = () => {
    scenesPanel.innerHTML = '';
    
    // Instructions
    const instructions = el('div', { style: { marginBottom: '12px', padding: '10px', background: '#1e3a5f', borderRadius: '6px', fontSize: '12px', color: '#93c5fd' } }, [
      el('strong', {}, '💡 '),
      'Click "Edit" to select a scene for editing. Click "👁️ Preview" to temporarily show a scene on stage. ',
      el('span', { style: { color: '#fbbf24' } }, `Currently editing: ${editingScene?.name || 'None'}`)
    ]);
    
    // Preview mode banner - shows when previewing a non-active scene
    const previewBanner = el('div', { 
      id: 'cc-mgr-preview-banner',
      style: { 
        display: currentDisplayMode === 'preview' ? 'flex' : 'none',
        marginBottom: '12px', 
        padding: '10px', 
        background: '#4c1d95', 
        borderRadius: '6px', 
        fontSize: '12px', 
        color: '#ddd6fe',
        alignItems: 'center',
        justifyContent: 'space-between'
      } 
    }, [
      el('span', {}, [
        el('strong', { style: { color: '#a78bfa' } }, '👁️ PREVIEW MODE: '),
        `Showing scene preview on stage. Changes won't be saved in preview mode.`
      ]),
      el('button', { 
        class: 'cc-modal-btn', 
        id: 'cc-mgr-return-to-live',
        style: { padding: '4px 10px', fontSize: '11px', background: '#22c55e', color: '#fff' } 
      }, '↩ Return to Live')
    ]);
    
    const sceneList = el('ul', { class: 'cc-scene-list', style: { marginBottom: '12px' } });
    
    // New scene input
    const newSceneArea = el('div', { class: 'cc-new-scene-area' }, [
      el('input', { type: 'text', placeholder: 'New scene name...', id: 'cc-mgr-new-scene' }),
      el('button', { class: 'cc-modal-btn primary', id: 'cc-mgr-create-scene' }, '+ Create Scene'),
      el('button', { class: 'cc-modal-btn', id: 'cc-mgr-import-scene', style: { background: '#0ea5e9', color: '#fff' } }, '📥 Import JSON')
    ]);
    
    // Hidden file input for import
    const importInput = el('input', { 
      type: 'file', 
      accept: '.json,application/json', 
      id: 'cc-mgr-import-input',
      style: { display: 'none' }
    });
    newSceneArea.appendChild(importInput);
    
    scenesPanel.appendChild(instructions);
    scenesPanel.appendChild(previewBanner);
    scenesPanel.appendChild(sceneList);
    scenesPanel.appendChild(newSceneArea);
    
    // Import button handler
    newSceneArea.querySelector('#cc-mgr-import-scene').addEventListener('click', () => {
      importInput.click();
    });
    
    // File input change handler - process imported JSON
    importInput.addEventListener('change', async (e) => {
      const file = e.target.files?.[0];
      if (!file) return;
      
      try {
        setStageStatus(root, 'saving', 'Importing scene…');
        const text = await file.text();
        const data = JSON.parse(text);
        
        // Validate import format
        if (!data._castconductor_export || !data.scene) {
          alert('Invalid scene file. Please select a valid CastConductor scene export.');
          setStageStatus(root, 'error', 'Invalid file format');
          importInput.value = '';
          return;
        }
        
        const importedScene = data.scene;
        const importedContainers = data.containers || [];
        
        // Generate unique name
        let newName = (importedScene.name || 'Imported Scene') + ' (Imported)';
        let counter = 2;
        const existingScenes = await listScenes();
        const existingNames = existingScenes.map(x => x.name);
        while (existingNames.includes(newName)) {
          newName = (importedScene.name || 'Imported Scene') + ` (Imported ${counter})`;
          counter++;
        }
        
        // Create new scene with imported data
        const base = (window.castconductorScenesAjax || {}).rest_url || '';
        const createResp = await fetch(`${base}castconductor/v5/scenes`, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': (window.castconductorScenesAjax || {}).nonce },
          body: JSON.stringify({
            name: newName,
            description: importedScene.description || '',
            rotation_enabled: importedScene.rotation_enabled === true,
            rotation_interval: importedScene.rotation_interval || 60,
            branding: importedScene.branding || {},
            background: importedScene.background || {},
            metadata: importedScene.metadata || {},
            schedule_enabled: importedScene.schedule_enabled === true,
            schedule_start: importedScene.schedule_start || null,
            schedule_end: importedScene.schedule_end || null,
            schedule_timezone: importedScene.schedule_timezone || 'America/Los_Angeles',
            schedule_days: importedScene.schedule_days || [],
            schedule_time_start: importedScene.schedule_time_start || null,
            schedule_time_end: importedScene.schedule_time_end || null,
            schedule_priority: importedScene.schedule_priority || 0
          })
        });
        const created = await createResp.json();
        const newId = created?.data?.id || created?.id;
        
        if (newId && importedContainers.length > 0) {
          // Import containers to new scene
          for (const c of importedContainers) {
            await fetch(`${base}castconductor/v5/scenes/${newId}/containers`, {
              method: 'PUT',
              headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': (window.castconductorScenesAjax || {}).nonce },
              body: JSON.stringify({
                container_id: c.container_id,
                overrides: c.overrides || {},
                zones: c.zones || {}
              })
            });
          }
        }
        
        setStageStatus(root, 'saved', `Imported: ${newName}`);
        setTimeout(() => setStageStatus(root, 'clear'), 900);
        refreshScenes();
        
        // Select the imported scene for editing
        if (newId) {
          const resp = await fetchJSON(base + `castconductor/v5/scenes/${newId}`);
          const sceneData = unwrapApi(resp);
          editingScene = {
            ...sceneData,
            id: sceneData.id || sceneData.ID,
            name: sceneData.name,
            background: parseJSONSafe(sceneData.background, {}),
            branding: parseJSONSafe(sceneData.branding, {})
          };
          updateSceneLabel();
          buildAppearanceTab();
          buildContainersTab();
          buildOverlaysTab();
          buildSchedulingTab();
        }
      } catch (e) {
        console.error('[SceneManager] Import failed:', e);
        alert('Failed to import scene. Please check the file format.');
        setStageStatus(root, 'error', 'Import failed');
      }
      
      // Reset file input
      importInput.value = '';
    });
    
    // Return to Live button handler
    previewBanner.querySelector('#cc-mgr-return-to-live')?.addEventListener('click', async () => {
      currentDisplayMode = 'live';
      previewingSceneId = null;
      setStageStatus(root, 'saving', 'Returning to live scene…');
      
      // Hide toolbar "Return to Live" button
      const returnBtn = root.querySelector('#cc-return-to-live-btn');
      if (returnBtn) returnBtn.style.display = 'none';
      
      // Re-render the active scene
      await renderSceneOnStage(root, scene, containers, { 
        showPreviewBadge: false,
        onContainerChange: async (containerId, rect) => {
          if (!scene?.id) return;
          const c = (containers || []).find(x => x.container_id === containerId);
          if (c) { c.rect = rect; c.overrides = { ...(c.overrides || {}), rect: rect }; }
          await persistContainer(scene.id, c || { container_id: containerId, rect, zones: {} });
          await renderContainerPreviews(root, scene, containers || []);
        },
        onSelect: () => {}
      });
      setActiveSceneLabel(root, scene);
      buildScenesTab();
      setStageStatus(root, 'saved', 'Returned to live scene');
      setTimeout(() => setStageStatus(root, 'clear'), 900);
    });
    
    // Refresh scene list
    const refreshScenes = async () => {
      sceneList.innerHTML = '';
      try {
        const scenes = await listScenes();
        if (!scenes || scenes.length === 0) {
          sceneList.appendChild(el('li', { class: 'cc-scene-item', style: { justifyContent: 'center', color: '#64748b' } }, 'No scenes. Create one below.'));
          return;
        }
        scenes.forEach(s => {
          // Fix: is_active might be "1" string or 1 number
          const isActive = s.is_active == 1 || s.is_active === true || s.is_active === '1';
          const isEditing = editingScene?.id == s.id;
          
          const li = el('li', { 
            class: `cc-scene-item ${isActive ? 'active' : ''} ${isEditing ? 'editing' : ''}`,
            style: { border: isEditing ? '2px solid #f59e0b' : undefined }
          });
          
          const info = el('div', { class: 'cc-scene-info' }, [
            el('span', { class: 'cc-scene-name' }, s.name || `Scene #${s.id}`)
          ]);
          
          // Show badges
          if (isActive) {
            info.appendChild(el('span', { class: 'cc-scene-badge active', style: { background: '#22c55e', marginLeft: '8px' } }, 'Active'));
          }
          if (isEditing) {
            info.appendChild(el('span', { class: 'cc-scene-badge editing', style: { background: '#f59e0b', marginLeft: '8px' } }, 'Editing'));
          }
          
          const btnGroup = el('div', { class: 'cc-scene-actions' });
          
          // Edit button - selects this scene for editing
          const editBtn = el('button', { 
            class: `cc-scene-btn ${isEditing ? 'active' : ''}`,
            style: { background: isEditing ? '#f59e0b' : undefined }
          }, isEditing ? '✓ Selected' : '✏️ Edit');
          editBtn.addEventListener('click', async () => {
            // Select this scene for editing
            setStageStatus(root, 'saving', 'Loading scene…');
            const base = (window.castconductorScenesAjax || {}).rest_url || '';
            try {
              const resp = await fetchJSON(base + `castconductor/v5/scenes/${s.id}`);
              const sceneData = unwrapApi(resp);
              // IMPORTANT: spread sceneData FIRST, then override with parsed JSON values
              editingScene = {
                ...sceneData,
                id: sceneData.id || sceneData.ID,
                name: sceneData.name,
                background: parseJSONSafe(sceneData.background, {}),
                branding: parseJSONSafe(sceneData.branding, {})
              };
              updateSceneLabel();
              buildScenesTab(); // Refresh to show "Editing" badge
              buildAppearanceTab();
              buildContainersTab();
              buildOverlaysTab();
              buildSchedulingTab();
              setStageStatus(root, 'saved', `Editing: ${editingScene.name}`);
              setTimeout(() => setStageStatus(root, 'clear'), 900);
            } catch (e) {
              console.error('[SceneManager] Failed to load scene:', e);
              setStageStatus(root, 'error', 'Failed to load scene');
            }
          });
          btnGroup.appendChild(editBtn);
          
          // Preview button - temporarily shows this scene on stage without activating
          const previewBtn = el('button', { 
            class: 'cc-scene-btn',
            title: 'Preview this scene on stage',
            style: { background: '#6366f1', color: '#fff' }
          }, '👁️ Preview');
          previewBtn.addEventListener('click', async () => {
            setStageStatus(root, 'saving', 'Loading preview…');
            try {
              // Load scene data by ID
              const { scene: previewScene, containers: previewContainers } = await loadSceneById(s.id);
              if (!previewScene) {
                setStageStatus(root, 'error', 'Failed to load scene');
                return;
              }
              
              // Track preview mode
              currentDisplayMode = 'preview';
              previewingSceneId = s.id;
              
              // ALSO set this as the editing scene so tabs show correct data
              editingScene = previewScene;
              updateSceneLabel();
              buildAppearanceTab();
              buildContainersTab();
              buildOverlaysTab();
              buildSchedulingTab();
              
              // Show "Return to Live" button in toolbar
              const returnBtn = root.querySelector('#cc-return-to-live-btn');
              if (returnBtn) returnBtn.style.display = 'inline-block';
              
              // Render the preview scene on stage
              await renderSceneOnStage(root, previewScene, previewContainers, { 
                showPreviewBadge: true,
                onContainerChange: async (containerId, rect) => {
                  // In preview mode, don't persist changes - just visual preview
                  console.log('[SceneManager] Preview mode - changes not persisted');
                },
                onSelect: (id) => {
                  // Still show assignments panel in preview mode (read-only context)
                  renderAssignmentsPanel(root, previewScene, previewContainers, id);
                }
              });
              
              // Update instructions to show we're in preview mode
              buildScenesTab();
              setStageStatus(root, 'saved', `Previewing: ${previewScene.name}`);
              setTimeout(() => setStageStatus(root, 'clear'), 900);
            } catch (e) {
              console.error('[SceneManager] Preview failed:', e);
              setStageStatus(root, 'error', 'Preview failed');
            }
          });
          btnGroup.appendChild(previewBtn);
          
          // Duplicate button - copy scene as template
          const duplicateBtn = el('button', { 
            class: 'cc-scene-btn',
            title: 'Duplicate this scene as a template',
            style: { background: '#8b5cf6', color: '#fff' }
          }, '📋 Duplicate');
          duplicateBtn.addEventListener('click', async () => {
            setStageStatus(root, 'saving', 'Duplicating scene…');
            try {
              const base = (window.castconductorScenesAjax || {}).rest_url || '';
              // Load full scene data
              const resp = await fetchJSON(base + `castconductor/v5/scenes/${s.id}`);
              const sceneData = unwrapApi(resp);
              
              // Load containers for this scene
              const containersResp = await fetchJSON(base + `castconductor/v5/scenes/${s.id}/containers`);
              const sceneContainers = unwrapApi(containersResp) || [];
              
              // Generate unique name
              let newName = (sceneData.name || 'Scene') + ' (Copy)';
              let counter = 2;
              const existingScenes = await listScenes();
              const existingNames = existingScenes.map(x => x.name);
              while (existingNames.includes(newName)) {
                newName = (sceneData.name || 'Scene') + ` (Copy ${counter})`;
                counter++;
              }
              
              // Create new scene with copied data
              const createResp = await fetch(`${base}castconductor/v5/scenes`, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': (window.castconductorScenesAjax || {}).nonce },
                body: JSON.stringify({
                  name: newName,
                  description: sceneData.description || '',
                  rotation_enabled: sceneData.rotation_enabled == 1,
                  rotation_interval: sceneData.rotation_interval || 60,
                  branding: parseJSONSafe(sceneData.branding, {}),
                  background: parseJSONSafe(sceneData.background, {}),
                  metadata: parseJSONSafe(sceneData.metadata, {})
                })
              });
              const created = await createResp.json();
              const newId = created?.data?.id || created?.id;
              
              if (newId && sceneContainers.length > 0) {
                // Copy containers to new scene
                for (const c of sceneContainers) {
                  await fetch(`${base}castconductor/v5/scenes/${newId}/containers`, {
                    method: 'PUT',
                    headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': (window.castconductorScenesAjax || {}).nonce },
                    body: JSON.stringify({
                      container_id: c.container_id,
                      overrides: parseJSONSafe(c.overrides, {}),
                      zones: parseJSONSafe(c.zones, {})
                    })
                  });
                }
              }
              
              setStageStatus(root, 'saved', `Duplicated: ${newName}`);
              setTimeout(() => setStageStatus(root, 'clear'), 900);
              refreshScenes();
            } catch (e) {
              console.error('[SceneManager] Duplicate failed:', e);
              setStageStatus(root, 'error', 'Duplicate failed');
            }
          });
          btnGroup.appendChild(duplicateBtn);
          
          // Export JSON button
          const exportBtn = el('button', { 
            class: 'cc-scene-btn',
            title: 'Export scene as JSON file',
            style: { background: '#0ea5e9', color: '#fff' }
          }, '📤 Export');
          exportBtn.addEventListener('click', async () => {
            setStageStatus(root, 'saving', 'Exporting scene…');
            try {
              const base = (window.castconductorScenesAjax || {}).rest_url || '';
              // Load full scene data
              const resp = await fetchJSON(base + `castconductor/v5/scenes/${s.id}`);
              const sceneData = unwrapApi(resp);
              
              // Load containers for this scene
              const containersResp = await fetchJSON(base + `castconductor/v5/scenes/${s.id}/containers`);
              const sceneContainers = unwrapApi(containersResp) || [];
              
              // Build export object
              const exportData = {
                _castconductor_export: true,
                _version: '5.6.7',
                _exported_at: new Date().toISOString(),
                scene: {
                  name: sceneData.name,
                  description: sceneData.description || '',
                  rotation_enabled: sceneData.rotation_enabled == 1,
                  rotation_interval: parseInt(sceneData.rotation_interval, 10) || 60,
                  branding: parseJSONSafe(sceneData.branding, {}),
                  background: parseJSONSafe(sceneData.background, {}),
                  metadata: parseJSONSafe(sceneData.metadata, {}),
                  schedule_enabled: sceneData.schedule_enabled == 1,
                  schedule_start: sceneData.schedule_start,
                  schedule_end: sceneData.schedule_end,
                  schedule_timezone: sceneData.schedule_timezone,
                  schedule_days: parseJSONSafe(sceneData.schedule_days, []),
                  schedule_time_start: sceneData.schedule_time_start,
                  schedule_time_end: sceneData.schedule_time_end,
                  schedule_priority: parseInt(sceneData.schedule_priority, 10) || 0
                },
                containers: sceneContainers.map(c => ({
                  container_id: c.container_id,
                  overrides: parseJSONSafe(c.overrides, {}),
                  zones: parseJSONSafe(c.zones, {})
                }))
              };
              
              // Create downloadable JSON file
              const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' });
              const url = URL.createObjectURL(blob);
              const a = document.createElement('a');
              a.href = url;
              a.download = `scene-${(sceneData.name || 'export').toLowerCase().replace(/[^a-z0-9]+/g, '-')}-${Date.now()}.json`;
              document.body.appendChild(a);
              a.click();
              document.body.removeChild(a);
              URL.revokeObjectURL(url);
              
              setStageStatus(root, 'saved', 'Scene exported');
              setTimeout(() => setStageStatus(root, 'clear'), 900);
            } catch (e) {
              console.error('[SceneManager] Export failed:', e);
              setStageStatus(root, 'error', 'Export failed');
            }
          });
          btnGroup.appendChild(exportBtn);
          
          // Activate button (only for non-active scenes)
          if (!isActive) {
            const activateBtn = el('button', { class: 'cc-scene-btn', style: { background: '#22c55e', color: '#fff' } }, '▶ Activate');
            activateBtn.addEventListener('click', async () => {
              setStageStatus(root, 'saving', 'Activating…');
              await activateSceneById(s.id);
              // Reload scene data
              const { scene: newScene, containers: newContainers } = await loadSceneAndContainers();
              scene = newScene;
              containers = newContainers;
              // Also set as editing scene
              editingScene = newScene;
              updateSceneLabel();
              buildAppearanceTab();
              buildContainersTab();
              buildOverlaysTab();
              buildSchedulingTab();
              setStageStatus(root, 'saved', 'Scene activated');
              setTimeout(() => setStageStatus(root, 'clear'), 900);
              refreshScenes();
              
              // Exit preview mode since we're activating a scene
              currentDisplayMode = 'live';
              previewingSceneId = null;
              
              // Re-render containers on stage with proper callbacks
              let selectedId = null;
              renderContainers(root, newContainers || [], async (containerId, rect) => {
                if (!newScene?.id) return;
                const c = (newContainers || []).find(x => x.container_id === containerId);
                if (c) { c.rect = rect; c.overrides = { ...(c.overrides || {}), rect: rect }; }
                await persistContainer(newScene.id, c || { container_id: containerId, rect, zones: {} });
                await renderContainerPreviews(root, newScene, newContainers || []);
              }, (id) => { selectedId = id; renderAssignmentsPanel(root, newScene, newContainers || [], selectedId); });
              
              // Update stage visuals
              applyBackground(root, scene);
              applyBranding(root, scene);
              setActiveSceneLabel(root, scene);
              
              // Refresh previews
              await renderContainerPreviews(root, newScene, newContainers || []);
            });
            btnGroup.appendChild(activateBtn);
          }
          
          // Delete button (only for non-active scenes)
          if (!isActive) {
            const deleteBtn = el('button', { 
              class: 'cc-scene-btn danger',
              title: 'Delete scene',
              style: { background: '#ef4444', color: '#fff' }
            }, '🗑️');
            deleteBtn.addEventListener('click', async () => {
              const deleted = await deleteSceneWithConfirmation(s.id, s.name || 'Untitled');
              if (deleted) {
                // If we deleted the scene we were editing, clear editing state
                if (editingScene?.id == s.id) {
                  editingScene = scene; // Fall back to active scene
                  updateSceneLabel();
                  buildAppearanceTab();
                  buildContainersTab();
                  buildOverlaysTab();
                  buildSchedulingTab();
                }
                setStageStatus(root, 'saved', 'Scene deleted');
                refreshScenes();
              }
            });
            btnGroup.appendChild(deleteBtn);
          } else {
            // Show disabled delete for active scene
            const deleteBtn = el('button', { 
              class: 'cc-scene-btn',
              title: 'Cannot delete active scene',
              style: { opacity: '0.5', cursor: 'not-allowed' },
              disabled: 'disabled'
            }, '🗑️');
            btnGroup.appendChild(deleteBtn);
          }
          
          li.appendChild(info);
          li.appendChild(btnGroup);
          sceneList.appendChild(li);
        });
      } catch (e) {
        console.error('[SceneManager] Failed to load scenes:', e);
        sceneList.appendChild(el('li', { class: 'cc-scene-item', style: { color: '#fca5a5' } }, 'Failed to load scenes'));
      }
    };
    
    // Create scene handler - creates and immediately selects for editing (but doesn't activate)
    scenesPanel.querySelector('#cc-mgr-create-scene').addEventListener('click', async () => {
      const input = scenesPanel.querySelector('#cc-mgr-new-scene');
      const rawName = (input.value || '').trim();
      // Send empty string to backend to trigger auto-numbering, or validate name
      const name = rawName || '';
      input.value = '';
      setStageStatus(root, 'saving', 'Creating scene…');
      
      // Create WITHOUT activating
      const base = (window.castconductorScenesAjax || {}).rest_url || '';
      try {
        const resp = await fetch(`${base}castconductor/v5/scenes`, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': (window.castconductorScenesAjax || {}).nonce },
          body: JSON.stringify({
            name,
            description: '',
            rotation_enabled: false,
            rotation_interval: 60,
            branding: {},
            background: {},
            metadata: {}
          })
        });
        const created = await resp.json();
        
        // Check for error
        if (!resp.ok || created?.success === false) {
          const errMsg = created?.message || created?.data?.message || 'Failed to create scene';
          alert(errMsg);
          setStageStatus(root, 'error', 'Failed');
          return;
        }
        
        // Check if scene already existed (idempotent response)
        const isExisting = created?.data?.existing === true;
        const newId = created?.data?.id || created?.id;
        
        // Load and select the scene for editing
        if (newId) {
          const resp = await fetchJSON(base + `castconductor/v5/scenes/${newId}`);
          const sceneData = unwrapApi(resp);
          // IMPORTANT: spread sceneData FIRST, then override with parsed JSON values
          editingScene = {
            ...sceneData,
            id: sceneData.id || sceneData.ID,
            name: sceneData.name,
            background: parseJSONSafe(sceneData.background, {}),
            branding: parseJSONSafe(sceneData.branding, {})
          };
          updateSceneLabel();
          buildAppearanceTab();
          buildContainersTab();
          buildOverlaysTab();
          buildSchedulingTab();
        }
        
        // Get the actual scene name (might have been auto-generated)
        const actualName = editingScene?.name || name || 'New scene';
        setStageStatus(root, 'saved', `Created: ${actualName}`);
        setTimeout(() => setStageStatus(root, 'clear'), 900);
        refreshScenes();
      } catch (e) {
        console.error('[SceneManager] Failed to create scene:', e);
        setStageStatus(root, 'error', 'Failed to create scene');
      }
    });
    
    // Enter key creates scene
    scenesPanel.querySelector('#cc-mgr-new-scene').addEventListener('keypress', (e) => {
      if (e.key === 'Enter') scenesPanel.querySelector('#cc-mgr-create-scene').click();
    });
    
    refreshScenes();
  };
  
  // ─────────────────────────────────────────────────────────────────────────────
  // APPEARANCE TAB (Background & Branding) - Uses editingScene
  // ─────────────────────────────────────────────────────────────────────────────
  const buildAppearanceTab = () => {
    appearancePanel.innerHTML = '';
    
    if (!editingScene?.id) {
      appearancePanel.appendChild(el('div', { style: { color: '#64748b', textAlign: 'center', padding: '20px' } }, 'No scene selected. Select a scene from the Scenes tab first.'));
      return;
    }
    
    // Show which scene we're editing (with warning if active)
    const isActive = editingScene.id === scene?.id;
    const editingInfo = el('div', { style: { marginBottom: '12px', padding: '8px', background: isActive ? '#7c2d12' : '#1e3a5f', borderRadius: '6px', fontSize: '12px', color: isActive ? '#fed7aa' : '#93c5fd' } }, [
      el('span', {}, `Editing appearance for: `),
      el('strong', {}, editingScene.name || `Scene #${editingScene.id}`),
      ...(isActive ? [el('span', { style: { marginLeft: '8px', color: '#fbbf24' } }, '⚠️ ACTIVE – changes push to Roku')] : [])
    ]);
    
    // Branding Enabled Toggle (allows disabling scene-level branding)
    const brandingEnabled = editingScene?.branding?.enabled !== false; // Default true
    const brandingEnabledRow = el('div', { 
      class: 'cc-form-row', 
      style: { 
        marginBottom: '16px', 
        padding: '12px', 
        background: '#0d1526', 
        borderRadius: '8px', 
        border: '1px solid #1f2a44',
        alignItems: 'flex-start'
      } 
    }, [
      el('label', { 
        style: { display: 'flex', alignItems: 'center', gap: '10px', cursor: 'pointer', flex: '1' } 
      }, [
        el('input', { 
          type: 'checkbox', 
          id: 'cc-mgr-branding-enabled', 
          checked: brandingEnabled, 
          style: { width: '18px', height: '18px', cursor: 'pointer' } 
        }),
        el('div', {}, [
          el('strong', { style: { color: '#e2e8f0' } }, 'Show Branding (Logo)'),
          el('div', { style: { fontSize: '11px', color: '#64748b', marginTop: '2px' } }, 
            'When disabled, scene-level logo is hidden. You can add branding via content blocks instead.')
        ])
      ])
    ]);
    
    // Branding logo picker
    const logoRow = el('div', { class: 'cc-form-row' }, [
      el('label', {}, 'Branding Logo'),
      el('input', { type: 'text', placeholder: 'Logo URL...', id: 'cc-mgr-logo-url', value: (editingScene?.branding?.logo?.src || editingScene?.branding?.logo || '') }),
      el('button', { class: 'cc-modal-btn', id: 'cc-mgr-pick-logo', style: { flex: '0 0 auto' } }, 'Choose…')
    ]);
    
    // Logo scale slider (10% to 100%)
    const currentScale = editingScene?.branding?.logo_scale ?? 70;
    const scaleLabel = el('span', { id: 'cc-mgr-logo-scale-label' }, `${currentScale}%`);
    const logoScaleRow = el('div', { class: 'cc-form-row', style: { alignItems: 'center' } }, [
      el('label', {}, 'Logo Size'),
      el('input', { 
        type: 'range', 
        id: 'cc-mgr-logo-scale', 
        min: '10', 
        max: '100', 
        value: currentScale, 
        style: { flex: '1', margin: '0 10px' } 
      }),
      scaleLabel
    ]);
    
    // Logo position - 49-zone grid (7x7 for granular control + true center)
    const currentPosition = editingScene?.branding?.logo_position ?? 'zone-25';
    const zoneGrid = el('div', { 
      class: 'cc-zone-grid', 
      id: 'cc-mgr-logo-position-grid',
      style: { 
        display: 'grid', 
        gridTemplateColumns: 'repeat(7, 1fr)', 
        gridTemplateRows: 'repeat(7, 1fr)',
        gap: '2px', 
        width: '210px', 
        height: '120px',  // 16:9 aspect ratio (210 * 9/16 = 118)
        background: '#1e293b',
        borderRadius: '6px',
        padding: '4px',
        border: '1px solid #334155'
      }
    });
    
    // Zone definitions: 7 rows × 7 columns = 49 zones
    // Zone 25 (row 4, col 4) = TRUE CENTER
    const zonePositions = [
      // Row 1 (top)
      { id: 'zone-1',  label: '' },
      { id: 'zone-2',  label: '' },
      { id: 'zone-3',  label: '' },
      { id: 'zone-4',  label: '' },
      { id: 'zone-5',  label: '' },
      { id: 'zone-6',  label: '' },
      { id: 'zone-7',  label: '' },
      // Row 2
      { id: 'zone-8',  label: '' },
      { id: 'zone-9',  label: '' },
      { id: 'zone-10', label: '' },
      { id: 'zone-11', label: '' },
      { id: 'zone-12', label: '' },
      { id: 'zone-13', label: '' },
      { id: 'zone-14', label: '' },
      // Row 3
      { id: 'zone-15', label: '' },
      { id: 'zone-16', label: '' },
      { id: 'zone-17', label: '' },
      { id: 'zone-18', label: '' },
      { id: 'zone-19', label: '' },
      { id: 'zone-20', label: '' },
      { id: 'zone-21', label: '' },
      // Row 4 (center row)
      { id: 'zone-22', label: '' },
      { id: 'zone-23', label: '' },
      { id: 'zone-24', label: '' },
      { id: 'zone-25', label: '●' },  // TRUE CENTER - special icon
      { id: 'zone-26', label: '' },
      { id: 'zone-27', label: '' },
      { id: 'zone-28', label: '' },
      // Row 5
      { id: 'zone-29', label: '' },
      { id: 'zone-30', label: '' },
      { id: 'zone-31', label: '' },
      { id: 'zone-32', label: '' },
      { id: 'zone-33', label: '' },
      { id: 'zone-34', label: '' },
      { id: 'zone-35', label: '' },
      // Row 6
      { id: 'zone-36', label: '' },
      { id: 'zone-37', label: '' },
      { id: 'zone-38', label: '' },
      { id: 'zone-39', label: '' },
      { id: 'zone-40', label: '' },
      { id: 'zone-41', label: '' },
      { id: 'zone-42', label: '' },
      // Row 7 (bottom)
      { id: 'zone-43', label: '' },
      { id: 'zone-44', label: '' },
      { id: 'zone-45', label: '' },
      { id: 'zone-46', label: '' },
      { id: 'zone-47', label: '' },
      { id: 'zone-48', label: '' },
      { id: 'zone-49', label: '' }
    ];
    
    // Zone 25 is true center (animated by default)
    
    zonePositions.forEach(zone => {
      const isSelected = currentPosition === zone.id || 
        (currentPosition === 'center' && ['upper-center-left', 'upper-center-right', 'lower-center-left', 'lower-center-right'].includes(zone.id));
      const zoneBtn = el('button', {
        type: 'button',
        class: 'cc-zone-btn' + (isSelected ? ' selected' : ''),
        'data-zone': zone.id,
        style: {
          background: isSelected ? '#3b82f6' : '#334155',
          border: 'none',
          borderRadius: '3px',
          cursor: 'pointer',
          color: isSelected ? '#fff' : '#94a3b8',
          fontSize: '10px',
          transition: 'all 0.15s ease'
        }
      }, zone.label);
      
      zoneBtn.addEventListener('click', () => {
        // Update selection
        zoneGrid.querySelectorAll('.cc-zone-btn').forEach(btn => {
          btn.classList.remove('selected');
          btn.style.background = '#334155';
          btn.style.color = '#94a3b8';
        });
        zoneBtn.classList.add('selected');
        zoneBtn.style.background = '#3b82f6';
        zoneBtn.style.color = '#fff';
        
        // Store selected zone ID
        zoneGrid.dataset.selectedZone = zone.id;
        
        // De-highlight Center button when a zone is selected
        const centerBtn = document.getElementById('cc-mgr-logo-center-btn');
        if (centerBtn) centerBtn.classList.remove('primary');
      });
      
      zoneGrid.appendChild(zoneBtn);
    });
    
    // Add "Center" button below grid with extra spacing
    const centerBtn = el('button', {
      type: 'button',
      class: 'cc-modal-btn' + (currentPosition === 'center' ? ' primary' : ''),
      id: 'cc-mgr-logo-center-btn',
      style: { marginTop: '16px', width: '210px' }
    }, '⬤ Center (Animated)');
    
    centerBtn.addEventListener('click', () => {
      zoneGrid.querySelectorAll('.cc-zone-btn').forEach(btn => {
        btn.classList.remove('selected');
        btn.style.background = '#334155';
        btn.style.color = '#94a3b8';
      });
      zoneGrid.dataset.selectedZone = 'center';
      centerBtn.classList.add('primary');
      
      // "Center (Animated)" should also enable animation - set to float-pulse if currently none
      const animSelect = appearancePanel.querySelector('#cc-mgr-logo-animation');
      if (animSelect && animSelect.value === 'none') {
        animSelect.value = 'float-pulse';
      }
    });
    
    // If not center, remove primary from center button
    if (currentPosition !== 'center') {
      centerBtn.classList.remove('primary');
    }
    
    zoneGrid.dataset.selectedZone = currentPosition;
    
    const logoPositionRow = el('div', { class: 'cc-form-row', style: { alignItems: 'flex-start' } }, [
      el('label', { style: { paddingTop: '4px' } }, 'Logo Position'),
      el('div', { style: { display: 'flex', flexDirection: 'column' } }, [zoneGrid, centerBtn])
    ]);
    
    // Logo Animation Type dropdown
    // Default to 'float-pulse' to match legacy Roku behavior (animated center logo)
    const currentAnimation = editingScene?.branding?.logo_animation ?? 'float-pulse';
    const animationSelect = el('select', { id: 'cc-mgr-logo-animation' }, [
      el('option', { value: 'none' }, 'None (Static)'),
      el('option', { value: 'fade' }, 'Fade In/Out'),
      el('option', { value: 'float' }, 'Float (Gentle Movement)'),
      el('option', { value: 'pulse' }, 'Pulse (Scale + Opacity)'),
      el('option', { value: 'float-pulse' }, 'Float + Pulse (Combined)'),
      el('option', { value: 'spin' }, 'Spin (Horizontal Flip)')
    ]);
    animationSelect.value = currentAnimation;
    
    const logoAnimationRow = el('div', { class: 'cc-form-row' }, [
      el('label', {}, 'Logo Animation'),
      animationSelect
    ]);
    
    // Animation Speed slider (only visible when animation is not 'none')
    const currentSpeed = editingScene?.branding?.logo_animation_speed ?? 50;
    const speedLabel = el('span', { id: 'cc-mgr-logo-speed-label' }, getSpeedLabel(currentSpeed));
    const speedSlider = el('input', { 
      type: 'range', 
      id: 'cc-mgr-logo-animation-speed', 
      min: '10', 
      max: '100', 
      value: currentSpeed, 
      style: { flex: '1', margin: '0 10px' } 
    });
    
    const logoAnimationSpeedRow = el('div', { 
      class: 'cc-form-row', 
      id: 'cc-mgr-logo-animation-speed-row',
      style: { alignItems: 'center', display: currentAnimation === 'none' ? 'none' : 'flex' } 
    }, [
      el('label', {}, 'Animation Speed'),
      speedSlider,
      speedLabel
    ]);
    
    // Helper function to convert speed value to label
    function getSpeedLabel(val) {
      if (val <= 25) return 'Slow';
      if (val <= 50) return 'Medium';
      if (val <= 75) return 'Fast';
      return 'Very Fast';
    }
    
    // Show/hide speed slider based on animation selection
    animationSelect.addEventListener('change', () => {
      const speedRow = appearancePanel.querySelector('#cc-mgr-logo-animation-speed-row');
      if (speedRow) {
        speedRow.style.display = animationSelect.value === 'none' ? 'none' : 'flex';
      }
    });
    
    // Speed slider live update
    speedSlider.addEventListener('input', () => {
      const label = appearancePanel.querySelector('#cc-mgr-logo-speed-label');
      if (label) label.textContent = getSpeedLabel(parseInt(speedSlider.value, 10));
    });
    
    // Background image picker (also serves as fallback for video backgrounds)
    const bgRow = el('div', { class: 'cc-form-row' }, [
      el('label', {}, 'Background Image'),
      el('input', { type: 'text', placeholder: 'Image URL (also used as video fallback)...', id: 'cc-mgr-bg-url', value: (() => { 
        const bg = editingScene?.background || {}; 
        // Check fallback_url first (for video backgrounds)
        if (bg?.fallback_url) return bg.fallback_url;
        if (Array.isArray(bg.layers)) { 
          const layer = bg.layers.find(l => l.kind === 'image' || l.kind === 'static-image'); 
          return (layer?.image?.src || layer?.image?.url || '');
        } 
        if (bg?.image?.src || bg?.image?.url) return bg.image.src || bg.image.url; 
        if (Array.isArray(bg?.sources) && bg.sources[0]) return bg.sources[0]; 
        return ''; 
      })() }),
      el('button', { class: 'cc-modal-btn', id: 'cc-mgr-pick-bg', style: { flex: '0 0 auto' } }, 'Choose…')
    ]);
    
    // Fit select
    const fitRow = el('div', { class: 'cc-form-row' }, [
      el('label', {}, 'Background Fit'),
      el('select', { id: 'cc-mgr-bg-fit' }, [
        el('option', { value: 'cover' }, 'Cover (fill, may crop)'),
        el('option', { value: 'contain' }, 'Contain (fit, may letterbox)'),
        el('option', { value: 'fill' }, 'Fill (stretch)')
      ])
    ]);
    
    // ─────────────────────────────────────────────────────────────────────────────
    // BACKGROUND VIDEO SECTION (plays muted behind content, loops by default)
    // ─────────────────────────────────────────────────────────────────────────────
    const videoSectionHeader = el('div', { style: { marginTop: '20px', marginBottom: '8px', paddingTop: '12px', borderTop: '1px solid #334155' } }, [
      el('strong', { style: { color: '#93c5fd' } }, '🎬 Background Video (Optional)'),
      el('div', { style: { fontSize: '11px', color: '#64748b', marginTop: '4px' } }, 'Video plays muted as an ambient background. Supports MP4, M4V, HLS (.m3u8). Takes precedence over background image when set.')
    ]);
    
    // Get existing video settings (stored in background object)
    const existingBg = editingScene?.background || {};
    const isVideoBackground = existingBg.type === 'video';
    const existingVideoUrl = isVideoBackground ? (existingBg.video_url || '') : '';
    const existingVideoLoop = isVideoBackground ? (existingBg.loop !== false) : true; // default true
    const existingVideoMuted = isVideoBackground ? (existingBg.muted !== false) : true; // default true
    
    // Background video URL (mute/loop options removed - Roku requires muxed HLS, can't play separate sources)
    const videoRow = el('div', { class: 'cc-form-row' }, [
      el('label', {}, 'Video URL'),
      el('input', { type: 'text', placeholder: 'https://example.com/video.m3u8 (muxed HLS)', id: 'cc-mgr-video-url', value: existingVideoUrl }),
      el('button', { class: 'cc-modal-btn', id: 'cc-mgr-pick-video', style: { flex: '0 0 auto' } }, 'Choose…')
    ]);
    // Note: Loop and Muted options removed - Roku can't play separate audio/video sources
    // Video must be muxed HLS with audio included in the stream
    
    // Advanced JSON toggle
    const advToggle = el('button', { class: 'cc-modal-btn', id: 'cc-mgr-adv-toggle', style: { marginTop: '12px' } }, 'Show Advanced JSON');
    const advWrap = el('div', { id: 'cc-mgr-adv-wrap', style: { display: 'none', marginTop: '12px' } });
    const bIn = el('textarea', { rows: '4', id: 'cc-mgr-branding-json', style: { width: '100%', fontFamily: 'ui-monospace, monospace', fontSize: '11px', background: '#1e293b', color: '#e2e8f0', border: '1px solid #334155', borderRadius: '6px', padding: '8px' } }, JSON.stringify(editingScene.branding || {}, null, 2));
    const bgIn = el('textarea', { rows: '4', id: 'cc-mgr-bg-json', style: { width: '100%', fontFamily: 'ui-monospace, monospace', fontSize: '11px', background: '#1e293b', color: '#e2e8f0', border: '1px solid #334155', borderRadius: '6px', padding: '8px', marginTop: '8px' } }, JSON.stringify(editingScene.background || {}, null, 2));
    advWrap.appendChild(el('label', { style: { marginBottom: '4px' } }, 'Branding (JSON)'));
    advWrap.appendChild(bIn);
    advWrap.appendChild(el('label', { style: { display: 'block', marginTop: '8px', marginBottom: '4px' } }, 'Background (JSON)'));
    advWrap.appendChild(bgIn);
    
    // Save button
    const saveRow = el('div', { style: { marginTop: '16px', textAlign: 'right' } }, [
      el('button', { class: 'cc-modal-btn primary', id: 'cc-mgr-save-appearance' }, 'Save Appearance')
    ]);
    
    appearancePanel.appendChild(editingInfo);
    appearancePanel.appendChild(brandingEnabledRow);
    appearancePanel.appendChild(logoRow);
    appearancePanel.appendChild(logoScaleRow);
    appearancePanel.appendChild(logoPositionRow);
    appearancePanel.appendChild(logoAnimationRow);
    appearancePanel.appendChild(logoAnimationSpeedRow);
    appearancePanel.appendChild(bgRow);
    appearancePanel.appendChild(fitRow);
    appearancePanel.appendChild(videoSectionHeader);
    appearancePanel.appendChild(videoRow);
    // videoLoopRow and videoMutedRow removed - Roku requires muxed HLS
    appearancePanel.appendChild(advToggle);
    appearancePanel.appendChild(advWrap);
    appearancePanel.appendChild(saveRow);
    
    // Toggle advanced JSON
    advToggle.addEventListener('click', () => {
      const showing = advWrap.style.display !== 'none';
      advWrap.style.display = showing ? 'none' : 'block';
      advToggle.textContent = showing ? 'Show Advanced JSON' : 'Hide Advanced JSON';
    });
    
    // Logo scale slider live update
    appearancePanel.querySelector('#cc-mgr-logo-scale')?.addEventListener('input', (e) => {
      const val = e.target.value;
      appearancePanel.querySelector('#cc-mgr-logo-scale-label').textContent = `${val}%`;
    });
    
    // Media pickers
    const openMedia = (onSelect) => {
      if (!window.wp || !wp.media) { alert('Media library not available'); return; }
      const frame = wp.media({ title: 'Select Image', multiple: false, library: { type: 'image' } });
      frame.on('select', function () { 
        const a = frame.state().get('selection').first().toJSON(); 
        if (a && a.url) onSelect(a.url, a); 
      });
      frame.open();
    };
    
    appearancePanel.querySelector('#cc-mgr-pick-logo')?.addEventListener('click', (e) => { 
      e.preventDefault(); 
      openMedia((url) => { appearancePanel.querySelector('#cc-mgr-logo-url').value = url; }); 
    });
    appearancePanel.querySelector('#cc-mgr-pick-bg')?.addEventListener('click', (e) => { 
      e.preventDefault(); 
      openMedia((url) => { appearancePanel.querySelector('#cc-mgr-bg-url').value = url; }); 
    });
    
    // Video picker (allows video files)
    const openVideoMedia = (onSelect) => {
      if (!window.wp || !wp.media) { alert('Media library not available'); return; }
      const frame = wp.media({ title: 'Select Video', multiple: false, library: { type: 'video' } });
      frame.on('select', function () { 
        const a = frame.state().get('selection').first().toJSON(); 
        if (a && a.url) onSelect(a.url, a); 
      });
      frame.open();
    };
    appearancePanel.querySelector('#cc-mgr-pick-video')?.addEventListener('click', (e) => { 
      e.preventDefault(); 
      openVideoMedia((url) => { appearancePanel.querySelector('#cc-mgr-video-url').value = url; }); 
    });
    
    // Save appearance - saves to editingScene, updates stage only if editingScene is active
    appearancePanel.querySelector('#cc-mgr-save-appearance').addEventListener('click', async () => {
      // Warn if editing the active scene
      const isEditingActive = editingScene.is_active == 1 || editingScene.is_active === true || editingScene.is_active === '1';
      if (isEditingActive) {
        const confirmed = confirm('⚠️ You are editing the ACTIVE scene.\n\nSaving these changes will immediately push them to your Roku app.\n\nContinue?');
        if (!confirmed) return;
      }
      
      try {
        setStageStatus(root, 'saving', 'Saving…');
        
        // Branding enabled toggle
        const brandingEnabledChecked = appearancePanel.querySelector('#cc-mgr-branding-enabled')?.checked !== false;
        
        const logoUrl = appearancePanel.querySelector('#cc-mgr-logo-url').value.trim();
        const logoScale = parseInt(appearancePanel.querySelector('#cc-mgr-logo-scale').value, 10) || 70;
        const logoPositionGrid = appearancePanel.querySelector('#cc-mgr-logo-position-grid');
        const logoPosition = logoPositionGrid?.dataset.selectedZone || 'center';
        const logoAnimation = appearancePanel.querySelector('#cc-mgr-logo-animation')?.value || 'none';
        const logoAnimationSpeed = parseInt(appearancePanel.querySelector('#cc-mgr-logo-animation-speed')?.value, 10) || 50;
        const bgUrl = appearancePanel.querySelector('#cc-mgr-bg-url').value.trim();
        const fit = appearancePanel.querySelector('#cc-mgr-bg-fit').value || 'cover';
        
        // Background video settings (loop/muted removed - Roku requires muxed HLS)
        const videoUrl = appearancePanel.querySelector('#cc-mgr-video-url')?.value.trim() || '';
        
        let branding = null, background = null;
        
        // Get logo dimensions for accurate Roku positioning (some devices can't detect dimensions)
        let logoDimensions = null;
        if (logoUrl) {
          logoDimensions = await getImageDimensions(logoUrl);
        }
        
        if (advWrap.style.display !== 'none') {
          try { branding = JSON.parse(bIn.value || '{}'); } catch (e) { alert('Branding JSON invalid: ' + e.message); setStageStatus(root, 'clear'); return; }
          try { background = JSON.parse(bgIn.value || '{}'); } catch (e) { alert('Background JSON invalid: ' + e.message); setStageStatus(root, 'clear'); return; }
          // Still apply enabled flag from toggle even in advanced mode
          branding.enabled = brandingEnabledChecked;
        } else {
          // Build branding object with logo settings
          const brandingBase = {
            enabled: brandingEnabledChecked,
            logo_scale: logoScale, 
            logo_position: logoPosition, 
            logo_animation: logoAnimation, 
            logo_animation_speed: logoAnimationSpeed
          };
          
          branding = logoUrl 
            ? { 
                ...brandingBase,
                logo: { src: logoUrl }, 
                ...(logoDimensions ? { logo_width: logoDimensions.width, logo_height: logoDimensions.height } : {})
              } 
            : { ...(editingScene.branding || {}), ...brandingBase };
          
          // Build background object - video takes precedence over image, but preserve image as fallback
          if (videoUrl) {
            // Muxed HLS live stream - audio is in the stream
            // IMPORTANT: Preserve bgUrl as fallback_url for when video fails or podcast is playing
            background = {
              type: 'video',
              video_url: videoUrl,
              ...(bgUrl ? { fallback_url: bgUrl } : {})
            };
          } else if (bgUrl) {
            // Image-only background
            background = { type: 'image', sources: [bgUrl], fit };
          } else {
            // BUGFIX: When both video and image URLs are cleared, explicitly clear the background
            // Previously this preserved existing background, causing video to persist after removal
            background = { type: 'image', sources: [] };
          }
        }
        
        const base = (window.castconductorScenesAjax || {}).rest_url || '';
        await fetch(base + `castconductor/v5/scenes/${editingScene.id}`, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': (window.castconductorScenesAjax || {}).nonce },
          body: JSON.stringify({ branding, background })
        }).then(r => { if (!r.ok) throw new Error('HTTP ' + r.status); });
        
        // Update editingScene data
        editingScene.branding = branding;
        editingScene.background = background;
        
        // Only update stage if we're editing the active scene
        if (editingScene.id === scene?.id) {
          scene.branding = branding;
          scene.background = background;
          applyBackground(root, scene);
          applyBranding(root, scene);
        }
        
        setStageStatus(root, 'saved', 'Appearance saved');
        setTimeout(() => setStageStatus(root, 'clear'), 900);
      } catch (e) { 
        console.error(e); 
        setStageStatus(root, 'error', 'Failed to save'); 
      }
    });
  };
  
  // ─────────────────────────────────────────────────────────────────────────────
  // CONTAINERS TAB - with pending state, remove buttons, and Save - Uses editingScene
  // ─────────────────────────────────────────────────────────────────────────────
  const buildContainersTab = async () => {
    containersPanel.innerHTML = '';
    
    if (!editingScene?.id) {
      containersPanel.appendChild(el('div', { style: { color: '#64748b', textAlign: 'center', padding: '20px' } }, 'No scene selected. Select a scene from the Scenes tab first.'));
      return;
    }
    
    // Show which scene we're editing (with warning if active)
    const isActive = editingScene.id === scene?.id;
    const editingInfo = el('div', { style: { marginBottom: '12px', padding: '8px', background: isActive ? '#7c2d12' : '#1e3a5f', borderRadius: '6px', fontSize: '12px', color: isActive ? '#fed7aa' : '#93c5fd' } }, [
      el('span', {}, `Editing containers for: `),
      el('strong', {}, editingScene.name || `Scene #${editingScene.id}`),
      ...(isActive ? [el('span', { style: { marginLeft: '8px', color: '#fbbf24' } }, '⚠️ ACTIVE – changes push to Roku')] : [])
    ]);
    
    // Fetch scene-specific containers (only those explicitly assigned)
    const base = (window.castconductorScenesAjax || {}).rest_url || '';
    let sceneContainers = [];
    try {
      const resp = await fetchJSON(base + `castconductor/v5/scenes/${editingScene.id}/containers`);
      sceneContainers = unwrapApi(resp) || [];
    } catch (e) {
      console.warn('[SceneManager] Failed to fetch scene containers:', e);
    }
    
    // Container presets lookup for matching names
    const presetMap = {
      'lower_third': 'Lower Third',
      'upper_third': 'Upper Third', 
      'center_third': 'Center Third',
      'full_screen': 'Full Screen',
      'left_half': 'Left Half',
      'right_half': 'Right Half',
      'top_half': 'Top Half',
      'bottom_half': 'Bottom Half',
      'top_left_quarter': 'Top Left',
      'top_right_quarter': 'Top Right',
      'bottom_left_quarter': 'Bottom Left',
      'bottom_right_quarter': 'Bottom Right',
      'horizontal_first_quarter': 'Horizontal 1st Quarter',
      'horizontal_second_quarter': 'Horizontal 2nd Quarter',
      'horizontal_third_quarter': 'Horizontal 3rd Quarter',
      'horizontal_fourth_quarter': 'Horizontal 4th Quarter',
      // Featured + Grid
      'featured_grid_hero': 'Featured Hero',
      'featured_grid_item_1': 'Grid Item 1',
      'featured_grid_item_2': 'Grid Item 2',
      'featured_grid_item_3': 'Grid Item 3',
      'featured_grid_item_4': 'Grid Item 4'
    };
    
    // Initialize pending containers from what's currently assigned
    // Now the API returns name, key, and master_rect directly
    pendingContainers = sceneContainers.map(sc => {
      // Prefer API-provided name/key, fall back to local lookup
      const key = sc.key || containers.find(c => c.container_id === sc.container_id)?.key || '';
      const name = sc.name || presetMap[key] || containers.find(c => c.container_id === sc.container_id)?.name || `Container #${sc.container_id}`;
      const rect = sc.overrides?.rect || sc.master_rect || containers.find(c => c.container_id === sc.container_id)?.rect || { x: 0, y: 0, width: 1280, height: 720 };
      
      return {
        container_id: sc.container_id,
        key,
        name,
        rect,
        enabled: sc.enabled !== 0,
        zones: sc.zones || {},
        overrides: sc.overrides || {}
      };
    });
    
    // Container presets for adding
    const presets = [
      { key: 'lower_third', name: 'Lower Third', desc: 'Bottom 240px', rect: { x: 0, y: 480, width: 1280, height: 240 } },
      { key: 'upper_third', name: 'Upper Third', desc: 'Top 240px', rect: { x: 0, y: 0, width: 1280, height: 240 } },
      { key: 'center_third', name: 'Center Third', desc: 'Middle 240px', rect: { x: 0, y: 240, width: 1280, height: 240 } },
      { key: 'full_screen', name: 'Full Screen', desc: '1280×720', rect: { x: 0, y: 0, width: 1280, height: 720 } },
      { key: 'left_half', name: 'Left Half', desc: '640×720', rect: { x: 0, y: 0, width: 640, height: 720 } },
      { key: 'right_half', name: 'Right Half', desc: '640×720', rect: { x: 640, y: 0, width: 640, height: 720 } },
      { key: 'top_half', name: 'Top Half', desc: '1280×360', rect: { x: 0, y: 0, width: 1280, height: 360 } },
      { key: 'bottom_half', name: 'Bottom Half', desc: '1280×360', rect: { x: 0, y: 360, width: 1280, height: 360 } },
      { key: 'top_left_quarter', name: 'Top Left', desc: '640×360', rect: { x: 0, y: 0, width: 640, height: 360 } },
      { key: 'top_right_quarter', name: 'Top Right', desc: '640×360', rect: { x: 640, y: 0, width: 640, height: 360 } },
      { key: 'bottom_left_quarter', name: 'Bottom Left', desc: '640×360', rect: { x: 0, y: 360, width: 640, height: 360 } },
      { key: 'bottom_right_quarter', name: 'Bottom Right', desc: '640×360', rect: { x: 640, y: 360, width: 640, height: 360 } },
      { key: 'horizontal_first_quarter', name: 'Horizontal 1st Quarter', desc: '1280×180', rect: { x: 0, y: 0, width: 1280, height: 180 } },
      { key: 'horizontal_second_quarter', name: 'Horizontal 2nd Quarter', desc: '1280×180', rect: { x: 0, y: 180, width: 1280, height: 180 } },
      { key: 'horizontal_third_quarter', name: 'Horizontal 3rd Quarter', desc: '1280×180', rect: { x: 0, y: 360, width: 1280, height: 180 } },
      { key: 'horizontal_fourth_quarter', name: 'Horizontal 4th Quarter', desc: '1280×180', rect: { x: 0, y: 540, width: 1280, height: 180 } },
      // Featured + Grid (hero 1280×480 top + 4 grid items 320×240 bottom)
      { key: 'featured_grid_hero', name: 'Featured Hero', desc: '1280×480 top', rect: { x: 0, y: 0, width: 1280, height: 480 } },
      { key: 'featured_grid_item_1', name: 'Grid Item 1', desc: '320×240 bottom-left', rect: { x: 0, y: 480, width: 320, height: 240 } },
      { key: 'featured_grid_item_2', name: 'Grid Item 2', desc: '320×240', rect: { x: 320, y: 480, width: 320, height: 240 } },
      { key: 'featured_grid_item_3', name: 'Grid Item 3', desc: '320×240', rect: { x: 640, y: 480, width: 320, height: 240 } },
      { key: 'featured_grid_item_4', name: 'Grid Item 4', desc: '320×240 bottom-right', rect: { x: 960, y: 480, width: 320, height: 240 } }
    ];
    
    // Current containers list with remove buttons
    const containerListLabel = el('label', { style: { marginBottom: '8px', display: 'block' } }, 'Containers in this scene:');
    const containerList = el('div', { class: 'cc-container-list', id: 'cc-mgr-container-list', style: { marginBottom: '16px', maxHeight: '200px', overflowY: 'auto' } });
    
    const renderContainerList = () => {
      containerList.innerHTML = '';
      if (pendingContainers.length === 0) {
        containerList.appendChild(el('div', { style: { color: '#64748b', padding: '12px', textAlign: 'center', background: '#1e293b', borderRadius: '6px' } }, 'No containers. Add some below.'));
      } else {
        pendingContainers.forEach((c, idx) => {
          const item = el('div', { class: 'cc-container-item', style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '8px 12px', background: '#1e293b', borderRadius: '6px', marginBottom: '6px' } }, [
            el('div', { style: { display: 'flex', alignItems: 'center', gap: '12px' } }, [
              el('span', { class: 'cc-container-name', style: { fontWeight: '500' } }, c.name || c.key || `Container #${c.container_id}`),
              el('span', { class: 'cc-container-dims', style: { color: '#64748b', fontSize: '12px' } }, `${c.rect?.width || '?'}×${c.rect?.height || '?'}`)
            ]),
            el('button', { 
              class: 'cc-remove-btn', 
              title: 'Remove from scene',
              style: { background: 'transparent', border: 'none', color: '#ef4444', cursor: 'pointer', fontSize: '16px', padding: '4px 8px' }
            }, '✕')
          ]);
          
          // Remove button handler
          item.querySelector('.cc-remove-btn').addEventListener('click', () => {
            pendingContainers.splice(idx, 1);
            renderContainerList();
          });
          
          containerList.appendChild(item);
        });
      }
    };
    
    // Divider
    const divider = el('div', { class: 'cc-modal-divider', style: { margin: '16px 0', borderTop: '1px solid #334155' } });
    
    // Add container section
    const addLabel = el('label', { style: { marginBottom: '8px', display: 'block' } }, 'Add a container preset:');
    
    const select = el('select', { class: 'cc-preset-select', id: 'cc-mgr-container-preset', style: { flex: '1' } }, [
      el('option', { value: '' }, '— Select a preset —')
    ]);
    presets.forEach(p => {
      select.appendChild(el('option', { value: p.key }, `${p.name} (${p.desc})`));
    });
    
    const addRow = el('div', { style: { display: 'flex', gap: '8px' } }, [
      select,
      el('button', { class: 'cc-modal-btn', id: 'cc-mgr-add-container', style: { flex: '0 0 auto' } }, '+ Add')
    ]);
    
    // Save button - commits all pending changes and closes modal
    const saveRow = el('div', { style: { marginTop: '20px', display: 'flex', justifyContent: 'flex-end', gap: '8px' } }, [
      el('button', { class: 'cc-modal-btn primary', id: 'cc-mgr-save-containers' }, 'Save Containers')
    ]);
    
    containersPanel.appendChild(editingInfo);
    containersPanel.appendChild(containerListLabel);
    containersPanel.appendChild(containerList);
    containersPanel.appendChild(divider);
    containersPanel.appendChild(addLabel);
    containersPanel.appendChild(addRow);
    containersPanel.appendChild(saveRow);
    
    // Initial render
    renderContainerList();
    
    // Add container to pending list (doesn't save yet)
    containersPanel.querySelector('#cc-mgr-add-container').addEventListener('click', () => {
      const key = select.value;
      if (!key) {
        select.style.borderColor = '#ef4444';
        setTimeout(() => select.style.borderColor = '', 1500);
        return;
      }
      const preset = presets.find(p => p.key === key);
      if (!preset) return;
      
      // Check if already added
      if (pendingContainers.some(c => c.key === key)) {
        alert(`"${preset.name}" is already in this scene.`);
        return;
      }
      
      // Find or create container_id from master containers
      const master = containers.find(c => c.key === key);
      const container_id = master?.container_id || 0;
      
      pendingContainers.push({
        container_id,
        key,
        name: preset.name,
        rect: preset.rect,
        enabled: true,
        zones: {},
        overrides: { rect: preset.rect }
      });
      
      renderContainerList();
      select.value = '';
    });
    
    // Save all pending containers to editingScene
    containersPanel.querySelector('#cc-mgr-save-containers').addEventListener('click', async () => {
      // Warn if editing the active scene
      const isEditingActive = editingScene.is_active == 1 || editingScene.is_active === true || editingScene.is_active === '1';
      if (isEditingActive) {
        const confirmed = confirm('⚠️ You are editing the ACTIVE scene.\n\nSaving these changes will immediately push them to your Roku app.\n\nContinue?');
        if (!confirmed) return;
      }
      
      try {
        setStageStatus(root, 'saving', 'Saving containers…');
        
        // First, we need to clear existing containers for this scene and save only pending ones
        // The API expects container_id - if a preset doesn't have a master container, we need to create one
        const payload = {
          containers: pendingContainers.map(c => ({
            container_id: c.container_id || 0,
            key: c.key,
            overrides: { rect: c.rect },
            zones: c.zones || {},
            enabled: c.enabled ? 1 : 0
          }))
        };
        
        // Clear and set new containers for the EDITING scene
        const base = (window.castconductorScenesAjax || {}).rest_url || '';
        
        // First clear all existing
        await fetch(base + `castconductor/v5/scenes/${editingScene.id}/containers/clear`, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': (window.castconductorScenesAjax || {}).nonce }
        }).catch(() => {}); // Endpoint may not exist yet
        
        // Then add the pending ones
        if (pendingContainers.length > 0) {
          const res = await fetch(base + `castconductor/v5/scenes/${editingScene.id}/containers`, {
            method: 'PUT',
            headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': (window.castconductorScenesAjax || {}).nonce },
            body: JSON.stringify(payload)
          });
          if (!res.ok) throw new Error('HTTP ' + res.status);
        }
        
        setStageStatus(root, 'saved', 'Containers saved');
        setTimeout(() => setStageStatus(root, 'clear'), 900);
        
        // Only refresh stage if we edited the active scene
        if (editingScene.id === scene?.id) {
          hide();
          boot();
        }
      } catch (e) {
        console.error('[SceneManager] Save containers failed:', e);
        setStageStatus(root, 'error', 'Failed to save containers');
      }
    });
  };
  
  // ─────────────────────────────────────────────────────────────────────────────
  // OVERLAYS TAB - Container overlay/navigation settings (Redesigned UX)
  // ─────────────────────────────────────────────────────────────────────────────
  const buildOverlaysTab = async () => {
    overlaysPanel.innerHTML = '';
    
    // Fetch all containers
    const base = (window.castconductorScenesAjax || {}).rest_url || '';
    let allContainers = [];
    try {
      const resp = await fetchJSON(base + 'castconductor/v5/containers?per_page=100');
      allContainers = unwrapApi(resp) || [];
    } catch (e) {
      console.warn('[SceneManager] Failed to fetch containers:', e);
    }
    
    if (allContainers.length === 0) {
      overlaysPanel.appendChild(el('div', { style: { color: '#64748b', textAlign: 'center', padding: '20px' } }, 'No containers found. Create containers in the Containers tab first.'));
      return;
    }
    
    // Find which containers are currently configured as overlays
    const overlayContainers = allContainers.filter(c => {
      if (!c.overlay) return false;
      const overlay = typeof c.overlay === 'string' ? JSON.parse(c.overlay) : c.overlay;
      return overlay.display_mode === 'overlay';
    });
    
    // Info header with explanation
    const infoHeader = el('div', { style: { marginBottom: '16px', padding: '12px', background: '#1e3a5f', borderRadius: '6px', fontSize: '12px', color: '#93c5fd' } }, [
      el('strong', {}, '🔗 Overlay Navigation'),
      el('p', { style: { margin: '8px 0 0 0', lineHeight: '1.5' } }, 
        'Overlays are containers that stay hidden until the viewer presses a remote button (like UP or Menu). ' +
        'Perfect for browse menus, playlist navigation, or info panels that slide in on demand.'
      )
    ]);
    
    // Summary of current overlay configuration
    const summarySection = el('div', { style: { marginBottom: '16px', padding: '12px', background: '#0f172a', borderRadius: '6px' } });
    
    if (overlayContainers.length === 0) {
      summarySection.appendChild(el('div', { style: { color: '#64748b', textAlign: 'center' } }, [
        el('span', { style: { fontSize: '24px', display: 'block', marginBottom: '8px' } }, '📭'),
        el('span', {}, 'No overlays configured yet. Choose a container below to set up as an overlay.')
      ]));
    } else {
      const listItems = overlayContainers.map(c => {
        const overlay = typeof c.overlay === 'string' ? JSON.parse(c.overlay) : c.overlay;
        return el('div', { style: { display: 'flex', alignItems: 'center', gap: '8px', padding: '6px 8px', background: '#1e293b', borderRadius: '4px', marginBottom: '4px' } }, [
          el('span', { style: { color: '#8b5cf6' } }, '🔗'),
          el('strong', { style: { color: '#e2e8f0' } }, c.name || `Container #${c.id}`),
          el('span', { style: { color: '#64748b', fontSize: '11px' } }, `• ${(overlay.trigger || 'none').toUpperCase()} button • ${overlay.position || 'bottom'}`)
        ]);
      });
      summarySection.appendChild(el('div', { style: { marginBottom: '8px', fontSize: '12px', color: '#94a3b8' } }, `${overlayContainers.length} overlay(s) configured:`));
      listItems.forEach(item => summarySection.appendChild(item));
    }
    
    // Divider
    const divider = el('div', { style: { height: '1px', background: '#334155', margin: '16px 0' } });
    
    // Container configuration section
    const configSection = el('div', { style: { marginBottom: '16px' } });
    const configHeader = el('div', { style: { marginBottom: '12px', fontSize: '13px', fontWeight: '600', color: '#e2e8f0' } }, 'Configure Container Overlay Settings');
    
    // Container selector
    const containerSelect = el('select', { class: 'cc-overlay-container-select', style: { width: '100%', padding: '8px', background: '#0f172a', border: '1px solid #334155', borderRadius: '4px', color: '#e2e8f0', marginBottom: '12px' } }, [
      el('option', { value: '' }, '— Select a container to configure —'),
      ...allContainers.map(c => {
        const overlay = c.overlay ? (typeof c.overlay === 'string' ? JSON.parse(c.overlay) : c.overlay) : {};
        const isOverlay = overlay.display_mode === 'overlay';
        const label = `${c.name || 'Container #' + c.id} ${isOverlay ? '🔗' : ''}`;
        return el('option', { value: String(c.id) }, label);
      })
    ]);
    
    // Settings panel (hidden until container selected)
    const settingsPanel = el('div', { class: 'cc-overlay-settings-panel', style: { display: 'none', padding: '12px', background: '#1e293b', borderRadius: '8px', border: '2px solid #8b5cf6' } });
    
    // Build settings panel content
    const buildSettings = (container) => {
      settingsPanel.innerHTML = '';
      if (!container) return;
      
      const overlay = container.overlay ? (typeof container.overlay === 'string' ? JSON.parse(container.overlay) : container.overlay) : {};
      const defaults = { display_mode: 'visible', trigger: 'none', position: 'bottom', animation: 'slide', background_color: '#000000CC', dismiss_on_select: true, auto_dismiss_seconds: 0 };
      const current = { ...defaults, ...overlay };
      
      // Container name header
      const header = el('div', { style: { marginBottom: '12px', display: 'flex', justifyContent: 'space-between', alignItems: 'center' } }, [
        el('strong', { style: { color: '#e2e8f0' } }, container.name || `Container #${container.id}`),
        el('span', { style: { fontSize: '11px', color: '#64748b' } }, `${container.width || '?'}×${container.height || '?'}`)
      ]);
      
      // Enable/Disable as overlay toggle
      const enableRow = el('div', { style: { display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '12px', padding: '8px', background: '#0f172a', borderRadius: '4px' } });
      const enableCheckbox = el('input', { type: 'checkbox', style: { width: '18px', height: '18px' } });
      enableCheckbox.checked = current.display_mode === 'overlay';
      enableRow.appendChild(enableCheckbox);
      enableRow.appendChild(el('label', { style: { fontSize: '13px', color: '#e2e8f0', fontWeight: '500' } }, 'Enable as Overlay'));
      enableRow.appendChild(el('span', { style: { fontSize: '11px', color: '#64748b', marginLeft: 'auto' } }, 'Hidden until triggered'));
      
      // Overlay-specific settings (shown when enabled)
      const detailSettings = el('div', { style: { display: enableCheckbox.checked ? 'block' : 'none', paddingTop: '12px', borderTop: '1px solid #334155', marginTop: '12px' } });
      
      // Trigger button
      const triggerSelect = el('select', { class: 'trigger-select', style: { flex: '1', padding: '6px', background: '#0f172a', border: '1px solid #334155', borderRadius: '4px', color: '#e2e8f0' } }, 
        ['up', 'down', 'left', 'right', 'menu', 'ok', 'back', 'play'].map(t => el('option', { value: t }, t.toUpperCase()))
      );
      triggerSelect.value = current.trigger !== 'none' ? current.trigger : 'up';
      const triggerRow = el('div', { style: { display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '8px' } }, [
        el('label', { style: { fontSize: '12px', color: '#94a3b8', minWidth: '100px' } }, 'Trigger Button'),
        triggerSelect
      ]);
      
      // Position
      const positionSelect = el('select', { class: 'position-select', style: { flex: '1', padding: '6px', background: '#0f172a', border: '1px solid #334155', borderRadius: '4px', color: '#e2e8f0' } }, 
        ['bottom', 'top', 'left', 'right', 'center', 'fullscreen'].map(p => el('option', { value: p }, p.charAt(0).toUpperCase() + p.slice(1)))
      );
      positionSelect.value = current.position || 'bottom';
      const positionRow = el('div', { style: { display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '8px' } }, [
        el('label', { style: { fontSize: '12px', color: '#94a3b8', minWidth: '100px' } }, 'Position'),
        positionSelect
      ]);
      
      // Animation
      const animationSelect = el('select', { class: 'animation-select', style: { flex: '1', padding: '6px', background: '#0f172a', border: '1px solid #334155', borderRadius: '4px', color: '#e2e8f0' } }, 
        ['slide', 'fade', 'none'].map(a => el('option', { value: a }, a.charAt(0).toUpperCase() + a.slice(1)))
      );
      animationSelect.value = current.animation || 'slide';
      const animationRow = el('div', { style: { display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '8px' } }, [
        el('label', { style: { fontSize: '12px', color: '#94a3b8', minWidth: '100px' } }, 'Animation'),
        animationSelect
      ]);
      
      // Background color
      const bgColorInput = el('input', { type: 'color', class: 'bg-color-input', value: (current.background_color || '#000000CC').substring(0, 7), style: { width: '40px', height: '28px', padding: '0', border: 'none', borderRadius: '4px', cursor: 'pointer' } });
      const bgColorRow = el('div', { style: { display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '8px' } }, [
        el('label', { style: { fontSize: '12px', color: '#94a3b8', minWidth: '100px' } }, 'Background'),
        bgColorInput,
        el('span', { style: { fontSize: '11px', color: '#64748b' } }, 'Semi-transparent backdrop')
      ]);
      
      // Dismiss on select
      const dismissCheckbox = el('input', { type: 'checkbox', class: 'dismiss-checkbox', style: { width: '16px', height: '16px' } });
      dismissCheckbox.checked = current.dismiss_on_select !== false;
      const dismissRow = el('div', { style: { display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '8px' } }, [
        dismissCheckbox,
        el('label', { style: { fontSize: '12px', color: '#94a3b8' } }, 'Dismiss when user selects an item')
      ]);
      
      // Auto-dismiss timer
      const autoDismissInput = el('input', { type: 'number', class: 'auto-dismiss-input', value: String(current.auto_dismiss_seconds || 0), min: '0', max: '300', style: { width: '70px', padding: '6px', background: '#0f172a', border: '1px solid #334155', borderRadius: '4px', color: '#e2e8f0' } });
      const autoDismissRow = el('div', { style: { display: 'flex', alignItems: 'center', gap: '12px' } }, [
        el('label', { style: { fontSize: '12px', color: '#94a3b8', minWidth: '100px' } }, 'Auto-dismiss'),
        autoDismissInput,
        el('span', { style: { fontSize: '11px', color: '#64748b' } }, 'seconds (0 = never)')
      ]);
      
      detailSettings.appendChild(triggerRow);
      detailSettings.appendChild(positionRow);
      detailSettings.appendChild(animationRow);
      detailSettings.appendChild(bgColorRow);
      detailSettings.appendChild(dismissRow);
      detailSettings.appendChild(autoDismissRow);
      
      // Save button for this container
      const saveBtn = el('button', { class: 'cc-modal-btn primary', style: { marginTop: '12px', width: '100%' } }, 'Save Overlay Settings');
      
      saveBtn.addEventListener('click', async () => {
        try {
          setStageStatus(root, 'saving', 'Saving overlay…');
          
          const overlayPayload = {
            overlay: {
              display_mode: enableCheckbox.checked ? 'overlay' : 'visible',
              trigger: enableCheckbox.checked ? triggerSelect.value : 'none',
              position: positionSelect.value,
              animation: animationSelect.value,
              background_color: bgColorInput.value + 'CC',
              dismiss_on_select: dismissCheckbox.checked,
              auto_dismiss_seconds: parseInt(autoDismissInput.value || '0', 10)
            }
          };
          
          await fetch(base + `castconductor/v5/containers/${container.id}`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': (window.castconductorScenesAjax || {}).nonce },
            body: JSON.stringify(overlayPayload)
          });
          
          setStageStatus(root, 'saved', 'Overlay settings saved');
          setTimeout(() => setStageStatus(root, 'clear'), 900);
          
          // Refresh the tab to update summary
          buildOverlaysTab();
        } catch (e) {
          console.error('[SceneManager] Save overlay failed:', e);
          setStageStatus(root, 'error', 'Failed to save overlay');
        }
      });
      
      // Toggle detail settings when enable checkbox changes
      enableCheckbox.addEventListener('change', () => {
        detailSettings.style.display = enableCheckbox.checked ? 'block' : 'none';
      });
      
      settingsPanel.appendChild(header);
      settingsPanel.appendChild(enableRow);
      settingsPanel.appendChild(detailSettings);
      settingsPanel.appendChild(saveBtn);
      settingsPanel.style.display = 'block';
    };
    
    // Handle container selection
    containerSelect.addEventListener('change', () => {
      const selectedId = containerSelect.value;
      if (!selectedId) {
        settingsPanel.style.display = 'none';
        return;
      }
      const container = allContainers.find(c => String(c.id) === selectedId);
      buildSettings(container);
    });
    
    configSection.appendChild(configHeader);
    configSection.appendChild(containerSelect);
    configSection.appendChild(settingsPanel);
    
    // Assemble the panel
    overlaysPanel.appendChild(infoHeader);
    overlaysPanel.appendChild(summarySection);
    overlaysPanel.appendChild(divider);
    overlaysPanel.appendChild(configSection);
  };
  
  // ─────────────────────────────────────────────────────────────────────────────
  // SCHEDULING TAB - Scene scheduling controls - Uses editingScene
  // ─────────────────────────────────────────────────────────────────────────────
  const buildSchedulingTab = async () => {
    schedulingPanel.innerHTML = '';
    
    if (!editingScene?.id) {
      schedulingPanel.appendChild(el('div', { style: { color: '#64748b', textAlign: 'center', padding: '20px' } }, 'No scene selected. Select a scene from the Scenes tab first.'));
      return;
    }
    
    // Show which scene we're editing (with warning if active)
    const isActive = editingScene.id === scene?.id;
    const editingInfo = el('div', { style: { marginBottom: '12px', padding: '8px', background: isActive ? '#7c2d12' : '#1e3a5f', borderRadius: '6px', fontSize: '12px', color: isActive ? '#fed7aa' : '#93c5fd' } }, [
      el('span', {}, `Editing scheduling for: `),
      el('strong', {}, editingScene.name || `Scene #${editingScene.id}`),
      ...(isActive ? [el('span', { style: { marginLeft: '8px', color: '#fbbf24' } }, '⚠️ ACTIVE – changes push to Roku')] : [])
    ]);
    
    // Fetch current scene data to get scheduling fields
    const base = (window.castconductorScenesAjax || {}).rest_url || '';
    let sceneData = {};
    try {
      const resp = await fetchJSON(base + `castconductor/v5/scenes/${editingScene.id}`);
      sceneData = unwrapApi(resp) || {};
    } catch (e) {
      console.warn('[SceneManager] Failed to fetch scene data:', e);
    }
    
    // Parse schedule_days if it's a string
    let scheduleDays = sceneData.schedule_days || [];
    if (typeof scheduleDays === 'string') {
      try { scheduleDays = JSON.parse(scheduleDays); } catch (e) { scheduleDays = []; }
    }
    
    // Determine if scheduling is enabled (handle string "1" or "0" from DB)
    const isScheduleEnabled = sceneData.schedule_enabled == 1 || sceneData.schedule_enabled === true;
    const isRotationEnabled = sceneData.rotation_enabled == 1 || sceneData.rotation_enabled === true;
    
    // Scheduling enabled toggle
    const scheduleCheckbox = el('input', { 
      type: 'checkbox', 
      id: 'cc-mgr-schedule-enabled',
      style: { width: '18px', height: '18px' }
    });
    scheduleCheckbox.checked = isScheduleEnabled;
    
    const enabledRow = el('div', { class: 'cc-form-row', style: { marginBottom: '16px' } }, [
      el('label', { style: { display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer' } }, [
        scheduleCheckbox,
        el('span', {}, 'Enable Scheduling for this Scene')
      ])
    ]);
    
    // Date range
    const dateRow = el('div', { class: 'cc-form-row', style: { display: 'flex', gap: '12px', marginBottom: '12px' } }, [
      el('div', { style: { flex: '1' } }, [
        el('label', { style: { display: 'block', marginBottom: '4px', fontSize: '12px', color: '#94a3b8' } }, 'Start Date'),
        el('input', { 
          type: 'datetime-local', 
          id: 'cc-mgr-schedule-start',
          value: sceneData.schedule_start ? sceneData.schedule_start.replace(' ', 'T').substring(0, 16) : '',
          style: { width: '100%', padding: '8px', background: '#1e293b', border: '1px solid #334155', borderRadius: '6px', color: '#e2e8f0' }
        })
      ]),
      el('div', { style: { flex: '1' } }, [
        el('label', { style: { display: 'block', marginBottom: '4px', fontSize: '12px', color: '#94a3b8' } }, 'End Date'),
        el('input', { 
          type: 'datetime-local', 
          id: 'cc-mgr-schedule-end',
          value: sceneData.schedule_end ? sceneData.schedule_end.replace(' ', 'T').substring(0, 16) : '',
          style: { width: '100%', padding: '8px', background: '#1e293b', border: '1px solid #334155', borderRadius: '6px', color: '#e2e8f0' }
        })
      ])
    ]);
    
    // Time range (daily window)
    const timeRow = el('div', { class: 'cc-form-row', style: { display: 'flex', gap: '12px', marginBottom: '12px' } }, [
      el('div', { style: { flex: '1' } }, [
        el('label', { style: { display: 'block', marginBottom: '4px', fontSize: '12px', color: '#94a3b8' } }, 'Daily Start Time'),
        el('input', { 
          type: 'time', 
          id: 'cc-mgr-schedule-time-start',
          value: sceneData.schedule_time_start || '',
          style: { width: '100%', padding: '8px', background: '#1e293b', border: '1px solid #334155', borderRadius: '6px', color: '#e2e8f0' }
        })
      ]),
      el('div', { style: { flex: '1' } }, [
        el('label', { style: { display: 'block', marginBottom: '4px', fontSize: '12px', color: '#94a3b8' } }, 'Daily End Time'),
        el('input', { 
          type: 'time', 
          id: 'cc-mgr-schedule-time-end',
          value: sceneData.schedule_time_end || '',
          style: { width: '100%', padding: '8px', background: '#1e293b', border: '1px solid #334155', borderRadius: '6px', color: '#e2e8f0' }
        })
      ])
    ]);
    
    // Days of week checkboxes - create separately to set checked property correctly
    const daysOfWeek = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
    const dayCheckboxes = daysOfWeek.map(day => {
      const checkbox = el('input', { 
        type: 'checkbox', 
        value: day.toLowerCase(),
        class: 'cc-schedule-day'
      });
      checkbox.checked = Array.isArray(scheduleDays) && scheduleDays.includes(day.toLowerCase());
      return el('label', { style: { display: 'flex', alignItems: 'center', gap: '4px', cursor: 'pointer', padding: '4px 8px', background: '#1e293b', borderRadius: '4px' } }, [
        checkbox,
        el('span', { style: { fontSize: '13px' } }, day)
      ]);
    });
    
    const daysRow = el('div', { class: 'cc-form-row', style: { marginBottom: '12px' } }, [
      el('label', { style: { display: 'block', marginBottom: '8px', fontSize: '12px', color: '#94a3b8' } }, 'Days of Week (leave unchecked for all days)'),
      el('div', { style: { display: 'flex', gap: '8px', flexWrap: 'wrap' } }, dayCheckboxes)
    ]);
    
    // Timezone selector
    const timezones = [
      'America/Los_Angeles', 'America/Denver', 'America/Chicago', 'America/New_York',
      'America/Phoenix', 'America/Anchorage', 'Pacific/Honolulu',
      'UTC', 'Europe/London', 'Europe/Paris', 'Asia/Tokyo', 'Australia/Sydney'
    ];
    const tzRow = el('div', { class: 'cc-form-row', style: { marginBottom: '12px' } }, [
      el('label', { style: { display: 'block', marginBottom: '4px', fontSize: '12px', color: '#94a3b8' } }, 'Timezone'),
      el('select', { id: 'cc-mgr-schedule-timezone', style: { width: '100%', padding: '8px', background: '#1e293b', border: '1px solid #334155', borderRadius: '6px', color: '#e2e8f0' } },
        timezones.map(tz => el('option', { value: tz, selected: (sceneData.schedule_timezone || 'America/Los_Angeles') === tz ? 'selected' : undefined }, tz))
      )
    ]);
    
    // Priority (for overlapping schedules)
    const priorityRow = el('div', { class: 'cc-form-row', style: { marginBottom: '16px' } }, [
      el('label', { style: { display: 'block', marginBottom: '4px', fontSize: '12px', color: '#94a3b8' } }, 'Priority (higher = takes precedence)'),
      el('input', { 
        type: 'number', 
        id: 'cc-mgr-schedule-priority',
        value: String(sceneData.schedule_priority || 0),
        min: '0',
        max: '100',
        style: { width: '100px', padding: '8px', background: '#1e293b', border: '1px solid #334155', borderRadius: '6px', color: '#e2e8f0' }
      })
    ]);
    
    // Divider and rotation section
    const rotationDivider = el('div', { style: { margin: '16px 0', borderTop: '1px solid #334155' } });
    const rotationTitle = el('h3', { style: { margin: '0 0 12px 0', fontSize: '14px', color: '#94a3b8' } }, 'Scene Rotation');
    
    // Rotation percentage row (for weighted random selection)
    const rotationPercentage = parseFloat(sceneData.rotation_percentage) || 0;
    const rotationPctRow = el('div', { class: 'cc-form-row', style: { marginBottom: '12px' } }, [
      el('label', { style: { display: 'block', marginBottom: '4px', fontSize: '12px', color: '#94a3b8' } }, 
        `Rotation Weight: ${rotationPercentage}% — 0 = excluded from rotation`
      ),
      el('input', { 
        type: 'range', 
        id: 'cc-mgr-rotation-percentage',
        value: String(rotationPercentage),
        min: '0',
        max: '100',
        step: '5',
        style: { width: '100%' }
      }),
      el('div', { style: { display: 'flex', justifyContent: 'space-between', fontSize: '11px', color: '#64748b', marginTop: '4px' } }, [
        el('span', {}, '0% (off)'),
        el('span', {}, '50%'),
        el('span', {}, '100%')
      ])
    ]);
    
    // Update label when slider moves
    const pctSlider = rotationPctRow.querySelector('#cc-mgr-rotation-percentage');
    const pctLabel = rotationPctRow.querySelector('label');
    pctSlider.addEventListener('input', () => {
      pctLabel.textContent = `Rotation Weight: ${pctSlider.value}% — 0 = excluded from rotation`;
    });
    
    // Legacy rotation checkbox - now just shows info
    const rotationInfoRow = el('div', { class: 'cc-form-row', style: { marginBottom: '12px', padding: '10px', background: '#1e293b', borderRadius: '6px', fontSize: '12px', color: '#94a3b8' } }, [
      el('div', {}, [
        el('strong', {}, '📊 How Rotation Works: '),
        'Set a rotation weight (1-100%) for each scene you want in rotation. Weights are relative — two scenes at 50% each will show 50/50. Three scenes at 33% each will show roughly equally. Set to 0% to exclude a scene from rotation entirely.'
      ])
    ]);
    
    // Interval row (separate from checkbox)
    const rotationIntervalRow = el('div', { class: 'cc-form-row', style: { display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '12px' } }, [
      el('label', { style: { fontSize: '12px', color: '#94a3b8' } }, 'Rotation Interval:'),
      el('input', { 
        type: 'number', 
        id: 'cc-mgr-rotation-interval',
        value: String(sceneData.rotation_interval || 60),
        min: '5',
        style: { width: '80px', padding: '6px', background: '#1e293b', border: '1px solid #334155', borderRadius: '6px', color: '#e2e8f0' }
      }),
      el('span', { style: { fontSize: '12px', color: '#64748b' } }, 'seconds between scene changes')
    ]);
    
    // Save button
    const saveRow = el('div', { style: { marginTop: '20px', display: 'flex', justifyContent: 'flex-end' } }, [
      el('button', { class: 'cc-modal-btn primary', id: 'cc-mgr-save-scheduling' }, 'Save Scheduling')
    ]);
    
    // Info box
    const infoBox = el('div', { style: { marginTop: '16px', padding: '12px', background: '#1e3a5f', borderRadius: '6px', fontSize: '12px', color: '#93c5fd' } }, [
      el('strong', {}, '💡 Tip: '),
      'Use scheduling to display scenes at specific times (e.g., Christmas scene only on Dec 25). ',
      'Rotation allows weighted random selection between scenes. Interval controls how often Roku checks for scene changes.'
    ]);
    
    schedulingPanel.appendChild(editingInfo);
    schedulingPanel.appendChild(enabledRow);
    schedulingPanel.appendChild(dateRow);
    schedulingPanel.appendChild(timeRow);
    schedulingPanel.appendChild(daysRow);
    schedulingPanel.appendChild(tzRow);
    schedulingPanel.appendChild(priorityRow);
    schedulingPanel.appendChild(rotationDivider);
    schedulingPanel.appendChild(rotationTitle);
    schedulingPanel.appendChild(rotationPctRow);
    schedulingPanel.appendChild(rotationInfoRow);
    schedulingPanel.appendChild(rotationIntervalRow);
    schedulingPanel.appendChild(saveRow);
    schedulingPanel.appendChild(infoBox);
    
    // Save scheduling handler
    schedulingPanel.querySelector('#cc-mgr-save-scheduling').addEventListener('click', async () => {
      // Warn if editing the active scene
      const isEditingActive = editingScene.is_active == 1 || editingScene.is_active === true || editingScene.is_active === '1';
      if (isEditingActive) {
        const confirmed = confirm('⚠️ You are editing the ACTIVE scene.\n\nSaving these changes will immediately push them to your Roku app.\n\nContinue?');
        if (!confirmed) return;
      }
      
      try {
        setStageStatus(root, 'saving', 'Saving scheduling…');
        
        const scheduleEnabled = schedulingPanel.querySelector('#cc-mgr-schedule-enabled').checked;
        const scheduleStart = schedulingPanel.querySelector('#cc-mgr-schedule-start').value;
        const scheduleEnd = schedulingPanel.querySelector('#cc-mgr-schedule-end').value;
        const scheduleTimeStart = schedulingPanel.querySelector('#cc-mgr-schedule-time-start').value;
        const scheduleTimeEnd = schedulingPanel.querySelector('#cc-mgr-schedule-time-end').value;
        const scheduleTimezone = schedulingPanel.querySelector('#cc-mgr-schedule-timezone').value;
        const schedulePriority = parseInt(schedulingPanel.querySelector('#cc-mgr-schedule-priority').value || '0', 10);
        const rotationPercentage = parseFloat(schedulingPanel.querySelector('#cc-mgr-rotation-percentage').value || '0');
        const rotationInterval = parseInt(schedulingPanel.querySelector('#cc-mgr-rotation-interval').value || '60', 10);
        
        // Collect checked days
        const checkedDays = [];
        schedulingPanel.querySelectorAll('.cc-schedule-day:checked').forEach(cb => {
          checkedDays.push(cb.value);
        });
        
        const payload = {
          schedule_enabled: scheduleEnabled,
          schedule_start: scheduleStart ? scheduleStart.replace('T', ' ') + ':00' : null,
          schedule_end: scheduleEnd ? scheduleEnd.replace('T', ' ') + ':00' : null,
          schedule_time_start: scheduleTimeStart || null,
          schedule_time_end: scheduleTimeEnd || null,
          schedule_timezone: scheduleTimezone,
          schedule_days: checkedDays.length > 0 ? checkedDays : null,
          schedule_priority: schedulePriority,
          rotation_percentage: rotationPercentage,
          rotation_interval: rotationInterval
        };
        
        const base = (window.castconductorScenesAjax || {}).rest_url || '';
        const res = await fetch(base + `castconductor/v5/scenes/${editingScene.id}`, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': (window.castconductorScenesAjax || {}).nonce },
          body: JSON.stringify(payload)
        });
        if (!res.ok) throw new Error('HTTP ' + res.status);
        
        setStageStatus(root, 'saved', 'Scheduling saved');
        setTimeout(() => setStageStatus(root, 'clear'), 900);
      } catch (e) {
        console.error('[SceneManager] Save scheduling failed:', e);
        setStageStatus(root, 'error', 'Failed to save scheduling');
      }
    });
  };
  
  // Initialize all tabs
  buildScenesTab();
  buildAppearanceTab();
  buildContainersTab();
  buildOverlaysTab();
  buildSchedulingTab();
  
  // If we're in preview mode, restore editing context for the previewed scene
  initEditingScene();
}

// Status overlay helpers
function setStageStatus(root, kind, text = '') {
  const host = root.querySelector('[data-host="status-pill"]') || root.querySelector('[data-host="status"]');
  if (!host) return;
  if (kind === 'clear') { host.innerHTML = ''; return; }
  host.innerHTML = '';
  const pill = el('div', { class: `cc-status-pill ${kind}` }, text || kind);
  host.appendChild(pill);
}

// Active scene label
function setActiveSceneLabel(root, scene) {
  const host = root.querySelector('[data-host="status-scene"]');
  if (!host) return;
  host.innerHTML = '';
  if (!scene?.id) return;
  const label = el('div', { class: 'cc-status-scene' }, `Active Scene: ${scene.name || ('#' + scene.id)}`);
  host.appendChild(label);
}

