/* global DS_BOOT, fabric */
(function () {
  if (typeof fabric === "undefined") return;
  try { window.__DS_EDITOR_BUILD__ = "2026-04-27-cart3d-center-nohandle-v1"; } catch (e) {}
  if (!window.__DS_TEMPLATE_PICKER_BOUND__) {
    window.__DS_TEMPLATE_PICKER_BOUND__ = true;
    (function bindTemplatePickerDelegates() {
      function getStudioFromEventTarget(t) {
        if (!t || !t.closest) return null;
        return t.closest(".ds-studio");
      }
      function readStudioBoot(studio) {
        if (!studio) return null;
        const bootEl = studio.querySelector(".ds-boot-json");
        if (!bootEl) return null;
        try { return JSON.parse(bootEl.textContent || "{}"); } catch (e) {}
        return null;
      }
      function persistTemplate(id) {
        const tid = parseInt(id || "0", 10);
        if (!tid) return;
        try { window.localStorage.setItem("ds_template_override", String(tid)); } catch (e) {}
        try { document.cookie = "ds_template_override=" + encodeURIComponent(String(tid)) + "; path=/; max-age=2592000; SameSite=Lax"; } catch (e) {}
      }
      function goWithTemplate(studio, templateId) {
        const tid = parseInt(templateId || "0", 10);
        if (!tid) return;
        persistTemplate(tid);
        let keepFullscreen = false;
        try {
          keepFullscreen = !!document.fullscreenElement || !!document.querySelector(".ds-studio.ds-fullscreen");
          if (keepFullscreen) window.sessionStorage.setItem("ds_editor_fullscreen", "1");
        } catch (e) {}
        const boot = readStudioBoot(studio) || {};
        const base = boot.editor_url || window.location.href;
        const url = new URL(base, window.location.origin);
        url.searchParams.set("ds_template", String(tid));
        url.searchParams.set("ds_pick", "1");
        // Always start template flow clean; stale startup params can lock UI.
        url.searchParams.delete("ds_start");
        url.searchParams.delete("ds_zsel");
        url.searchParams.delete("ds_step");
        url.searchParams.delete("ds_open_zones");
        url.searchParams.set("_dsr", String(Date.now()));
        if (keepFullscreen) url.searchParams.set("ds_fullscreen", "1");
        else url.searchParams.delete("ds_fullscreen");
        const designInput = studio ? studio.querySelector('[data-input="design-id"]') : null;
        const designId = designInput ? parseInt(designInput.value || "0", 10) : 0;
        if (designId > 0) url.searchParams.set("ds_design", String(designId));
        else url.searchParams.delete("ds_design");
        window.location.href = url.toString();
      }

      document.addEventListener("change", function (e) {
        const t = e.target;
        if (!t || !t.matches || !t.matches('[data-input="template-select"]')) return;
        const studio = getStudioFromEventTarget(t);
        goWithTemplate(studio, t.value);
      }, true);

      document.addEventListener("click", function (e) {
        const t = e.target;
        if (!t || !t.matches || !t.matches('[data-action="template-apply"]')) return;
        const studio = getStudioFromEventTarget(t);
        if (!studio) return;
        const select = studio.querySelector('[data-input="template-select"]');
        if (!select) return;
        goWithTemplate(studio, select.value);
      }, true);
    })();
  }

  function isVisibleStudio(el) {
    if (!el) return false;
    const cs = window.getComputedStyle ? window.getComputedStyle(el) : null;
    if (cs && (cs.display === "none" || cs.visibility === "hidden")) return false;
    const rect = el.getBoundingClientRect ? el.getBoundingClientRect() : { width: 0, height: 0 };
    return rect.width > 0 && rect.height > 0;
  }
  function pickBestStudio(candidates) {
    if (!candidates.length) return null;
    const vh = window.innerHeight || 900;
    let best = null;
    let bestScore = -Infinity;
    candidates.forEach((el) => {
      const r = el.getBoundingClientRect ? el.getBoundingClientRect() : { top: 0, bottom: 0, height: 0 };
      const inView = Math.max(0, Math.min(r.bottom, vh) - Math.max(r.top, 0));
      const aboveFoldPenalty = r.top < 0 ? Math.abs(r.top) * 40 : 0;
      const farPenalty = Math.abs(r.top) * 0.5;
      const score = (inView * 1000) - aboveFoldPenalty - farPenalty;
      if (score > bestScore) {
        bestScore = score;
        best = el;
      }
    });
    return best;
  }

  const studioRoots = Array.from(document.querySelectorAll(".ds-studio"));
  const visibleCandidates = studioRoots.filter((el) =>
    el &&
    isVisibleStudio(el) &&
    el.getAttribute("data-ds-editor-init") !== "1" &&
    el.querySelector("#ds-canvas")
  );
  const allCandidates = studioRoots.filter((el) =>
    el &&
    el.getAttribute("data-ds-editor-init") !== "1" &&
    el.querySelector("#ds-canvas")
  );
  const root = pickBestStudio(visibleCandidates) || pickBestStudio(allCandidates) || null;
  if (!root) return;
  if (root.getAttribute("data-ds-editor-init") === "1") return;
  root.setAttribute("data-ds-editor-init", "1");
  // Prevent legacy inline bridge from attaching competing handlers.
  root.__dsMobileBridgeReady = true;
  root.classList.add("ds-shell-active");
  try {
    if (document && document.documentElement) {
      document.documentElement.classList.add("ds-designer-shell-active");
    }
    if (document && document.body) {
      document.body.classList.add("ds-designer-shell-active");
    }
  } catch (err) {}

  let cfg = null;
  const bootEl = root.querySelector(".ds-boot-json");
  if (bootEl) {
    try { cfg = JSON.parse(bootEl.textContent || "{}"); } catch (e) {}
  }
  if (!cfg) cfg = window.DS_BOOT;
  if (!cfg) return;
  if (typeof window !== "undefined") window.DS_BOOT = cfg;
  const i18nRoot = (cfg && cfg.i18n && typeof cfg.i18n === "object") ? cfg.i18n : {};
  const i18nUi = (i18nRoot && i18nRoot.ui && typeof i18nRoot.ui === "object") ? i18nRoot.ui : {};
  const i18nStatus = (i18nRoot && i18nRoot.status && typeof i18nRoot.status === "object") ? i18nRoot.status : {};
  function i18nFormat(template, values) {
    let out = String(template == null ? "" : template);
    const vars = Array.isArray(values) ? values : [];
    vars.forEach((val) => {
      out = out.replace("%s", String(val == null ? "" : val));
    });
    return out;
  }
  function tUi(key, fallback, values) {
    const k = String(key || "");
    const source = (k && Object.prototype.hasOwnProperty.call(i18nUi, k))
      ? i18nUi[k]
      : fallback;
    return i18nFormat(source, values);
  }
  function tStatus(msg) {
    const source = String(msg == null ? "" : msg);
    if (!source) return "";
    if (Object.prototype.hasOwnProperty.call(i18nStatus, source)) {
      return String(i18nStatus[source] || source);
    }
    return source;
  }
  const previewUiCfg = (cfg && cfg.preview && typeof cfg.preview === "object") ? cfg.preview : {};
  const hideWhiteZoneBgInPreview = Number(previewUiCfg.hide_white_zone_bg || 0) === 1;
  const selectedTemplateId = parseInt(cfg.id || cfg.template_id || "0", 10);
  const startupParams = (() => {
    try { return new URLSearchParams((window && window.location && window.location.search) || ""); } catch (e) { return new URLSearchParams(""); }
  })();
  const unsavedDesignRef = (() => {
    const tpl = String(parseInt(selectedTemplateId || "0", 10) || 0);
    try {
      const qs = String(startupParams.get("_dsr") || "").trim();
      if (qs) return "new:" + tpl + ":" + qs;
    } catch (e) {}
    try {
      const key = "ds_unsaved_design_ref_" + tpl;
      const fromSession = String(window.sessionStorage.getItem(key) || "").trim();
      if (fromSession) return fromSession;
      const seed = "new:" + tpl + ":" + String(Date.now()) + ":" + Math.random().toString(36).slice(2, 8);
      window.sessionStorage.setItem(key, seed);
      return seed;
    } catch (e2) {}
    return "new:" + tpl + ":" + String(Date.now());
  })();
  const initialDesignId = (() => {
    const bootId = Number(cfg && cfg.load_design_id ? cfg.load_design_id : 0);
    if (bootId > 0) return bootId;
    try {
      const qsId = parseInt(String(startupParams.get("ds_design") || "0"), 10);
      if (qsId > 0) return qsId;
    } catch (e) {}
    try {
      const pending = parseInt(String(window.sessionStorage.getItem("ds_pending_design_id") || "0"), 10);
      if (pending > 0) return pending;
    } catch (e2) {}
    return 0;
  })();
  let pendingInitialDesignLoad = Number(initialDesignId || 0) > 0;
  const initialAutoApplyToProduct = (() => {
    try {
      const qs = String(startupParams.get("ds_auto_apply") || "").trim();
      if (qs === "1") return true;
    } catch (e) {}
    try {
      return String(window.sessionStorage.getItem("ds_pending_auto_apply_product") || "") === "1";
    } catch (e2) {}
    return false;
  })();
  const dragDebugEnabled = (() => {
    try {
      const qp = String(startupParams.get("ds_debug_drag") || "").toLowerCase();
      if (qp === "1" || qp === "true" || qp === "yes") return true;
    } catch (e) {}
    try {
      const ls = String(window.localStorage.getItem("ds_debug_drag") || "").toLowerCase();
      if (ls === "1" || ls === "true" || ls === "yes") return true;
    } catch (e2) {}
    return false;
  })();
  function dragDbg(tag, payload) {
    if (!dragDebugEnabled) return;
    try {
      if (typeof console !== "undefined" && typeof console.debug === "function") {
        console.debug("[DS-DRAG]", tag, payload || {});
      }
    } catch (e) {}
  }
  const startupFlowEnabled = false;
  let startupStep = String(startupParams.get("ds_step") || "").toLowerCase();
  let startupOpenZonesRequested = String(startupParams.get("ds_open_zones") || "") === "1";
  let startupApproved = String(startupParams.get("ds_start") || "") === "1";
  const startupZonesParam = String(startupParams.get("ds_zsel") || "").trim();
  let forceInitialZoneSelectionAfterTemplatePick = false;
  let startupFlowStarted = true;
  let startupFlowPanel = null;
  let startupZoneStepRedirecting = false;
  function setStartupLoadingOverlay(on) {
    return;
  }
  let startupHasPreparedSelection = startupZonesParam !== "";
  if (!startupHasPreparedSelection && startupApproved && selectedTemplateId) {
    try {
      const key = "ds_startup_selection_" + String(parseInt(selectedTemplateId || "0", 10) || 0);
      const raw = window.sessionStorage.getItem(key);
      if (raw) {
        const parsed = JSON.parse(raw || "{}");
        const ids = Array.isArray(parsed && parsed.selectedIds) ? parsed.selectedIds : [];
        startupHasPreparedSelection = ids.length > 0;
      }
    } catch (e) {}
  }
  if (startupApproved && !startupHasPreparedSelection && startupStep !== "zones") {
    startupApproved = false;
    try {
      const u = new URL(window.location.href);
      u.searchParams.delete("ds_start");
      u.searchParams.delete("ds_zsel");
      window.history.replaceState(null, "", u.toString());
    } catch (e) {}
  }
  if (!startupFlowEnabled) {
    startupStep = "";
    startupOpenZonesRequested = false;
    startupApproved = true;
  }
  const startupAutoOpenZonesAfterEditorInit = consumeStartupAutoOpenZonesForTemplate(selectedTemplateId);
  if (startupApproved && startupHasPreparedSelection) setStartupLoadingOverlay(true);
  (function normalizeTemplateQueryAfterPickerLoad() {
    try {
      if (!selectedTemplateId || typeof window === "undefined" || !window.location || !window.history || !window.history.replaceState) return;
      const u = new URL(window.location.href);
      const fromPicker = String(u.searchParams.get("ds_pick") || "") === "1";
      const hasProduct = String(u.searchParams.get("ds_product") || "").trim() !== "";
      const hasDesign = String(u.searchParams.get("ds_design") || "").trim() !== "";
      const hasTemplateParam = String(u.searchParams.get("ds_template") || "").trim() !== "";
      forceInitialZoneSelectionAfterTemplatePick = !hasProduct && !hasDesign && (fromPicker || hasTemplateParam);
      if (!fromPicker || hasProduct || hasDesign) return;
      u.searchParams.delete("ds_template");
      u.searchParams.delete("ds_pick");
      u.searchParams.delete("_dsr");
      window.history.replaceState(null, "", u.toString());
    } catch (e) {}
  })();
  const preInitAreaCount = (() => {
    try {
      const pc = (cfg && cfg.preview_config && typeof cfg.preview_config === "object") ? cfg.preview_config : {};
      const mockups = Array.isArray(pc.mockups) ? pc.mockups : [];
      let count = 0;
      mockups.forEach((m) => {
        const areas = Array.isArray(m && m.areas) ? m.areas : [];
        count += areas.length;
      });
      return count;
    } catch (e) {
      return 0;
    }
  })();
  const preInitZoneCount = Array.isArray(cfg && cfg.zones) ? cfg.zones.length : 0;
  const preInitPreviewMode = String((cfg && cfg.preview_mode) || "").toLowerCase();
  const preInitLooks2D = preInitPreviewMode ? (preInitPreviewMode !== "3d") : true;
  const preInitNeedsZoneGate = !!(
    selectedTemplateId &&
    forceInitialZoneSelectionAfterTemplatePick &&
    preInitLooks2D &&
    Math.max(preInitZoneCount, preInitAreaCount) > 1
  );
  if (preInitNeedsZoneGate) {
    root.classList.add("ds-no-active-canvas");
    const preWrap = root.querySelector(".ds-canvas-wrap");
    if (preWrap) {
      preWrap.style.display = "none";
      preWrap.style.visibility = "hidden";
    }
  }
  function getTemplateOptionsHtml(sourceSelect) {
    let optionsHtml = '<option value="">' + escHtml(tUi("choose_template_option", "Choose Template")) + "</option>";
    if (sourceSelect && sourceSelect.options && sourceSelect.options.length) {
      optionsHtml = Array.from(sourceSelect.options).map((opt) => {
        const v = String(opt.value || "");
        const t = String(opt.textContent || "");
        return '<option value="' + escAttr(v) + '">' + escHtml(t) + '</option>';
      }).join("");
    }
    return optionsHtml;
  }
  function getTemplateItemsForStartPanel() {
    const list = Array.isArray(cfg && cfg.templates) ? cfg.templates : [];
    return list.map((item) => {
      const id = parseInt(item && item.id ? item.id : 0, 10);
      if (!id) return null;
      const title = String((item && item.title) || tUi("template_fallback", "Template #%s", [id]));
      const w = Number((item && item.width_mm) || 0);
      const h = Number((item && item.height_mm) || 0);
      const size = (w > 0 && h > 0) ? (Math.round(w) + "x" + Math.round(h) + " mm") : "";
      const thumbUrl = String((item && item.thumb_url) || "").trim();
      return { id, title, size, thumbUrl };
    }).filter(Boolean);
  }
  function getTemplateCardsHtml(items) {
    if (!Array.isArray(items) || !items.length) {
      return '<div class="ds-no-template-grid-empty">' + escHtml(tUi("no_templates_found", "No templates found.")) + "</div>";
    }
    const cardStyle = "background:#ffffff;color:#12243b;border:1px solid #c9d8ea;box-shadow:0 3px 10px rgba(10,24,44,0.07);";
    const thumbStyle = "background:#edf3fa;border:1px solid #d7e3f0;";
    const titleStyle = "color:#12243b;";
    const sizeStyle = "color:#4f6785;";
    return items.map((tpl) => {
      const thumb = tpl.thumbUrl
        ? ('<img src="' + escAttr(tpl.thumbUrl) + '" alt="' + escAttr(tpl.title) + '" loading="lazy" />')
        : ('<span class="ds-no-template-thumb-fallback">' + escHtml(tUi("no_image", "No image")) + "</span>");
      const sizeHtml = tpl.size ? ('<span class="ds-no-template-card-size" style="' + escAttr(sizeStyle) + '">' + escHtml(tpl.size) + '</span>') : "";
      return (
        '<div class="ds-no-template-card" style="' + escAttr(cardStyle) + '" role="button" tabindex="0" aria-pressed="false" data-role="no-template-card" data-template-id="' + escAttr(String(tpl.id)) + '">' +
          '<span class="ds-no-template-card-thumb" style="' + escAttr(thumbStyle) + '">' + thumb + "</span>" +
          '<span class="ds-no-template-card-meta">' +
            '<span class="ds-no-template-card-title" style="' + escAttr(titleStyle) + '">' + escHtml(tpl.title) + "</span>" +
            sizeHtml +
          "</span>" +
        "</div>"
      );
    }).join("");
  }
  function updateStartPanelSelection(startCard, templateId, statusEl) {
    if (!startCard) return;
    forceNoTemplateCardLightStyles(startCard);
    try { applyStartupActionRoleStyles(startCard); } catch (e0) {}
    const selected = String(templateId || "").trim();
    const localSelect = startCard.querySelector('[data-role="no-template-select"]');
    const applyBtn = startCard.querySelector('[data-role="no-template-next"]') || startCard.querySelector('[data-role="no-template-apply"]');
    if (localSelect) localSelect.value = selected;
    startCard.querySelectorAll('[data-role="no-template-card"]').forEach((card) => {
      const on = String(card.getAttribute("data-template-id") || "") === selected;
      card.classList.toggle("is-active", on);
      card.setAttribute("aria-pressed", on ? "true" : "false");
    });
    const hasValue = selected !== "";
    let selectedTemplateName = "";
    if (hasValue && localSelect && localSelect.selectedOptions && localSelect.selectedOptions.length) {
      selectedTemplateName = String(localSelect.selectedOptions[0].textContent || "").trim();
    }
    if (!selectedTemplateName && hasValue) {
      const activeCardTitle = startCard.querySelector('[data-role="no-template-card"].is-active .ds-no-template-card-title');
      selectedTemplateName = activeCardTitle ? String(activeCardTitle.textContent || "").trim() : "";
    }
    if (applyBtn) {
      applyBtn.disabled = !hasValue;
      if (hasValue) applyBtn.removeAttribute("disabled");
      else applyBtn.setAttribute("disabled", "disabled");
    }
    const selectedLabel = startCard.querySelector('[data-role="no-template-selected-label"]');
    if (selectedLabel) {
      selectedLabel.textContent = hasValue
        ? tUi("selected_template_named", "Selected template: %s", [selectedTemplateName || tUi("template_fallback", "Template #%s", [selected])])
        : tUi("selected_template_none", "Selected template: none");
    }
    if (statusEl) {
      statusEl.textContent = hasValue
        ? tUi("template_selected_click_next", "Template selected. Click Next.")
        : tUi("start_choose_template_first", "Start Designing: choose a template first.");
    }
  }
  function getStartupBackHref() {
    const link = root.querySelector(".ds-topbar-back") || root.querySelector(".ds-mobile-topbar-back");
    return link ? String(link.getAttribute("href") || "").trim() : "";
  }
  function getStartupExitHref() {
    const fromCfg = String((cfg && cfg.home_url) || "").trim();
    if (fromCfg) return fromCfg;
    const link = root.querySelector(".ds-mobile-topbar-back") || root.querySelector(".ds-topbar-back");
    const fallback = link ? String(link.getAttribute("data-exit-fallback") || "").trim() : "";
    return fallback || "/";
  }
  function hardRefreshNow() {
    try {
      const u = new URL(window.location.href);
      u.searchParams.set("_dsr", String(Date.now()));
      window.location.href = u.toString();
      return;
    } catch (e) {}
    window.location.reload();
  }
  function buildNoTemplateStartPanel(sourceTemplateSelect, statusEl) {
    const existingStart = root.querySelector('[data-role="no-template-start"]');
    if (existingStart && existingStart.parentNode) existingStart.parentNode.removeChild(existingStart);
    const startCard = document.createElement("section");
    startCard.className = "ds-no-template-start";
    startCard.setAttribute("data-role", "no-template-start");
    startCard.setAttribute("aria-live", "polite");
    const optionsHtml = getTemplateOptionsHtml(sourceTemplateSelect);
    const templateCardsHtml = getTemplateCardsHtml(getTemplateItemsForStartPanel());
    const backHref = getStartupBackHref();
    const backActionHtml = backHref ? ('<a href="' + escAttr(backHref) + '" data-role="startup-back-link">' + escHtml(tUi("back", "Back")) + "</a>") : "";
    const exitHref = getStartupExitHref();
    const exitActionHtml = exitHref ? ('<a href="' + escAttr(exitHref) + '" data-role="startup-back-link">' + escHtml(tUi("exit", "Exit")) + "</a>") : "";
    const step2CreateIcon = getUiIconByName("sparkles");
    const step2LoadIcon = getUiIconByName("cloud-download");
    const step2BackIcon = getUiIconByName("arrow-left");

    function navigateToTemplate(templateId, designId, autoApplyToProduct) {
      const chosen = String(templateId || "").trim();
      if (!chosen) return;
      persistTemplateOverride(chosen);
      if (designId && Number(designId) > 0) clearStartupAutoOpenZonesForTemplate(chosen);
      else markStartupAutoOpenZonesForTemplate(chosen);
      const base = cfg.editor_url || window.location.href;
      const nextUrl = new URL(base, window.location.origin);
      nextUrl.searchParams.set("ds_template", String(chosen));
      nextUrl.searchParams.set("ds_pick", "1");
      if (designId && Number(designId) > 0) {
        nextUrl.searchParams.set("ds_design", String(designId));
        try { window.sessionStorage.setItem("ds_pending_design_id", String(designId)); } catch (e) {}
        if (autoApplyToProduct) {
          nextUrl.searchParams.set("ds_auto_apply", "1");
          try { window.sessionStorage.setItem("ds_pending_auto_apply_product", "1"); } catch (e2) {}
        } else {
          nextUrl.searchParams.delete("ds_auto_apply");
          try { window.sessionStorage.removeItem("ds_pending_auto_apply_product"); } catch (e3) {}
        }
      } else {
        nextUrl.searchParams.delete("ds_design");
        nextUrl.searchParams.delete("ds_auto_apply");
        try { window.sessionStorage.removeItem("ds_pending_auto_apply_product"); } catch (e4) {}
      }
      nextUrl.searchParams.delete("ds_start");
      nextUrl.searchParams.delete("ds_zsel");
      nextUrl.searchParams.delete("ds_step");
      nextUrl.searchParams.delete("ds_open_zones");
      nextUrl.searchParams.set("_dsr", String(Date.now()));
      window.location.href = nextUrl.toString();
    }

  function renderDesignRows(items, opts) {
      const options = opts && typeof opts === "object" ? opts : {};
      const showUseOnProduct = !!options.showUseOnProduct;
      const arr = Array.isArray(items) ? items : [];
      if (!arr.length) return '<div class="ds-my-designs-empty">' + escHtml(tUi("no_designs", "No designs.")) + "</div>";
      return arr.map((d) => {
        const id = Number(d.id) || 0;
        const tid = Number(d.template_id) || 0;
        const designTitle = String((d && d.title) || tUi("design_fallback", "Design #%s", [id]));
        const png = d && d.png ? String(d.png) : "";
        const template = d && d.template_title ? String(d.template_title) : ("Template #" + tid);
        const date = d && d.created_at ? new Date(String(d.created_at)).toLocaleString() : "";
        const zonesUsed = Array.isArray(d && d.zones_used) ? d.zones_used.map((z) => String(z || "")).filter(Boolean) : [];
        const zonesPng = (d && d.zones_png && typeof d.zones_png === "object") ? d.zones_png : {};
        const setThumbs = zonesUsed.map((zid) => {
          const zpng = zonesPng[zid] ? String(zonesPng[zid]) : "";
          if (!zpng) return "";
          return '' +
            '<div class="ds-my-design-set-item">' +
              '<img src="' + escAttr(zpng) + '" alt="' + escAttr(tUi("zone_label", "Zone %s", [zid])) + '" loading="lazy" />' +
              '<span>' + escAttr(zid) + '</span>' +
            '</div>';
        }).filter(Boolean).join("");
        const thumb = setThumbs
          ? ('<div class="ds-my-design-set"><div class="ds-my-design-set-grid">' + setThumbs + '</div></div>')
          : (png
            ? ('<div class="ds-my-design-thumb"><img src="' + escAttr(png) + '" alt="' + escAttr(designTitle) + '" loading="lazy" /></div>')
            : "");
        return '' +
          '<div class="ds-my-design-item' + (setThumbs ? ' ds-my-design-item-set' : '') + '" data-design-id="' + id + '">' +
            thumb +
            '<div class="ds-my-design-meta">' +
              '<strong>' + escHtml(designTitle) + '</strong>' +
              '<span>' + escHtml(template) + (date ? (" | " + escHtml(date)) : "") + (zonesUsed.length ? (" | " + escHtml(tUi("set_zones_count", "Set zones: %s", [zonesUsed.length]))) : "") + '</span>' +
            '</div>' +
            '<div class="ds-my-design-actions">' +
              (showUseOnProduct
                ? ('<button type="button" class="ds-design-use-btn" data-action="no-template-design-use" data-role="no-template-design-use" data-id="' + id + '" data-template-id="' + tid + '" data-title="' + escAttr(designTitle) + '">' + escHtml(tUi("use_on_product", "Use on product")) + "</button>")
                : '') +
              '<button type="button" class="ds-design-load-btn" data-action="no-template-design-load" data-role="no-template-design-load" data-id="' + id + '" data-template-id="' + tid + '" data-title="' + escAttr(designTitle) + '">' + escHtml(tUi("load", "Load")) + "</button>" +
            '</div>' +
          '</div>';
      }).join("");
  }

    function setStep(stepName) {
      const s1 = startCard.querySelector('[data-role="no-template-step-1"]');
      const s2 = startCard.querySelector('[data-role="no-template-step-2"]');
      const s2l = startCard.querySelector('[data-role="no-template-step-2-load"]');
      if (s1) s1.hidden = stepName !== "step1";
      if (s2) s2.hidden = stepName !== "step2";
      if (s2l) s2l.hidden = stepName !== "step2-load";
      if (statusEl) {
        if (stepName === "step1") {
          statusEl.textContent = tUi("step_status_1", "Step 1: Choose template and click Next.");
        } else if (stepName === "step2") {
          statusEl.textContent = tUi("step_status_2", "Step 2: Load design or create new.");
        } else {
          statusEl.textContent = tUi("step_status_2_load", "Step 2: Choose a saved design.");
        }
      }
      try { applyStartupActionRoleStyles(startCard); } catch (e5) {}
    }

    function loadDesignLibraryForSelectedTemplate() {
      const sel = startCard.querySelector('[data-role="no-template-select"]');
      const chosen = sel ? parseInt(String(sel.value || "0"), 10) : 0;
      const myHost = startCard.querySelector('[data-role="no-template-my-designs"]');
      const pubHost = startCard.querySelector('[data-role="no-template-public-designs"]');
      if (!myHost || !pubHost) return;
      if (!chosen) {
        myHost.innerHTML = '<div class="ds-my-designs-empty">' + escHtml(tUi("choose_template_first", "Choose template first.")) + "</div>";
        pubHost.innerHTML = '<div class="ds-my-designs-empty">' + escHtml(tUi("choose_template_first", "Choose template first.")) + "</div>";
        return;
      }
      myHost.innerHTML = '<div class="ds-my-designs-empty">' + escHtml(tUi("loading", "Loading...")) + "</div>";
      pubHost.innerHTML = '<div class="ds-my-designs-empty">' + escHtml(tUi("loading", "Loading...")) + "</div>";
      fetchJSON(cfg.rest.base + "/design-library", {
        method: "GET",
        headers: { "X-WP-Nonce": cfg.rest.nonce }
      }).then((res) => {
        const all = Array.isArray(res && res.items) ? res.items : [];
        const filtered = all.filter((d) => Number(d && d.template_id ? d.template_id : 0) === chosen);
        const mine = filtered.filter((d) => Number(d && d.is_owner ? d.is_owner : 0) === 1);
        const publicOther = filtered.filter((d) => {
          const isOwner = Number(d && d.is_owner ? d.is_owner : 0) === 1;
          const vis = String(d && d.visibility ? d.visibility : "private");
          return !isOwner && vis === "shared";
        });
        myHost.innerHTML = renderDesignRows(mine, { showUseOnProduct: true });
        pubHost.innerHTML = renderDesignRows(publicOther, { showUseOnProduct: false });
        try { applyStartupActionRoleStyles(startCard); } catch (e2) {}
      }).catch((err) => {
        const msg = err && err.message ? err.message : "Load Design error.";
        myHost.innerHTML = '<div class="ds-my-designs-empty">' + escHtml(tUi("error_loading_designs", "Error loading designs.")) + "</div>";
        pubHost.innerHTML = '<div class="ds-my-designs-empty">' + escHtml(tUi("error_loading_designs", "Error loading designs.")) + "</div>";
        setStatus(msg);
        try { applyStartupActionRoleStyles(startCard); } catch (e3) {}
      });
    }

    startCard.innerHTML =
      '<div class="ds-no-template-start-card">' +
      '<div data-role="no-template-step-1">' +
        '<h3>' + escHtml(tUi("step1_title", "STEP 1: CHOOSE TEMPLATE")) + '</h3>' +
        '<p>' + escHtml(tUi("step1_help", "Select template to continue.")) + '</p>' +
        '<label class="ds-no-template-start-field ds-no-template-start-field--hidden">' + escHtml(tUi("template_label", "Template")) +
          '<select data-role="no-template-select">' + optionsHtml + '</select>' +
        '</label>' +
        '<div class="ds-no-template-grid" data-role="no-template-grid">' + templateCardsHtml + '</div>' +
        '<div class="ds-no-template-selected-label" data-role="no-template-selected-label">' + escHtml(tUi("selected_template_none", "Selected template: none")) + '</div>' +
        '<div class="ds-no-template-start-actions">' +
          '<button type="button" class="ds-no-template-start-apply" data-role="no-template-next" disabled="disabled">' + escHtml(tUi("next", "Next")) + '</button>' +
          '<button type="button" data-role="startup-hard-refresh">' + escHtml(tUi("refresh", "Refresh")) + '</button>' +
          exitActionHtml +
        '</div>' +
      '</div>' +
      '<div data-role="no-template-step-2" hidden>' +
        '<h3>' + escHtml(tUi("step2_title", "STEP 2: LOAD OR CREATE")) + '</h3>' +
        '<p>' + escHtml(tUi("step2_help", "Load an existing design or create a new one.")) + '</p>' +
        '<div class="ds-no-template-start-actions">' +
          '<button type="button" class="ds-no-template-start-apply" data-role="no-template-create-new">' +
            '<span class="ds-no-template-step2-btn-icon" aria-hidden="true">' + step2CreateIcon + '</span>' +
            '<span class="ds-no-template-step2-btn-text">' + escHtml(tUi("create_new_design", "Create New Design")) + '</span>' +
          '</button>' +
          '<button type="button" data-role="no-template-open-load">' +
            '<span class="ds-no-template-step2-btn-icon" aria-hidden="true">' + step2LoadIcon + '</span>' +
            '<span class="ds-no-template-step2-btn-text">' + escHtml(tUi("load_design", "Load Design")) + '</span>' +
          '</button>' +
          '<button type="button" data-role="no-template-back-step-1">' +
            '<span class="ds-no-template-step2-btn-icon" aria-hidden="true">' + step2BackIcon + '</span>' +
            '<span class="ds-no-template-step2-btn-text">' + escHtml(tUi("back", "Back")) + '</span>' +
          '</button>' +
        '</div>' +
      '</div>' +
      '<div data-role="no-template-step-2-load" hidden>' +
        '<h3>' + escHtml(tUi("step2_load_title", "STEP 2: LOAD DESIGN")) + '</h3>' +
        '<p>' + escHtml(tUi("step2_load_help", "Choose a saved design for the selected template.")) + '</p>' +
        '<div class="ds-no-template-load-groups" data-role="no-template-load-groups">' +
          '<h4>' + escHtml(tUi("my_designs", "My Designs")) + '</h4>' +
          '<div class="ds-my-designs-list" data-role="no-template-my-designs"></div>' +
          '<h4>' + escHtml(tUi("public_designs", "Public Designs")) + '</h4>' +
          '<div class="ds-my-designs-list" data-role="no-template-public-designs"></div>' +
        '</div>' +
        '<div class="ds-no-template-start-actions">' +
          '<button type="button" data-role="startup-hard-refresh">' + escHtml(tUi("refresh", "Refresh")) + '</button>' +
          backActionHtml +
        '</div>' +
      '</div>' +
      '</div>';

    forceNoTemplateCardLightStyles(startCard);
    applyStartupActionRoleStyles(startCard);

    startCard.addEventListener("click", (e) => {
      const cardBtn = e.target && e.target.closest ? e.target.closest('[data-role="no-template-card"]') : null;
      if (cardBtn) {
        e.preventDefault();
        const templateId = String(cardBtn.getAttribute("data-template-id") || "").trim();
        updateStartPanelSelection(startCard, templateId, statusEl);
        return;
      }
      const nextBtn = e.target && e.target.closest ? e.target.closest('[data-role="no-template-next"]') : null;
      if (nextBtn) {
        const sel = startCard.querySelector('[data-role="no-template-select"]');
        const chosen = sel ? String(sel.value || "").trim() : "";
        if (!chosen) return;
        setStep("step2");
        return;
      }
      const createBtn = e.target && e.target.closest ? e.target.closest('[data-role="no-template-create-new"]') : null;
      if (createBtn) {
        const sel = startCard.querySelector('[data-role="no-template-select"]');
        const chosen = sel ? String(sel.value || "").trim() : "";
        if (!chosen) return;
        navigateToTemplate(chosen, 0);
        return;
      }
      const openLoadBtn = e.target && e.target.closest ? e.target.closest('[data-role="no-template-open-load"]') : null;
      if (openLoadBtn) {
        setStep("step2-load");
        loadDesignLibraryForSelectedTemplate();
        return;
      }
      const back1 = e.target && e.target.closest ? e.target.closest('[data-role="no-template-back-step-1"]') : null;
      if (back1) {
        setStep("step1");
        return;
      }
      const back2 = e.target && e.target.closest ? e.target.closest('[data-role="no-template-back-step-2"]') : null;
      if (back2) {
        setStep("step2");
        return;
      }
      const refreshBtn = e.target && e.target.closest ? e.target.closest('[data-role="startup-hard-refresh"]') : null;
      if (refreshBtn) {
        hardRefreshNow();
        return;
      }
      const loadBtn = e.target && e.target.closest ? e.target.closest('[data-action="no-template-design-load"]') : null;
      if (loadBtn) {
        const did = parseInt(String(loadBtn.getAttribute("data-id") || "0"), 10);
        const tid = parseInt(String(loadBtn.getAttribute("data-template-id") || "0"), 10);
        if (!did || !tid) {
          setStatus("Invalid design selection.");
          return;
        }
        navigateToTemplate(tid, did);
        return;
      }
      const useBtn = e.target && e.target.closest ? e.target.closest('[data-action="no-template-design-use"]') : null;
      if (useBtn) {
        const did = parseInt(String(useBtn.getAttribute("data-id") || "0"), 10);
        const tid = parseInt(String(useBtn.getAttribute("data-template-id") || "0"), 10);
        if (!did || !tid) {
          setStatus("Invalid design selection.");
          return;
        }
        navigateToTemplate(tid, did, true);
      }
    });

    startCard.addEventListener("keydown", (e) => {
      const key = String(e && e.key ? e.key : "");
      if (key !== "Enter" && key !== " ") return;
      const cardBtn = e.target && e.target.closest ? e.target.closest('[data-role="no-template-card"]') : null;
      if (!cardBtn) return;
      e.preventDefault();
      const templateId = String(cardBtn.getAttribute("data-template-id") || "").trim();
      updateStartPanelSelection(startCard, templateId, statusEl);
    });

    startCard.addEventListener("change", (e) => {
      const sel = e.target && e.target.closest ? e.target.closest('[data-role="no-template-select"]') : null;
      if (!sel) return;
      updateStartPanelSelection(startCard, String(sel.value || ""), statusEl);
    });

    const preselected = sourceTemplateSelect ? String(sourceTemplateSelect.value || "") : "";
    updateStartPanelSelection(startCard, preselected, statusEl);
    setStep("step1");
    root.appendChild(startCard);
    return startCard;
  }

  function normalizeHexColor(v, fallback) {
    const s = String(v || "").trim();
    if (/^#[0-9a-fA-F]{6}$/.test(s) || /^#[0-9a-fA-F]{3}$/.test(s)) return s;
    return fallback;
  }

  function applyButtonThemeInline(el, bg, border, text, shadow, gradient) {
    if (!el || !el.style) return;
    const solid = String(bg || "").trim() || String(border || "").trim() || "#ffffff";
    const grad = String(gradient || "").trim();
    el.style.setProperty("background", solid, "important");
    el.style.setProperty("background-color", solid, "important");
    el.style.setProperty("background-image", grad || "none", "important");
    el.style.setProperty("border-color", border, "important");
    el.style.setProperty("color", text, "important");
    el.style.setProperty("box-shadow", shadow, "important");
  }

  function applyStartupActionRoleStyles(host) {
    const rootEl = host && host.querySelector ? host : root;
    if (!rootEl || !rootEl.querySelectorAll) return;
    const cs = (typeof window !== "undefined" && window.getComputedStyle) ? window.getComputedStyle(root) : null;
    const accent = normalizeHexColor((cs && cs.getPropertyValue("--ds-accent")) || "", "#f89f25");
    const secondary = normalizeHexColor((cs && cs.getPropertyValue("--ds-accent-secondary")) || "", "#2f80ed");

    const accentButtons = rootEl.querySelectorAll(
      '.ds-no-template-start-actions [data-role="no-template-next"], ' +
      '.ds-no-template-start-actions [data-role="no-template-create-new"], ' +
      '.ds-my-design-actions [data-role="no-template-design-use"]'
    );
    accentButtons.forEach((btn) => {
      applyButtonThemeInline(
        btn,
        accent,
        accent,
        "#ffffff",
        "0 8px 18px " + accent + "55",
        "linear-gradient(180deg, " + accent + " 0%, " + accent + " 100%)"
      );
    });

    const secondaryButtons = rootEl.querySelectorAll(
      '.ds-no-template-start-actions [data-role="no-template-open-load"], ' +
      '.ds-no-template-start-actions [data-role="no-template-load-design"], ' +
      '.ds-no-template-start-actions [data-role="startup-hard-refresh"], ' +
      '.ds-my-design-actions [data-role="no-template-design-load"]'
    );
    secondaryButtons.forEach((btn) => {
      applyButtonThemeInline(
        btn,
        secondary,
        secondary,
        "#ffffff",
        "0 8px 18px " + secondary + "55",
        "linear-gradient(180deg, " + secondary + " 0%, " + secondary + " 100%)"
      );
    });

    const backButtons = rootEl.querySelectorAll(
      '.ds-no-template-start-actions [data-role="no-template-back-step-1"], ' +
      '.ds-no-template-start-actions [data-role="no-template-back-step-2"], ' +
      '.ds-no-template-start-actions [data-role="startup-back-link"]'
    );
    backButtons.forEach((btn) => {
      applyButtonThemeInline(
        btn,
        "#1a2638",
        "#0f1828",
        "#e9f1ff",
        "0 8px 18px rgba(12, 23, 40, 0.24)",
        "linear-gradient(180deg, #263349 0%, #121d30 100%)"
      );
    });
  }

  function forceNoTemplateCardLightStyles(host) {
    const rootEl = host && host.querySelector ? host : root;
    if (!rootEl || !rootEl.querySelectorAll) return;
    const cards = rootEl.querySelectorAll('[data-role="no-template-card"]');
    cards.forEach((card) => {
      if (!card || !card.style) return;
      card.style.setProperty("background", "#ffffff", "important");
      card.style.setProperty("background-color", "#ffffff", "important");
      card.style.setProperty("background-image", "none", "important");
      card.style.setProperty("color", "#12243b", "important");
      card.style.setProperty("border-color", "#c9d8ea", "important");
      card.style.setProperty("text-shadow", "none", "important");
      card.style.setProperty("filter", "none", "important");
      card.style.setProperty("cursor", "pointer", "important");
      const thumb = card.querySelector(".ds-no-template-card-thumb");
      if (thumb && thumb.style) {
        thumb.style.setProperty("background", "#edf3fa", "important");
        thumb.style.setProperty("border-color", "#d7e3f0", "important");
      }
      const title = card.querySelector(".ds-no-template-card-title");
      if (title && title.style) title.style.setProperty("color", "#12243b", "important");
      const size = card.querySelector(".ds-no-template-card-size");
      if (size && size.style) size.style.setProperty("color", "#4f6785", "important");
    });
  }
  if (!selectedTemplateId) {
    root.classList.add("ds-no-template");
    root.classList.remove("ds-startup-gated");
    const startupPanel = root.querySelector('[data-role="startup-only-panel"]');
    if (startupPanel && startupPanel.parentNode) startupPanel.parentNode.removeChild(startupPanel);
    root.querySelectorAll(".ds-toolbar .ds-toolblock").forEach((block) => {
      if (!block.classList.contains("ds-template-block")) {
        block.style.display = "none";
      }
    });
    const ws = root.querySelector(".ds-workspace");
    if (ws) {
      ws.style.display = "none";
      ws.style.visibility = "hidden";
    }
    const sourceTemplateSelect = root.querySelector('[data-input="template-select"]');
    const statusForNoTemplate = root.querySelector('[data-role="status-text"]') || root.querySelector(".ds-status");
    buildNoTemplateStartPanel(sourceTemplateSelect, statusForNoTemplate);
    const st = statusForNoTemplate;
    if (st) st.textContent = tUi("choose_template_first_canvas_load", "Choose template first. Canvas will load after template selection.");
    return;
  }

  function startupSelectionKeyForTemplate(templateId) {
    return "ds_startup_selection_" + String(parseInt(templateId || "0", 10) || 0);
  }

  function startupZoneReadyKeyForTemplate(templateId) {
    return "ds_startup_zone_ready_" + String(parseInt(templateId || "0", 10) || 0);
  }

function startupAutoOpenZonesKeyForTemplate(templateId) {
  return "ds_startup_auto_open_zones_" + String(parseInt(templateId || "0", 10) || 0);
}

function clearStartupAutoOpenZonesForTemplate(templateId) {
  try {
    const key = startupAutoOpenZonesKeyForTemplate(templateId);
    if (!key) return;
    window.sessionStorage.removeItem(key);
  } catch (e) {}
}

function markStartupAutoOpenZonesForTemplate(templateId) {
  try {
    const key = startupAutoOpenZonesKeyForTemplate(templateId);
    window.sessionStorage.setItem(key, "1");
    } catch (e) {}
  }

  function consumeStartupAutoOpenZonesForTemplate(templateId) {
    try {
      const key = startupAutoOpenZonesKeyForTemplate(templateId);
      const raw = window.sessionStorage.getItem(key);
      if (raw === "1") {
        window.sessionStorage.removeItem(key);
        return true;
      }
    } catch (e) {}
    return false;
  }

  function loadStartupZoneReadyForTemplate(templateId) {
    try {
      const key = startupZoneReadyKeyForTemplate(templateId);
      const raw = window.sessionStorage.getItem(key);
      if (!raw) return [];
      const parsed = JSON.parse(raw || "{}");
      const ids = Array.isArray(parsed && parsed.selectedIds) ? parsed.selectedIds : [];
      return ids.map((v) => String(v || "").trim()).filter(Boolean);
    } catch (e) {
      return [];
    }
  }

  function saveStartupZoneReadyForTemplate(templateId, selectedIds) {
    try {
      const key = startupZoneReadyKeyForTemplate(templateId);
      const ids = Array.isArray(selectedIds) ? selectedIds.map((v) => String(v || "").trim()).filter(Boolean) : [];
      window.sessionStorage.setItem(key, JSON.stringify({ selectedIds: ids }));
    } catch (e) {}
  }

  function getStartupZonesLite() {
    const byId = {};
    const tz = Array.isArray(cfg && cfg.zones) ? cfg.zones : [];
    tz.forEach((z) => {
      const zid = String((z && z.id) || "").trim();
      if (!zid) return;
      const existing = byId[zid] || { id: zid, label: zid, excludes: [] };
      if (z && z.label) existing.label = String(z.label);
      const ex = []
        .concat(Array.isArray(z && z.excludes) ? z.excludes : [])
        .concat(Array.isArray(z && z.conflicts) ? z.conflicts : [])
        .concat(Array.isArray(z && z.conflict_with) ? z.conflict_with : [])
        .concat(Array.isArray(z && z.not_with) ? z.not_with : [])
        .concat(Array.isArray(z && z.blocked_with) ? z.blocked_with : [])
        .map((v) => String(v || "").trim())
        .filter(Boolean);
      existing.excludes = existing.excludes.concat(ex);
      byId[zid] = existing;
    });
    const previewCfg = (cfg && cfg.preview_config && typeof cfg.preview_config === "object") ? cfg.preview_config : {};
    const mockups = Array.isArray(previewCfg.mockups) ? previewCfg.mockups : [];
    mockups.forEach((m) => {
      const areas = Array.isArray(m && m.areas) ? m.areas : [];
      areas.forEach((a) => {
        const id = String((a && a.id) || "").trim();
        if (!id) return;
        const existing = byId[id] || { id: id, label: id, excludes: [] };
        if (a && a.label) existing.label = String(a.label);
        const ex = Array.isArray(a && a.excludes) ? a.excludes.map((v) => String(v || "").trim()).filter(Boolean) : [];
        existing.excludes = existing.excludes.concat(ex);
        byId[id] = existing;
      });
    });
    return Object.keys(byId).map((id) => byId[id]);
  }

  function getStartupConflictMapLite(zonesLite) {
    const map = {};
    const add = (a, b) => {
      const x = String(a || "").trim();
      const y = String(b || "").trim();
      if (!x || !y || x === y) return;
      if (!map[x]) map[x] = {};
      if (!map[y]) map[y] = {};
      map[x][y] = true;
      map[y][x] = true;
    };
    (Array.isArray(zonesLite) ? zonesLite : []).forEach((z) => {
      const id = String((z && z.id) || "");
      if (!id) return;
      const ex = Array.isArray(z && z.excludes) ? z.excludes : [];
      ex.forEach((eid) => add(id, eid));
    });
    const cfgConf = (cfg.preview_config && cfg.preview_config.zone_conflicts) ? cfg.preview_config.zone_conflicts : null;
    if (cfgConf && typeof cfgConf === "object") {
      if (Array.isArray(cfgConf)) {
        cfgConf.forEach((row) => add(row && (row.a || row.zone || row.id), row && (row.b || row.with || row.conflict)));
      } else {
        Object.keys(cfgConf).forEach((a) => {
          const list = Array.isArray(cfgConf[a]) ? cfgConf[a] : [];
          list.forEach((b) => add(a, b));
        });
      }
    }
    return map;
  }

  function redirectToStartupTemplateStep() {
    const base = cfg.editor_url || window.location.href;
    const next = new URL(base, window.location.origin);
    next.searchParams.delete("ds_template");
    next.searchParams.delete("ds_start");
    next.searchParams.delete("ds_step");
    next.searchParams.delete("ds_zsel");
    next.searchParams.delete("ds_open_zones");
    next.searchParams.set("_dsr", String(Date.now()));
    window.location.href = next.toString();
  }

  function renderStartupOnlyPanel() {
    root.classList.add("ds-startup-gated");
    const existing = root.querySelector('[data-role="startup-only-panel"]');
    if (existing && existing.parentNode) existing.parentNode.removeChild(existing);
    const panel = document.createElement("section");
    panel.className = "ds-no-template-start ds-startup-flow-panel";
    panel.setAttribute("data-role", "startup-only-panel");
    panel.setAttribute("aria-live", "polite");
    const zonesLite = getStartupZonesLite();
    const zoneRequired = zonesLite.length > 1;
    if (!zoneRequired) {
      const autoSelected = zonesLite.map((z) => String(z.id || "")).filter(Boolean);
      try {
        const key = startupSelectionKeyForTemplate(selectedTemplateId);
        window.sessionStorage.setItem(key, JSON.stringify({ selectedIds: autoSelected }));
      } catch (err) {}
      const u = new URL(window.location.href);
      u.searchParams.set("ds_template", String(selectedTemplateId));
      u.searchParams.set("ds_pick", "1");
      u.searchParams.set("ds_start", "1");
      u.searchParams.set("ds_zsel", autoSelected.join(","));
      u.searchParams.delete("ds_step");
      u.searchParams.delete("ds_open_zones");
      u.searchParams.set("_dsr", String(Date.now()));
      window.location.href = u.toString();
      return;
    }
    const backHref = getStartupBackHref();
    let stageTitle = tUi("step3_choose_print_areas", "Step 3: Choose Print Areas");
    let stageText = tUi("step3_open_zone_tools_help", "Open Zone Tools and choose print areas.");
    let actionsHtml = '<button type="button" data-role="startup-open-zone-tools">' + escHtml(tUi("open_zone_tools", "Open Zone Tools")) + '</button>';
    panel.innerHTML =
      '<div class="ds-no-template-start-card">' +
      '<h3>' + stageTitle + '</h3>' +
      '<p>' + stageText + '</p>' +
      '<div class="ds-mobile-empty-actions">' +
      actionsHtml +
      '</div>' +
      '<div class="ds-start-flow-utility">' +
      '<button type="button" data-role="startup-hard-refresh">' + escHtml(tUi("refresh", "Refresh")) + '</button>' +
      '<button type="button" data-role="startup-change-template">' + escHtml(tUi("change_template", "Change Template")) + '</button>' +
      (backHref ? ('<a href="' + escAttr(backHref) + '" data-role="startup-back-link">' + escHtml(tUi("back", "Back")) + '</a>') : "") +
      "</div>" +
      "</div>";
    root.appendChild(panel);
    const openZoneToolsFromStartup = () => {
      const u = new URL(window.location.href);
      u.searchParams.set("ds_template", String(selectedTemplateId));
      u.searchParams.set("ds_pick", "1");
      u.searchParams.set("ds_start", "1");
      u.searchParams.set("ds_step", "zones");
      u.searchParams.delete("ds_zsel");
      u.searchParams.set("ds_open_zones", "1");
      u.searchParams.set("_dsr", String(Date.now()));
      window.location.href = u.toString();
    };
    panel.addEventListener("click", (e) => {
      const changeTpl = e.target && e.target.closest ? e.target.closest('[data-role="startup-change-template"]') : null;
      if (changeTpl) {
        redirectToStartupTemplateStep();
        return;
      }
      const openZoneTools = e.target && e.target.closest ? e.target.closest('[data-role="startup-open-zone-tools"]') : null;
      if (openZoneTools) {
        openZoneToolsFromStartup();
        return;
      }
      const refreshBtn = e.target && e.target.closest ? e.target.closest('[data-role="startup-hard-refresh"]') : null;
      if (refreshBtn) hardRefreshNow();
    });
    const st = root.querySelector('[data-role="status-text"]') || root.querySelector(".ds-status");
    if (st) st.textContent = tUi("step3_required_zone_tools", "Step 3 required: choose print areas in Zone Tools.");
  }

  if (!startupApproved) {
    const startupPanel = root.querySelector('[data-role="startup-only-panel"]');
    if (startupPanel && startupPanel.parentNode) startupPanel.parentNode.removeChild(startupPanel);
    root.classList.remove("ds-startup-gated");
    startupApproved = true;
  }
  const previewTypeEarly = String(cfg.preview_type || "mug").toLowerCase();
  const previewModeEarly = String(cfg.preview_mode || "").toLowerCase();
  const isTemplate3DPreview = previewModeEarly ? (previewModeEarly === "3d") : (previewTypeEarly === "mug" || previewTypeEarly === "bottle" || previewTypeEarly === "tshirt");
  const needsThree = isTemplate3DPreview && (previewTypeEarly === "mug" || previewTypeEarly === "bottle" || previewTypeEarly === "tshirt");
  if (needsThree && typeof window.THREE === "undefined") {
    if (window.__DS_THREE_LOADING__) return;
    window.__DS_THREE_LOADING__ = true;
    const assets = (cfg.assets && typeof cfg.assets === "object") ? cfg.assets : {};
    const editorSrc = String(assets.editor_js || "").trim();
    const restartEditor = function() {
      window.__DS_THREE_LOADING__ = false;
      if (!editorSrc) return;
      root.removeAttribute("data-ds-editor-init");
      const s2 = document.createElement("script");
      s2.src = editorSrc + (editorSrc.indexOf("?") === -1 ? "?" : "&") + "dsr=" + Date.now();
      s2.async = false;
      document.head.appendChild(s2);
    };
    const threeSrc = String(assets.three_js || "").trim();
    const gltfSrc = String(assets.gltf_loader_js || "").trim();
    Promise.resolve()
      .then(() => loadExternalScriptOnce(threeSrc))
      .then(() => loadExternalScriptOnce(gltfSrc))
      .then(() => {
        if (typeof window !== "undefined" && typeof window.GLTFLoader !== "undefined") {
          window.__DS_GLTFLoader = window.GLTFLoader;
        }
      })
      .catch(() => {
        if (typeof console !== "undefined") console.warn("[DS] Failed to load Three.js runtime assets.");
      })
      .finally(restartEditor);
    return;
  }
  const isTouchDevice = (typeof window !== "undefined") && (
    ("ontouchstart" in window) ||
    (window.matchMedia && window.matchMedia("(pointer: coarse)").matches)
  );

  const statusEl = root.querySelector(".ds-status");
  const statusTextEl = root.querySelector('[data-role="status-text"]');
  const statusPoweredInitialEl = statusEl ? statusEl.querySelector(".ds-status-powered") : null;
  const statusPoweredLogoSrc = (() => {
    try {
      const img = statusPoweredInitialEl ? statusPoweredInitialEl.querySelector(".ds-status-powered-logo") : null;
      const src = img ? String(img.getAttribute("src") || img.src || "").trim() : "";
      if (src) return src;
    } catch (e) {}
    return "";
  })();
  const canvasEl = root.querySelector("#ds-canvas");
  const wrapEl = root.querySelector(".ds-canvas-wrap");
  const viewportEl = root.querySelector(".ds-canvas-viewport");
  const layerListEl = root.querySelector('[data-role="layer-list"]');
  const propsEl = root.querySelector('[data-role="props"]');
  const propsModalEl = root.querySelector('[data-role="props-modal"]');
  const myDesignsModalEl = root.querySelector('[data-role="my-designs-modal"]');
  const myDesignsListEl = root.querySelector('[data-role="my-designs-list"]');
  const myDesignsTitleEl = myDesignsModalEl ? myDesignsModalEl.querySelector(".ds-my-designs-head h4") : null;
  const applyProductModalEl = root.querySelector('[data-role="apply-product-modal"]');
  const applyProductListEl = root.querySelector('[data-role="apply-product-list"]');
  const assetLibraryModalEl = root.querySelector('[data-role="asset-library-modal"]');
  const assetLibraryTitleEl = root.querySelector('[data-role="asset-library-title"]');
  const assetLibraryListEl = root.querySelector('[data-role="asset-library-list"]');
  const mugMiniCanvasEl = root.querySelector('[data-role="mug-canvas-mini"]');
  const mugModalEl = root.querySelector('[data-role="mug-modal"]');
  const mugLargeCanvasEl = root.querySelector('[data-role="mug-canvas-large"]');
  const previewTitleEl = mugModalEl ? mugModalEl.querySelector(".ds-preview-head h4") : null;
  const mugMiniGroupEl = root.querySelector(".ds-mug-mini-group");
  const outputAuthHintEl = root.querySelector('[data-role="output-auth-hint"]');
  const aiRemoveBgBtn = root.querySelector('[data-action="ai-remove-bg"]');
  const zoomInput = root.querySelector('[data-input="zoom"]');
  const zoomLabel = root.querySelector('[data-role="zoom-label"]');
  const templateSelectInput = root.querySelector('[data-input="template-select"]');
  const designIdInput = root.querySelector('[data-input="design-id"]');
  const gridToggle = root.querySelector('[data-opt="grid"]');
  const snapToggle = root.querySelector('[data-opt="snap"]');
  const mugOpenBtn = root.querySelector('[data-action="mug-open"]');
  const previewType = (cfg.preview_type || "mug").toLowerCase();
  const previewMode = String(cfg.preview_mode || "").toLowerCase();
  const previewConfig = (cfg.preview_config && typeof cfg.preview_config === "object") ? cfg.preview_config : {};
  const templateZones = Array.isArray(cfg.zones) ? cfg.zones : [];
  let designZoneAttrsOverride = {};
  const previewFactories = {
    mug: initMugPreview,
    bottle: initMugPreview,
    tshirt: initTshirtPreview
  };
  const activePreviewFactory = (previewMode === "3d") ? (previewFactories[previewType] || null) : null;
  const activePreviewNeedsThree = !!activePreviewFactory && (previewType === "mug" || previewType === "bottle" || previewType === "tshirt");
  const enablePreview3D = !!activePreviewFactory;
  const topbarPreview3DIconSrc = "data:image/svg+xml;utf8," + encodeURIComponent(
    "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64' fill='none' stroke='%230b2e57' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'><path d='M11 22 32 10l21 12-21 12z' fill='%23d8e8fb'/><path d='M11 22v20l21 12 21-12V22'/><path d='M32 34v20'/><path d='M11 22l21 12 21-12'/><circle cx='50' cy='14' r='4.5' fill='%23a7c6ea' stroke='%230b2e57'/></svg>"
  );
  const canOutput = !!(cfg.user && cfg.user.logged_in);
  const aiRemoveBgEnabled = !!(cfg.ai && cfg.ai.remove_bg_enabled);
  const loginUrl = (cfg.user && cfg.user.login_url) ? String(cfg.user.login_url) : "";
  const protectionCfg = (cfg.protection && typeof cfg.protection === "object") ? cfg.protection : {};
  const protectionState = {
    enabled: !!Number(protectionCfg.enabled || 0),
    is_pro: false,
    trial_total: Math.max(0, Number(protectionCfg.trial_total || 0) || 0),
    trial_remaining: Math.max(0, Number(protectionCfg.trial_total || 0) || 0),
    loaded: false
  };
  const libraryConfig = (cfg.library && typeof cfg.library === "object") ? cfg.library : {};
  let miniPreview = null;
  let largePreview = null;
  let productPreview2DModal = null;
  const productPreview2DImageCache = {};
  let myDesignsLoading = false;
  let myDesignsMode = "my";
  let applyProductsLoading = false;
  let zoneState = null;
  let orientationState = null;
  let placementCounter = 1;
  let tshirtContourMasks = { front: null, back: null, left: null, right: null };
  let templatesCache = [];
  let gltfLoaderCtorPromise = null;
  let decalGeometryCtorPromise = null;
  const webFontLoadCache = {};
  const uiConfig = (cfg && cfg.ui && typeof cfg.ui === "object") ? cfg.ui : {};
  const contextToolbarEl = root.querySelector('[data-role="context-toolbar"]');
  const contextFontFamilyEl = contextToolbarEl ? contextToolbarEl.querySelector('[data-ctx-control="font-family"]') : null;
  const contextFontSizeEl = contextToolbarEl ? contextToolbarEl.querySelector('[data-ctx-control="font-size"]') : null;
  const contextFillEls = contextToolbarEl ? Array.from(contextToolbarEl.querySelectorAll('[data-ctx-control="fill"]')) : [];
  const contextStrokeEl = contextToolbarEl ? contextToolbarEl.querySelector('[data-ctx-control="stroke"]') : null;
  const contextStrokeWidthEl = contextToolbarEl ? contextToolbarEl.querySelector('[data-ctx-control="stroke-width"]') : null;
  const contextOpacityEl = contextToolbarEl ? contextToolbarEl.querySelector('[data-ctx-control="opacity"]') : null;
  const contextRotateEl = contextToolbarEl ? contextToolbarEl.querySelector('[data-ctx-control="rotate"]') : null;
  const contextGroupBtn = contextToolbarEl ? contextToolbarEl.querySelector('[data-ctx-control="group"]') : null;
  const contextUngroupBtn = contextToolbarEl ? contextToolbarEl.querySelector('[data-ctx-control="ungroup"]') : null;
  const fontCatalog = [
    { group: "Sans (Latin)", name: "Arial", stack: "Arial, Helvetica, sans-serif" },
    { group: "Sans (Latin)", name: "Helvetica", stack: "\"Helvetica Neue\", Helvetica, Arial, sans-serif" },
    { group: "Sans (Latin)", name: "Verdana", stack: "Verdana, Geneva, sans-serif" },
    { group: "Sans (Latin)", name: "Tahoma", stack: "Tahoma, Geneva, sans-serif" },
    { group: "Sans (Latin)", name: "Trebuchet MS", stack: "\"Trebuchet MS\", Helvetica, sans-serif" },
    { group: "Sans (Latin)", name: "Gill Sans", stack: "\"Gill Sans\", \"Gill Sans MT\", Calibri, sans-serif" },
    { group: "Sans (Latin)", name: "Segoe UI", stack: "\"Segoe UI\", Tahoma, sans-serif" },
    { group: "Sans (Latin)", name: "Calibri", stack: "Calibri, \"Segoe UI\", sans-serif" },
    { group: "Sans (Latin)", name: "Candara", stack: "Candara, Calibri, sans-serif" },
    { group: "Sans (Latin)", name: "Optima", stack: "Optima, Segoe, sans-serif" },
    { group: "Sans (Latin)", name: "Franklin Gothic", stack: "\"Franklin Gothic Medium\", Arial, sans-serif" },
    { group: "Sans (Latin)", name: "Avenir", stack: "Avenir, \"Avenir Next\", \"Segoe UI\", sans-serif" },
    { group: "Sans (Latin)", name: "Futura", stack: "Futura, \"Trebuchet MS\", sans-serif" },
    { group: "Sans (Latin)", name: "Century Gothic", stack: "\"Century Gothic\", Arial, sans-serif" },
    { group: "Sans (Latin)", name: "Corbel", stack: "Corbel, \"Segoe UI\", sans-serif" },
    { group: "Serif (Latin)", name: "Times New Roman", stack: "\"Times New Roman\", Times, serif" },
    { group: "Serif (Latin)", name: "Georgia", stack: "Georgia, serif" },
    { group: "Serif (Latin)", name: "Garamond", stack: "Garamond, \"Times New Roman\", serif" },
    { group: "Serif (Latin)", name: "Baskerville", stack: "Baskerville, \"Times New Roman\", serif" },
    { group: "Serif (Latin)", name: "Didot", stack: "Didot, \"Times New Roman\", serif" },
    { group: "Serif (Latin)", name: "Palatino", stack: "\"Palatino Linotype\", Palatino, serif" },
    { group: "Serif (Latin)", name: "Book Antiqua", stack: "\"Book Antiqua\", Palatino, serif" },
    { group: "Serif (Latin)", name: "Cambria", stack: "Cambria, Georgia, serif" },
    { group: "Serif (Latin)", name: "Constantia", stack: "Constantia, Georgia, serif" },
    { group: "Serif (Latin)", name: "Bodoni MT", stack: "\"Bodoni MT\", Didot, serif" },
    { group: "Serif (Latin)", name: "Rockwell", stack: "Rockwell, \"Courier New\", serif" },
    { group: "Serif (Latin)", name: "Perpetua", stack: "Perpetua, Georgia, serif" },
    { group: "Serif (Latin)", name: "Lucida Bright", stack: "\"Lucida Bright\", Georgia, serif" },
    { group: "Serif (Latin)", name: "American Typewriter", stack: "\"American Typewriter\", Georgia, serif" },
    { group: "Serif (Latin)", name: "Hoefler Text", stack: "\"Hoefler Text\", Garamond, serif" },
    { group: "Monospace", name: "Courier New", stack: "\"Courier New\", Courier, monospace" },
    { group: "Monospace", name: "Consolas", stack: "Consolas, \"Courier New\", monospace" },
    { group: "Monospace", name: "Lucida Console", stack: "\"Lucida Console\", Monaco, monospace" },
    { group: "Monospace", name: "Monaco", stack: "Monaco, Consolas, monospace" },
    { group: "Monospace", name: "Menlo", stack: "Menlo, Monaco, monospace" },
    { group: "Monospace", name: "Source Code Pro", stack: "\"Source Code Pro\", Consolas, monospace" },
    { group: "Monospace", name: "Inconsolata", stack: "Inconsolata, Consolas, monospace" },
    { group: "Monospace", name: "Fira Code", stack: "\"Fira Code\", Consolas, monospace" },
    { group: "Monospace", name: "JetBrains Mono", stack: "\"JetBrains Mono\", Consolas, monospace" },
    { group: "Monospace", name: "IBM Plex Mono", stack: "\"IBM Plex Mono\", Consolas, monospace" },
    { group: "Display", name: "Impact", stack: "Impact, Charcoal, sans-serif" },
    { group: "Display", name: "Copperplate", stack: "Copperplate, Papyrus, fantasy" },
    { group: "Display", name: "Papyrus", stack: "Papyrus, fantasy" },
    { group: "Display", name: "Brush Script MT", stack: "\"Brush Script MT\", cursive" },
    { group: "Display", name: "Comic Sans MS", stack: "\"Comic Sans MS\", cursive" },
    { group: "Display", name: "Lobster", stack: "Lobster, \"Brush Script MT\", cursive" },
    { group: "Display", name: "Pacifico", stack: "Pacifico, \"Brush Script MT\", cursive" },
    { group: "Display", name: "Bebas Neue", stack: "\"Bebas Neue\", Impact, sans-serif" },
    { group: "Display", name: "Anton", stack: "Anton, Impact, sans-serif" },
    { group: "Display", name: "Righteous", stack: "Righteous, \"Trebuchet MS\", sans-serif" },
    { group: "Cyrillic-Friendly", name: "Roboto", stack: "Roboto, \"Segoe UI\", Arial, sans-serif" },
    { group: "Cyrillic-Friendly", name: "Open Sans", stack: "\"Open Sans\", Arial, sans-serif" },
    { group: "Cyrillic-Friendly", name: "Noto Sans", stack: "\"Noto Sans\", Arial, sans-serif" },
    { group: "Cyrillic-Friendly", name: "Noto Serif", stack: "\"Noto Serif\", Georgia, serif" },
    { group: "Cyrillic-Friendly", name: "PT Sans", stack: "\"PT Sans\", Arial, sans-serif" },
    { group: "Cyrillic-Friendly", name: "PT Serif", stack: "\"PT Serif\", Georgia, serif" },
    { group: "Cyrillic-Friendly", name: "Inter", stack: "Inter, \"Segoe UI\", Arial, sans-serif" },
    { group: "Cyrillic-Friendly", name: "Montserrat", stack: "Montserrat, Arial, sans-serif" },
    { group: "Cyrillic-Friendly", name: "Lato", stack: "Lato, Arial, sans-serif" },
    { group: "Cyrillic-Friendly", name: "Oswald", stack: "Oswald, Arial, sans-serif" },
    { group: "Cyrillic-Friendly", name: "Ubuntu", stack: "Ubuntu, Arial, sans-serif" },
    { group: "Cyrillic-Friendly", name: "Rubik", stack: "Rubik, Arial, sans-serif" },
    { group: "Cyrillic-Friendly", name: "Manrope", stack: "Manrope, Arial, sans-serif" },
    { group: "Cyrillic-Friendly", name: "Fira Sans", stack: "\"Fira Sans\", Arial, sans-serif" },
    { group: "Cyrillic-Friendly", name: "IBM Plex Sans", stack: "\"IBM Plex Sans\", Arial, sans-serif" },
    { group: "Cyrillic-Friendly", name: "Merriweather", stack: "Merriweather, Georgia, serif" },
    { group: "Cyrillic-Friendly", name: "Lora", stack: "Lora, Georgia, serif" },
    { group: "Cyrillic-Friendly", name: "Arimo", stack: "Arimo, Arial, sans-serif" },
    { group: "Cyrillic-Friendly", name: "Tinos", stack: "Tinos, \"Times New Roman\", serif" },
    { group: "Cyrillic-Friendly", name: "Cousine", stack: "Cousine, \"Courier New\", monospace" },
    { group: "Cyrillic-Friendly", name: "Exo 2", stack: "\"Exo 2\", Arial, sans-serif" },
    { group: "Cyrillic-Friendly", name: "Comfortaa", stack: "Comfortaa, Arial, sans-serif" },
    { group: "Cyrillic-Friendly", name: "Jura", stack: "Jura, Arial, sans-serif" },
    { group: "Cyrillic-Friendly", name: "Russo One", stack: "\"Russo One\", Arial, sans-serif" },
    { group: "Cyrillic-Friendly", name: "Cuprum", stack: "Cuprum, Arial, sans-serif" },
    { group: "Cyrillic-Friendly", name: "Neucha", stack: "Neucha, cursive" },
    { group: "Cyrillic-Friendly", name: "Didact Gothic", stack: "\"Didact Gothic\", Arial, sans-serif" },
    { group: "Cyrillic-Friendly", name: "Golos Text", stack: "\"Golos Text\", Arial, sans-serif" },
    { group: "Cyrillic-Friendly", name: "Alegreya Sans", stack: "\"Alegreya Sans\", Arial, sans-serif" },
    { group: "Cyrillic-Friendly", name: "Alegreya", stack: "Alegreya, Georgia, serif" },
    { group: "Cyrillic-Friendly", name: "Philosopher", stack: "Philosopher, Arial, sans-serif" },
    { group: "Cyrillic-Friendly", name: "Play", stack: "Play, Arial, sans-serif" },
    { group: "Cyrillic-Friendly", name: "Marck Script", stack: "\"Marck Script\", cursive" },
    { group: "Cyrillic-Friendly", name: "Bad Script", stack: "\"Bad Script\", cursive" },
    { group: "Cyrillic-Friendly", name: "Poiret One", stack: "\"Poiret One\", Arial, sans-serif" },
    { group: "Cyrillic-Friendly", name: "Vollkorn", stack: "Vollkorn, Georgia, serif" },
    { group: "Cyrillic-Friendly", name: "M PLUS 1p", stack: "\"M PLUS 1p\", Arial, sans-serif" },
    { group: "Cyrillic-Friendly", name: "Cormorant Garamond", stack: "\"Cormorant Garamond\", Georgia, serif" },
    { group: "Cyrillic-Friendly", name: "EB Garamond", stack: "\"EB Garamond\", Garamond, serif" },
    { group: "Cyrillic-Friendly", name: "Source Sans 3", stack: "\"Source Sans 3\", Arial, sans-serif" },
    { group: "Cyrillic-Friendly", name: "Source Serif 4", stack: "\"Source Serif 4\", Georgia, serif" },
    { group: "Cyrillic-Friendly", name: "Assistant", stack: "Assistant, Arial, sans-serif" },
    { group: "Cyrillic-Friendly", name: "Mulish", stack: "Mulish, Arial, sans-serif" },
    { group: "Cyrillic-Friendly", name: "Nunito", stack: "Nunito, Arial, sans-serif" },
    { group: "Cyrillic-Friendly", name: "Nunito Sans", stack: "\"Nunito Sans\", Arial, sans-serif" },
    { group: "Cyrillic-Friendly", name: "Work Sans", stack: "\"Work Sans\", Arial, sans-serif" },
    { group: "Cyrillic-Friendly", name: "Sarabun", stack: "Sarabun, Arial, sans-serif" },
    { group: "Cyrillic-Friendly", name: "Marmelad", stack: "Marmelad, Arial, sans-serif" },
    { group: "Cyrillic-Friendly", name: "Oranienbaum", stack: "Oranienbaum, Georgia, serif" },
    { group: "Cyrillic-Friendly", name: "Prata", stack: "Prata, Georgia, serif" },
    { group: "Cyrillic-Friendly", name: "Yeseva One", stack: "\"Yeseva One\", Georgia, serif" }
  ];

  function applyUiConfig() {
    if (!root) return;
    const p = String(uiConfig.primary_color || "").trim();
    const a = String(uiConfig.accent_color || "").trim();
    const b = String(uiConfig.bg_color || "").trim();
    const t = String(uiConfig.text_color || "").trim();
    const tf = String(uiConfig.font_family || "").trim();
    const panel = String(uiConfig.panel_color || "").trim();
    const button = String(uiConfig.button_color || "").trim();
    const line = String(uiConfig.line_color || "").trim();
    const muted = String(uiConfig.muted_color || "").trim();
    const mi = String(uiConfig.modern_icon_color || "").trim();
    const panelPosRaw = String(uiConfig.modern_right_panel_position || "right").toLowerCase();
    const panelPos = panelPosRaw === "left" ? "left" : "right";
    const variantRaw = String(uiConfig.variant || root.getAttribute("data-ds-ui-variant") || "classic").toLowerCase();
    const variant = (variantRaw === "modern") ? "modern" : "classic";
    const forceClassicLandscape = typeof window !== "undefined" &&
      window.matchMedia &&
      window.matchMedia("(max-width: 1023px) and (orientation: landscape)").matches;
    const effectiveVariant = forceClassicLandscape ? "classic" : variant;
    root.setAttribute("data-ds-ui-variant", effectiveVariant);
    root.classList.toggle("ds-ui-modern", effectiveVariant === "modern");
    root.classList.toggle("ds-ui-classic", effectiveVariant !== "modern");
    root.classList.toggle("ds-modern-right-left", effectiveVariant === "modern" && panelPos === "left");
    if (p) {
      root.style.setProperty("--accent", p);
      root.style.setProperty("--ds-primary", p);
    }
    if (a) {
      root.style.setProperty("--ds-accent-2", a);
      root.style.setProperty("--ds-accent", a);
    }
    if (tf) root.style.setProperty("--ds-ui-font", tf);
    if (b) root.style.setProperty("--ds-bg-custom", b);
    if (t) root.style.setProperty("--text", t);
    if (panel) root.style.setProperty("--panel", panel);
    if (button) root.style.setProperty("--ds-button-surface", button);
    if (line) root.style.setProperty("--line", line);
    if (muted) root.style.setProperty("--muted", muted);
    if (mi) root.style.setProperty("--ds-modern-icon-color", mi);
  }

  function loadExternalScriptOnce(src) {
    return new Promise((resolve) => {
      if (!src) { resolve(false); return; }
      const exists = Array.from(document.scripts || []).some((s) => s && s.src && s.src.indexOf(src) === 0);
      if (exists) { resolve(true); return; }
      const s = document.createElement("script");
      s.src = src + (src.indexOf("?") === -1 ? "?" : "&") + "dsl=" + Date.now();
      s.async = false;
      s.onload = function() { resolve(true); };
      s.onerror = function() { resolve(false); };
      document.head.appendChild(s);
    });
  }

  function resolveGLTFLoaderCtor() {
    if (typeof THREE !== "undefined" && typeof THREE.GLTFLoader !== "undefined") {
      return Promise.resolve(THREE.GLTFLoader);
    }
    if (typeof window !== "undefined" && typeof window.__DS_GLTFLoader !== "undefined") {
      return Promise.resolve(window.__DS_GLTFLoader);
    }
    if (typeof window !== "undefined" && typeof window.GLTFLoader !== "undefined") {
      return Promise.resolve(window.GLTFLoader);
    }
    if (gltfLoaderCtorPromise) return gltfLoaderCtorPromise;
    const assets = (cfg && cfg.assets && typeof cfg.assets === "object") ? cfg.assets : {};
    const primary = String(assets.gltf_loader_js || "").trim();
    const fallback = String(assets.gltf_loader_fallback_js || "").trim();
    gltfLoaderCtorPromise = loadExternalScriptOnce(primary).then(() => {
      if (typeof THREE !== "undefined" && typeof THREE.GLTFLoader !== "undefined") return THREE.GLTFLoader;
      if (typeof window !== "undefined" && typeof window.GLTFLoader !== "undefined") return window.GLTFLoader;
      if (!fallback || fallback === primary) return null;
      return loadExternalScriptOnce(fallback).then(() => {
        if (typeof THREE !== "undefined" && typeof THREE.GLTFLoader !== "undefined") return THREE.GLTFLoader;
        if (typeof window !== "undefined" && typeof window.GLTFLoader !== "undefined") return window.GLTFLoader;
        return null;
      });
    }).catch(() => null);
    return gltfLoaderCtorPromise;
  }

  function resolveDecalGeometryCtor() {
    if (typeof THREE !== "undefined" && typeof THREE.DecalGeometry !== "undefined") {
      return Promise.resolve(THREE.DecalGeometry);
    }
    if (typeof window !== "undefined" && typeof window.DecalGeometry !== "undefined") {
      return Promise.resolve(window.DecalGeometry);
    }
    if (decalGeometryCtorPromise) return decalGeometryCtorPromise;
    const assets = (cfg && cfg.assets && typeof cfg.assets === "object") ? cfg.assets : {};
    const primary = String(assets.decal_geometry_js || "").trim();
    const fallback = String(assets.decal_geometry_fallback_js || "").trim();
    decalGeometryCtorPromise = loadExternalScriptOnce(primary).then(() => {
      if (typeof THREE !== "undefined" && typeof THREE.DecalGeometry !== "undefined") return THREE.DecalGeometry;
      if (typeof window !== "undefined" && typeof window.DecalGeometry !== "undefined") return window.DecalGeometry;
      if (!fallback || fallback === primary) return null;
      return loadExternalScriptOnce(fallback).then(() => {
        if (typeof THREE !== "undefined" && typeof THREE.DecalGeometry !== "undefined") return THREE.DecalGeometry;
        if (typeof window !== "undefined" && typeof window.DecalGeometry !== "undefined") return window.DecalGeometry;
        return null;
      });
    }).catch(() => null);
    return decalGeometryCtorPromise;
  }

  function normalizeZoneList() {
    const areaMap = getMockupAreaMap();
    const areaIds = Object.keys(areaMap);
    if (areaIds.length) {
      return areaIds.map((id) => ({
        id: id,
        label: String((areaMap[id] && areaMap[id].label) || id)
      }));
    }

    if (previewType === "tshirt") {
      return [
        { id: "main_front", label: "MainFront" },
        { id: "main_back", label: "MainBack" },
        { id: "top_left", label: "topLeft" },
        { id: "top_right", label: "topRight" },
        { id: "top_center", label: "topCenter" }
      ];
    }
    const raw = Array.isArray(templateZones) ? templateZones : [];
    const out = [];
    const seen = {};
    const isTshirt = previewType === "tshirt";
    const excluded = { chest_left: true, chest_right: true, top: false };
    raw.forEach((z) => {
      if (!z || !z.id) return;
      const id = String(z.id);
      if (isTshirt && excluded[id]) return;
      if (!id || seen[id]) return;
      seen[id] = true;
      out.push({
        id: id,
        label: z.label ? String(z.label) : id
      });
    });
    if (!out.length) out.push({ id: "front_full", label: "Front" });
    return out;
  }

  function getTemplateMockups() {
    const out = [];
    const raw = (previewConfig && Array.isArray(previewConfig.mockups)) ? previewConfig.mockups : [];
    raw.forEach((m, idx) => {
      if (!m || typeof m !== "object") return;
      const side = String(m.side || "custom").toLowerCase();
      const imageUrl = String(m.image_url || "").trim();
      const name = String(m.name || ("Mockup " + (idx + 1))).trim();
      if (!imageUrl) return;
      const areas = Array.isArray(m.areas) ? m.areas.map((a, ai) => ({
        id: String((a && a.id) || ("area_" + idx + "_" + ai)),
        zone_id: String((a && (a.zone_id || a.zoneId || a.id)) || ""),
        label: String((a && a.label) || ("Area " + (ai + 1))),
        x_pct: Number((a && a.x_pct) || 0),
        y_pct: Number((a && a.y_pct) || 0),
        w_pct: Number((a && a.w_pct) || 0),
        h_pct: Number((a && a.h_pct) || 0),
        width_mm: Number((a && a.width_mm) || 0),
        height_mm: Number((a && a.height_mm) || 0),
        excludes: Array.isArray(a && a.excludes) ? a.excludes.map((v) => String(v || "")).filter(Boolean) : [],
        allow_subcanvas: !!(a && a.allow_subcanvas),
        portrait_width_mm: Number((a && a.portrait_width_mm) || 0),
        portrait_height_mm: Number((a && a.portrait_height_mm) || 0),
        landscape_width_mm: Number((a && a.landscape_width_mm) || 0),
        landscape_height_mm: Number((a && a.landscape_height_mm) || 0)
      })) : [];
      out.push({ side: side, image_url: imageUrl, name: name, areas: areas });
    });
    if (out.length) return out;
    const twoD = (previewConfig && previewConfig.mockup_2d && typeof previewConfig.mockup_2d === "object")
      ? previewConfig.mockup_2d
      : {};
    ["front", "back", "left", "right"].forEach((side) => {
      const url = String(twoD[side] || "").trim();
      if (!url) return;
      out.push({ side: side, image_url: url, name: side.charAt(0).toUpperCase() + side.slice(1) });
    });
    return out;
  }

  function applyDesignZoneSnapshot(res) {
    designZoneAttrsOverride = {};
    if (!res || typeof res !== "object") return;
    const zonesAttrs = (res.zones_attrs && typeof res.zones_attrs === "object") ? res.zones_attrs : {};
    Object.keys(zonesAttrs).forEach((zid) => {
      const key = String(zid || "").trim();
      const row = zonesAttrs[zid];
      if (!key || !row || typeof row !== "object") return;
      designZoneAttrsOverride[key] = row;
    });

    const mockups = Array.isArray(res.preview_mockups) ? res.preview_mockups : [];
    if (mockups.length) {
      previewConfig.mockups = mockups;
    }
  }

  function normalizeZoneMatchText(v) {
    return String(v || "").trim().toLowerCase();
  }

  function resolveAreaZoneId(area, zoneById, usedMap, sideHint) {
    if (!area || !zoneById) return "";
    const explicitZoneId = String((area && (area.zone_id || area.zoneId)) || "").trim();
    if (explicitZoneId && zoneById[explicitZoneId] && !(usedMap && usedMap[explicitZoneId])) {
      return explicitZoneId;
    }
    const rawId = String((area && area.id) || "").trim();
    if (rawId && zoneById[rawId] && !(usedMap && usedMap[rawId])) {
      return rawId;
    }
    const areaLabel = normalizeZoneMatchText(area && area.label);
    if (areaLabel) {
      const byLabel = Object.keys(zoneById).find((zid) => {
        if (usedMap && usedMap[zid]) return false;
        const zl = normalizeZoneMatchText(zoneById[zid] && (zoneById[zid].label || zid));
        return zl === areaLabel;
      });
      if (byLabel) return byLabel;
    }
    const side = normalizeZoneMatchText(sideHint);
    if (side) {
      const bySide = Object.keys(zoneById).filter((zid) => {
        if (usedMap && usedMap[zid]) return false;
        const z = zoneById[zid] || {};
        let zside = normalizeZoneMatchText(z.side || z.anchor || "");
        if (!zside) {
          if (zid === "main_front" || zid === "top_left" || zid === "top_right" || zid === "top_center") zside = "front";
          else if (zid === "main_back") zside = "back";
          else if (/^front_/i.test(zid)) zside = "front";
          else if (/^back_/i.test(zid)) zside = "back";
          else if (zid === "sleeve_left" || zid === "side_left") zside = "left";
          else if (zid === "sleeve_right" || zid === "side_right") zside = "right";
          else zside = "front";
        }
        return zside === side;
      });
      if (bySide.length) return bySide[0];
    }
    return "";
  }

  function getMockupAreaMap() {
    const map = {};
    const zones = Array.isArray(templateZones) ? templateZones : [];
    const zoneById = {};
    zones.forEach((z) => {
      const zid = String((z && z.id) || "");
      if (!zid) return;
      zoneById[zid] = z;
    });
    const zoneIds = Object.keys(zoneById);
    const hasTemplateZoneMap = zoneIds.length > 0;
    const isPlaceholderWrapOnly = zoneIds.length === 1 && zoneIds[0] === "wrap";
    const zoneByIdForResolve = isPlaceholderWrapOnly ? {} : zoneById;
    // If template zones are missing/placeholder (e.g. only "wrap"), use mockup areas as source of truth.
    const enforceTemplateZoneIds = hasTemplateZoneMap && !isPlaceholderWrapOnly && zoneIds.length > 1;
    const mockups = getTemplateMockups();
    mockups.forEach((m, mi) => {
      const side = String(m.side || "custom");
      const usedAuto = {};
      const areas = Array.isArray(m.areas) ? m.areas : [];
      areas.forEach((a, ai) => {
        const fallbackId = String((a && a.id) || ("area_" + mi + "_" + ai));
        const explicitZoneId = String((a && (a.zone_id || a.zoneId)) || "").trim();
        const explicitAreaId = String((a && a.id) || "").trim();
        let id = "";
        if (explicitZoneId) {
          id = explicitZoneId;
        } else if (explicitAreaId) {
          id = explicitAreaId;
        } else {
          id = resolveAreaZoneId(a, zoneByIdForResolve, usedAuto, side) || fallbackId;
        }
        if (!id || map[id]) return;
        if (enforceTemplateZoneIds && !zoneById[id]) return;
        const rawExcludes = Array.isArray(a && a.excludes) ? a.excludes : [];
        const excludes = rawExcludes
          .map((v) => String(v || "").trim())
          .filter(Boolean)
          .map((eid) => {
            if (zoneById[eid]) return eid;
            return resolveAreaZoneId({ id: eid, label: eid }, zoneByIdForResolve, null, side) || eid;
          })
          .filter(Boolean);
        map[id] = {
          id: id,
          label: String((a && a.label) || (zoneById[id] && zoneById[id].label) || id),
          width_mm: Number((a && a.width_mm) || 0),
          height_mm: Number((a && a.height_mm) || 0),
          x_pct: Number((a && a.x_pct) || 0),
          y_pct: Number((a && a.y_pct) || 0),
          w_pct: Number((a && a.w_pct) || 0),
          h_pct: Number((a && a.h_pct) || 0),
          side: side,
          excludes: excludes,
          allow_subcanvas: !!(a && a.allow_subcanvas),
          portrait_width_mm: Number((a && a.portrait_width_mm) || 0),
          portrait_height_mm: Number((a && a.portrait_height_mm) || 0),
          landscape_width_mm: Number((a && a.landscape_width_mm) || 0),
          landscape_height_mm: Number((a && a.landscape_height_mm) || 0)
        };
        if (enforceTemplateZoneIds && zoneById[id]) usedAuto[id] = true;
      });
    });
    return map;
  }

  function getZoneConflictMap() {
    const out = {};
    const addNode = (id) => {
      const zid = String(id || "");
      if (!zid) return "";
      if (!out[zid]) out[zid] = {};
      return zid;
    };
    const addPair = (a, b) => {
      const za = addNode(a);
      const zb = addNode(b);
      if (!za || !zb || za === zb) return;
      out[za][zb] = true;
      out[zb][za] = true;
    };

    const areaMap = getMockupAreaMap();
    Object.keys(areaMap).forEach((id) => addNode(id));
    Object.keys(areaMap).forEach((id) => {
      const ex = Array.isArray(areaMap[id] && areaMap[id].excludes) ? areaMap[id].excludes : [];
      ex.forEach((raw) => {
        const eid = String(raw || "");
        if (!eid) return;
        addPair(id, eid);
      });
    });

    const tZones = Array.isArray(templateZones) ? templateZones : [];
    tZones.forEach((z) => {
      const zid = addNode(z && z.id);
      if (!zid) return;
      const ex = []
        .concat(Array.isArray(z && z.excludes) ? z.excludes : [])
        .concat(Array.isArray(z && z.conflicts) ? z.conflicts : [])
        .concat(Array.isArray(z && z.conflict_with) ? z.conflict_with : [])
        .concat(Array.isArray(z && z.not_with) ? z.not_with : [])
        .concat(Array.isArray(z && z.blocked_with) ? z.blocked_with : []);
      ex.forEach((raw) => {
        const eid = String(raw || "");
        if (!eid) return;
        addPair(zid, eid);
      });
    });

    const cfgConf = (previewConfig && previewConfig.zone_conflicts) ? previewConfig.zone_conflicts : null;
    if (cfgConf && typeof cfgConf === "object") {
      if (Array.isArray(cfgConf)) {
        cfgConf.forEach((row) => {
          if (!row || typeof row !== "object") return;
          const a = String(row.a || row.zone || row.id || "");
          const b = String(row.b || row.with || row.conflict || "");
          addPair(a, b);
        });
      } else {
        Object.keys(cfgConf).forEach((a) => {
          const list = Array.isArray(cfgConf[a]) ? cfgConf[a] : [];
          list.forEach((b) => addPair(a, b));
        });
      }
    }
    return out;
  }

  function shouldEnableZones(normalizedZones) {
    const zones = Array.isArray(normalizedZones) ? normalizedZones : [];
    const mapCount = Object.keys(getMockupAreaMap()).length;
    const effectiveMode = previewMode || ((previewType === "mug" || previewType === "bottle" || previewType === "tshirt") ? "3d" : "2d");
    if (effectiveMode === "3d") return false;
    // 2D mockup areas are the source of truth even when there is only one area.
    if (mapCount > 0) return true;
    // Multi-zone behavior is allowed only in 2D templates unless areas explicitly define multiple regions.
    return Math.max(zones.length, mapCount) > 1;
  }

  function getSelectedZoneIds() {
    if (!zoneState || !Array.isArray(zoneState.zones)) return [];
    if (zoneState.enabled && !zoneState.userSelectionReady) return [];
    const all = zoneState.zones.map((z) => String(z.id || "")).filter(Boolean);
    const selected = Array.isArray(zoneState.selectedIds) ? zoneState.selectedIds.map((id) => String(id || "")) : [];
    const normalized = selected.filter((id) => all.includes(id));
    return normalized.length ? normalized : all;
  }

  function isZoneSelected(zoneId) {
    const id = String(zoneId || "");
    if (!id) return false;
    return getSelectedZoneIds().includes(id);
  }

  function getZoneLabel(zoneId) {
    const id = String(zoneId || "");
    if (!id || !zoneState || !Array.isArray(zoneState.zones)) return id;
    const z = zoneState.zones.find((x) => String(x.id || "") === id);
    return z && z.label ? String(z.label) : id;
  }

  function getZoneMeta(zoneId) {
    const id = String(zoneId || "");
    if (id && designZoneAttrsOverride && designZoneAttrsOverride[id]) {
      const ov = designZoneAttrsOverride[id];
      return {
        id: id,
        label: String(ov.label || id),
        width_mm: Number(ov.width_mm || 0),
        height_mm: Number(ov.height_mm || 0),
        side: String(ov.side || "front"),
        allow_subcanvas: !!ov.allow_subcanvas,
        portrait_width_mm: Number(ov.portrait_width_mm || 0),
        portrait_height_mm: Number(ov.portrait_height_mm || 0),
        landscape_width_mm: Number(ov.landscape_width_mm || 0),
        landscape_height_mm: Number(ov.landscape_height_mm || 0)
      };
    }
    const areaMap = getMockupAreaMap();
    if (areaMap[id]) {
      return {
        id: id,
        label: areaMap[id].label || id,
        width_mm: Number(areaMap[id].width_mm || 0),
        height_mm: Number(areaMap[id].height_mm || 0),
        side: areaMap[id].side || "front",
        allow_subcanvas: !!areaMap[id].allow_subcanvas,
        portrait_width_mm: Number(areaMap[id].portrait_width_mm || 0),
        portrait_height_mm: Number(areaMap[id].portrait_height_mm || 0),
        landscape_width_mm: Number(areaMap[id].landscape_width_mm || 0),
        landscape_height_mm: Number(areaMap[id].landscape_height_mm || 0)
      };
    }
    if (previewType === "tshirt") {
      const fixed = {
        // Main zones use A4 working canvas; preview area remains 297x297 in zoneRect().
        main_front: { id: "main_front", label: "MainFront", width_mm: 210, height_mm: 297, side: "front" },
        main_back: { id: "main_back", label: "MainBack", width_mm: 210, height_mm: 297, side: "back" },
        top_left: { id: "top_left", label: "topLeft", width_mm: 120, height_mm: 120, side: "front" },
        top_right: { id: "top_right", label: "topRight", width_mm: 120, height_mm: 120, side: "front" },
        top_center: { id: "top_center", label: "topCenter", width_mm: 120, height_mm: 297, side: "front" }
      };
      if (fixed[id]) return fixed[id];
    }
    if (zoneState && zoneState.placements && zoneState.placements[id]) {
      const p = zoneState.placements[id];
      return {
        id: id,
        label: p.label || id,
        width_mm: Number(p.width_mm || 0),
        height_mm: Number(p.height_mm || 0),
        side: p.side || "front",
        placement: p
      };
    }
    const raw = Array.isArray(templateZones) ? templateZones : [];
    for (let i = 0; i < raw.length; i++) {
      const z = raw[i];
      if (z && String(z.id || "") === id) return z;
    }
    return null;
  }

  function isPlacementZoneId(zoneId) {
    const id = String(zoneId || "");
    return /^front_p_\d+$/i.test(id) || /^back_p_\d+$/i.test(id);
  }

  function zoneSide(zoneId) {
    const id = String(zoneId || "");
    const areaMap = getMockupAreaMap();
    if (areaMap[id] && areaMap[id].side) return String(areaMap[id].side);
    if (id === "main_front" || id === "top_left" || id === "top_right" || id === "top_center") return "front";
    if (id === "main_back") return "back";
    if (/^front_/i.test(id)) return "front";
    if (/^back_/i.test(id)) return "back";
    if (id === "sleeve_left" || id === "side_left") return "left";
    if (id === "sleeve_right" || id === "side_right") return "right";
    return "front";
  }

  function getTshirt2DConfig() {
    return (previewConfig && previewConfig.mockup_2d && typeof previewConfig.mockup_2d === "object")
      ? previewConfig.mockup_2d
      : {};
  }

  function getTshirtBodyBounds(side) {
    const twoD = getTshirt2DConfig();
    const s = String(side || "front");
    if (s === "back") {
      return {
        x: Number(twoD.back_body_x != null ? twoD.back_body_x : 0.13),
        y: Number(twoD.back_body_y != null ? twoD.back_body_y : 0.15),
        w: Number(twoD.back_body_w != null ? twoD.back_body_w : 0.74),
        h: Number(twoD.back_body_h != null ? twoD.back_body_h : 0.80)
      };
    }
    if (s === "left") return { x: 0.30, y: 0.20, w: 0.44, h: 0.68 };
    if (s === "right") return { x: 0.26, y: 0.20, w: 0.44, h: 0.68 };
    return {
      x: Number(twoD.front_body_x != null ? twoD.front_body_x : 0.13),
      y: Number(twoD.front_body_y != null ? twoD.front_body_y : 0.15),
      w: Number(twoD.front_body_w != null ? twoD.front_body_w : 0.74),
      h: Number(twoD.front_body_h != null ? twoD.front_body_h : 0.80)
    };
  }

  function getGarmentMmForSide(side) {
    const twoD = getTshirt2DConfig();
    const s = String(side || "front");
    const fw = Number(twoD.garment_front_width_mm || 500);
    const fh = Number(twoD.garment_front_height_mm || 750);
    const bw = Number(twoD.garment_back_width_mm || fw);
    const bh = Number(twoD.garment_back_height_mm || fh);
    if (s === "back") return { w: bw, h: bh };
    return { w: fw, h: fh };
  }

  function sideToMockupKey(side) {
    const s = String(side || "front");
    if (s === "back") return "back";
    if (s === "left") return "left";
    if (s === "right") return "right";
    return "front";
  }

  function buildContourMaskFromImage(img) {
    try {
      if (!img || !img.naturalWidth || !img.naturalHeight) return null;
      const w = Math.max(1, img.naturalWidth | 0);
      const h = Math.max(1, img.naturalHeight | 0);
      const off = document.createElement("canvas");
      off.width = w;
      off.height = h;
      const ictx = off.getContext("2d", { willReadFrequently: true });
      if (!ictx) return null;
      ictx.drawImage(img, 0, 0, w, h);
      const im = ictx.getImageData(0, 0, w, h);
      const d = im.data;
      const xMin = new Int32Array(h);
      const xMax = new Int32Array(h);
      for (let y = 0; y < h; y++) { xMin[y] = -1; xMax[y] = -1; }
      const darkThreshold = 85;
      for (let y = 0; y < h; y++) {
        let minX = -1;
        let maxX = -1;
        const row = y * w * 4;
        for (let x = 0; x < w; x++) {
          const i = row + (x * 4);
          const a = d[i + 3];
          if (a < 20) continue;
          const lum = (0.2126 * d[i]) + (0.7152 * d[i + 1]) + (0.0722 * d[i + 2]);
          if (lum <= darkThreshold) {
            if (minX === -1) minX = x;
            maxX = x;
          }
        }
        xMin[y] = minX;
        xMax[y] = maxX;
      }
      return { w: w, h: h, xMin: xMin, xMax: xMax };
    } catch (e) {
      return null;
    }
  }

  function contourRowRange(mask, py) {
    if (!mask) return null;
    const h = mask.h | 0;
    const y0 = Math.max(0, Math.min(h - 1, py | 0));
    const maxSearch = 8;
    for (let r = 0; r <= maxSearch; r++) {
      const ys = [];
      if (r === 0) ys.push(y0);
      else { ys.push(y0 - r, y0 + r); }
      for (let i = 0; i < ys.length; i++) {
        const y = ys[i];
        if (y < 0 || y >= h) continue;
        const mn = mask.xMin[y];
        const mx = mask.xMax[y];
        if (mn >= 0 && mx > mn) return { min: mn, max: mx };
      }
    }
    return null;
  }

  function pointInsideContour(side, xRel, yRel) {
    const key = sideToMockupKey(side);
    const mask = tshirtContourMasks[key];
    if (!mask) return true;
    const px = Math.round(Math.max(0, Math.min(1, Number(xRel))) * (mask.w - 1));
    const py = Math.round(Math.max(0, Math.min(1, Number(yRel))) * (mask.h - 1));
    const rr = contourRowRange(mask, py);
    if (!rr) return false;
    const inset = 2;
    return px >= (rr.min + inset) && px <= (rr.max - inset);
  }

  function rectInsideContour(side, x, y, w, h) {
    const cx = x + (w * 0.5);
    const cy = y + (h * 0.5);
    const pts = [
      [x, y], [x + w, y], [x, y + h], [x + w, y + h],
      [cx, y], [cx, y + h], [x, cy], [x + w, cy], [cx, cy]
    ];
    for (let i = 0; i < pts.length; i++) {
      if (!pointInsideContour(side, pts[i][0], pts[i][1])) return false;
    }
    return true;
  }

  function escAttr(v) {
    return String(v == null ? "" : v)
      .replace(/&/g, "&amp;")
      .replace(/"/g, "&quot;")
      .replace(/</g, "&lt;")
      .replace(/>/g, "&gt;");
  }

  function escHtml(v) {
    return String(v == null ? "" : v)
      .replace(/&/g, "&amp;")
      .replace(/</g, "&lt;")
      .replace(/>/g, "&gt;");
  }

  function cssEscapeSafe(v) {
    const s = String(v == null ? "" : v);
    try {
      if (typeof CSS !== "undefined" && CSS && typeof CSS.escape === "function") {
        return CSS.escape(s);
      }
    } catch (e) {}
    return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
  }

  function setStatus(msg) {
    const t = statusTextEl || statusEl;
    if (t) t.textContent = tStatus(msg || "");
  }

  function ensurePoweredBadge() {
    if (!statusEl) return;
    const poweredText = "Powered by Lorandit Design Studio Pro";
    let badge = statusEl.querySelector(".ds-status-powered");
    if (!badge) {
      badge = document.createElement("a");
      badge.className = "ds-status-powered";
      statusEl.appendChild(badge);
    }
    if (badge.tagName !== "A") {
      const link = document.createElement("a");
      link.className = "ds-status-powered";
      while (badge.firstChild) link.appendChild(badge.firstChild);
      badge.parentNode.replaceChild(link, badge);
      badge = link;
    }
    badge.setAttribute("href", "https://lorandit.com/");
    badge.setAttribute("target", "_blank");
    badge.setAttribute("rel", "noopener noreferrer nofollow");
    badge.setAttribute("aria-label", poweredText);
    let logo = badge.querySelector(".ds-status-powered-logo");
    if (!logo) {
      logo = document.createElement("img");
      logo.className = "ds-status-powered-logo";
      badge.insertBefore(logo, badge.firstChild || null);
    }
    if (statusPoweredLogoSrc) logo.setAttribute("src", statusPoweredLogoSrc);
    logo.setAttribute("alt", "Lorandit Design Studio Pro");
    logo.setAttribute("loading", "lazy");
    logo.setAttribute("decoding", "async");
    let text = badge.querySelector(".ds-status-powered-text");
    if (!text) {
      text = document.createElement("span");
      text.className = "ds-status-powered-text";
      badge.appendChild(text);
    }
    text.textContent = poweredText;
  }

  ensurePoweredBadge();

  function showDesignerToast(msg, tone) {
    const text = String(msg || "").trim();
    if (!text) return;
    let toast = root.querySelector('[data-role="designer-toast"]');
    if (!toast) {
      toast = document.createElement("div");
      toast.className = "ds-designer-toast";
      toast.setAttribute("data-role", "designer-toast");
      toast.setAttribute("aria-live", "polite");
      root.appendChild(toast);
    }
    toast.textContent = text;
    toast.classList.remove("is-success", "is-error", "is-visible");
    toast.classList.add(tone === "error" ? "is-error" : "is-success");
    toast.classList.add("is-visible");
    if (showDesignerToast.__timer) {
      try { window.clearTimeout(showDesignerToast.__timer); } catch (e) {}
    }
    showDesignerToast.__timer = window.setTimeout(() => {
      toast.classList.remove("is-visible");
    }, 2200);
  }

  function closePostSaveOptionsModal() {
    const modal = root.querySelector('[data-role="post-save-options-modal"]');
    if (!modal) return;
    modal.classList.remove("is-open");
    modal.setAttribute("aria-hidden", "true");
  }

  function openPostSaveOptionsModal() {
    let modal = root.querySelector('[data-role="post-save-options-modal"]');
    if (!modal) {
      modal = document.createElement("div");
      modal.className = "ds-post-save-modal";
      modal.setAttribute("data-role", "post-save-options-modal");
      modal.setAttribute("aria-hidden", "true");
      modal.innerHTML =
        '<div class="ds-post-save-dialog" role="dialog" aria-modal="true" aria-label="' + escAttr(tUi("design_saved", "Design Saved")) + '">' +
          '<div class="ds-post-save-head"><h3>' + escHtml(tUi("design_saved", "Design Saved")) + '</h3></div>' +
          '<p class="ds-post-save-help">' + escHtml(tUi("what_next_after_save", "What do you want to do next?")) + '</p>' +
          '<div class="ds-post-save-actions">' +
            '<button type="button" data-action="post-save-continue">' + escHtml(tUi("continue_editing", "Continue Editing")) + '</button>' +
            '<button type="button" data-action="post-save-template">' + escHtml(tUi("open_new_template", "Open New Template")) + '</button>' +
            '<button type="button" data-action="post-save-apply">' + escHtml(tUi("apply_to_product", "Apply to Product")) + '</button>' +
          '</div>' +
        "</div>";
      modal.addEventListener("click", (e) => {
        if (e.target === modal) {
          closePostSaveOptionsModal();
          return;
        }
        const btn = e.target && e.target.closest ? e.target.closest("button[data-action]") : null;
        if (!btn) return;
        const action = String(btn.getAttribute("data-action") || "");
        if (action === "post-save-continue") {
          closePostSaveOptionsModal();
          return;
        }
        if (action === "post-save-template") {
          closePostSaveOptionsModal();
          let opened = false;
          try {
            if (typeof root.__dsOpenMobileSheet === "function" && window.matchMedia("(max-width: 1023px)").matches) {
              root.__dsOpenMobileSheet("library");
              opened = true;
            }
          } catch (e2) {}
          if (!opened) {
            const mobileLibraryBtn = root.querySelector('[data-role="mobile-bottom-nav"] [data-mobile-action="library"]');
            if (mobileLibraryBtn && typeof mobileLibraryBtn.click === "function") {
              mobileLibraryBtn.click();
              opened = true;
            }
          }
          if (!opened) {
            const tplSelect = root.querySelector('.ds-toolbar [data-input="template-select"]');
            if (tplSelect) {
              try { tplSelect.focus(); } catch (e3) {}
              try { tplSelect.click(); } catch (e4) {}
              setStatus("Choose template and click Apply.");
            }
          }
          return;
        }
        if (action === "post-save-apply") {
          closePostSaveOptionsModal();
          if (typeof openApplyProductModal === "function") openApplyProductModal();
        }
      });
      root.appendChild(modal);
    }
    modal.classList.add("is-open");
    modal.setAttribute("aria-hidden", "false");
  }

  function normalizeDesignName(raw) {
    return String(raw || "").replace(/\s+/g, " ").trim();
  }

  function defaultDesignName() {
    const now = new Date();
    const yy = now.getFullYear();
    const mm = String(now.getMonth() + 1).padStart(2, "0");
    const dd = String(now.getDate()).padStart(2, "0");
    const hh = String(now.getHours()).padStart(2, "0");
    const mi = String(now.getMinutes()).padStart(2, "0");
    return tUi("design_default_prefix", "Design") + " " + yy + "-" + mm + "-" + dd + " " + hh + ":" + mi;
  }

  function closeSaveBeforeApplyModal() {
    const modal = root.querySelector('[data-role="save-before-apply-modal"]');
    if (!modal) return;
    modal.classList.remove("is-open");
    modal.setAttribute("aria-hidden", "true");
  }

  function applySaveBeforeApplyActionStyles(host) {
    if (!host || !host.querySelector) return;
    const cs = (typeof window !== "undefined" && window.getComputedStyle) ? window.getComputedStyle(root) : null;
    const secondary = normalizeHexColor((cs && cs.getPropertyValue("--ds-accent-secondary")) || "", "#2f80ed");
    const btnSave = host.querySelector('button[data-action="save-before-apply-save"]');
    const btnCancel = host.querySelector('button[data-action="save-before-apply-cancel"]');
    applyButtonThemeInline(
      btnSave,
      secondary,
      secondary,
      "#ffffff",
      "0 8px 18px " + secondary + "55",
      "linear-gradient(180deg, " + secondary + " 0%, " + secondary + " 100%)"
    );
    applyButtonThemeInline(
      btnCancel,
      "#ffffff",
      "#c5d1df",
      "#20354e",
      "0 4px 12px rgba(12, 26, 44, 0.08)",
      "none"
    );
  }

  function openSaveBeforeApplyModal() {
    let modal = root.querySelector('[data-role="save-before-apply-modal"]');
    if (!modal) {
      modal = document.createElement("div");
      modal.className = "ds-post-save-modal ds-save-before-apply-modal";
      modal.setAttribute("data-role", "save-before-apply-modal");
      modal.setAttribute("aria-hidden", "true");
      modal.innerHTML =
        '<div class="ds-post-save-dialog ds-save-before-apply-dialog" role="dialog" aria-modal="true" aria-label="' + escAttr(tUi("save_design", "Save Design")) + '">' +
          '<div class="ds-post-save-head"><h3>' + escHtml(tUi("save_design_first", "Save Design First")) + '</h3></div>' +
          '<p class="ds-post-save-help">' + escHtml(tUi("enter_design_name_then_save_apply", "Enter design name, then save and apply to product.")) + '</p>' +
          '<label class="ds-save-before-apply-field">' +
            '<span>' + escHtml(tUi("design_name", "Design Name")) + '</span>' +
            '<input type="text" data-role="save-before-apply-name" maxlength="120" />' +
          '</label>' +
          '<div class="ds-post-save-actions ds-save-before-apply-actions">' +
            '<button type="button" data-action="save-before-apply-save">' + escHtml(tUi("save_design", "Save Design")) + '</button>' +
            '<button type="button" data-action="save-before-apply-cancel">' + escHtml(tUi("cancel", "Cancel")) + '</button>' +
          '</div>' +
        '</div>';
      modal.addEventListener("click", (e) => {
        if (e.target === modal) {
          closeSaveBeforeApplyModal();
          return;
        }
        const btn = e.target && e.target.closest ? e.target.closest("button[data-action]") : null;
        if (!btn) return;
        const action = String(btn.getAttribute("data-action") || "");
        if (action === "save-before-apply-cancel") {
          closeSaveBeforeApplyModal();
          return;
        }
        if (action === "save-before-apply-save") {
          const input = modal.querySelector('[data-role="save-before-apply-name"]');
          const designName = normalizeDesignName(input && typeof input.value !== "undefined" ? input.value : "");
          if (!designName) {
            setStatus("Enter a design name.");
            try { if (input) input.focus(); } catch (e2) {}
            return;
          }
          closeSaveBeforeApplyModal();
          saveDesign(false, {
            designName: designName,
            autoApplyAfterSave: true
          });
        }
      });
      root.appendChild(modal);
    }
    const input = modal.querySelector('[data-role="save-before-apply-name"]');
    const suggested = normalizeDesignName(
      (window.DS_LAST_SAVED && typeof window.DS_LAST_SAVED === "object" && window.DS_LAST_SAVED.design_title)
        ? String(window.DS_LAST_SAVED.design_title)
        : defaultDesignName()
    );
    if (input) input.value = suggested;
    applySaveBeforeApplyActionStyles(modal);
    modal.classList.add("is-open");
    modal.setAttribute("aria-hidden", "false");
    try {
      if (input) {
        input.focus();
        input.setSelectionRange(0, String(input.value || "").length);
      }
    } catch (e3) {}
  }

  function populateFontFamilySelect() {
    if (!propsEl) return;
    const select = propsEl.querySelector('[data-prop="fontFamily"]');
    if (!select) return;
    select.innerHTML = "";
    const groups = {};
    fontCatalog.forEach((f) => {
      const g = String(f.group || "Fonts");
      if (!groups[g]) groups[g] = [];
      groups[g].push(f);
    });
    Object.keys(groups).forEach((gname) => {
      const og = document.createElement("optgroup");
      og.label = gname;
      groups[gname].forEach((f) => {
        const opt = document.createElement("option");
        opt.value = String(f.stack || f.name || "");
        opt.textContent = String(f.name || f.stack || "Font");
        opt.style.fontFamily = String(f.stack || f.name || "");
        og.appendChild(opt);
      });
      select.appendChild(og);
    });
    try {
      const selected = String(select.value || "");
      select.style.fontFamily = selected || "inherit";
    } catch (e) {}
  }

  function primaryFontFamilyFromStack(stack) {
    const raw = String(stack || "").split(",")[0] || "";
    return raw.trim().replace(/^['"]+|['"]+$/g, "");
  }

  function ensureWebFontLoaded(stack) {
    try {
      const family = primaryFontFamilyFromStack(stack);
      if (!family) return Promise.resolve(false);
      const generic = {
        "serif": 1, "sans-serif": 1, "monospace": 1, "cursive": 1, "fantasy": 1,
        "system-ui": 1, "ui-sans-serif": 1, "ui-serif": 1, "ui-monospace": 1,
        "emoji": 1, "math": 1, "fangsong": 1
      };
      if (generic[String(family).toLowerCase()]) return Promise.resolve(false);
      if (webFontLoadCache[family]) return webFontLoadCache[family];
      if (typeof document === "undefined" || !document.head) return Promise.resolve(false);

      const id = "ds-font-" + family.toLowerCase().replace(/[^a-z0-9]+/g, "-");
      if (document.getElementById(id)) {
        webFontLoadCache[family] = Promise.resolve(true);
        return webFontLoadCache[family];
      }

      const href = "https://fonts.googleapis.com/css2?family=" +
        encodeURIComponent(family).replace(/%20/g, "+") +
        ":wght@400;700&display=swap";
      const link = document.createElement("link");
      link.id = id;
      link.rel = "stylesheet";
      link.href = href;
      webFontLoadCache[family] = new Promise((resolve) => {
        let done = false;
        const finish = (ok) => {
          if (done) return;
          done = true;
          resolve(!!ok);
        };
        link.onload = () => finish(true);
        link.onerror = () => finish(false);
        window.setTimeout(() => finish(true), 1200);
      });
      document.head.appendChild(link);
      return webFontLoadCache[family];
    } catch (e) {
      return Promise.resolve(false);
    }
  }

  function normalizeOrientation(v) {
    const s = String(v || "").toLowerCase();
    if (s === "landscape") return "landscape";
    return "portrait";
  }

  function zoneSupportsOrientation(zoneId) {
    const z = getZoneMeta(zoneId);
    if (!z) return false;
    return !!z.allow_subcanvas;
  }

  function getCurrentZoneOrientation(zoneId) {
    if (!zoneSupportsOrientation(zoneId)) return "portrait";
    if (!zoneState || !zoneState.enabled) {
      if (orientationState && orientationState.mode) return normalizeOrientation(orientationState.mode);
      return "portrait";
    }
    const id = String(zoneId || zoneState.currentId || "");
    if (!id) return "portrait";
    const v = zoneState.orientations && zoneState.orientations[id]
      ? zoneState.orientations[id]
      : "portrait";
    return normalizeOrientation(v);
  }

  function setProjectionModeDebug(mode) {
    try {
      if (root) root.setAttribute("data-ds-projection-mode", String(mode || ""));
      if (typeof window !== "undefined") window.__DS_PROJECTION_MODE__ = String(mode || "");
    } catch (e) {}
  }

  function requireAuthForOutput(actionName) {
    if (canOutput) return true;
    const actionMap = {
      "My Designs": tUi("my_designs", "My Designs"),
      "Apply to Product": tUi("apply_to_product", "Apply to Product"),
      "Export": tUi("export", "Export"),
      "Save": tUi("save", "Save"),
      "Final Save": tUi("final_save", "Final Save")
    };
    const rawAction = String(actionName || "").trim();
    const actionLabel = actionMap[rawAction] || rawAction || tUi("this_action", "This action");
    const loginHint = loginUrl
      ? tUi("please_log_in_continue", " Please log in to continue.")
      : tUi("login_required_short", " Login required.");
    setStatus(tUi("available_registered_users", "%s is available only for registered users.%s", [actionLabel, loginHint]));
    return false;
  }

  function isProtectionEnabled() {
    return !!protectionState.enabled;
  }

  async function refreshProtectionState(force) {
    if (!isProtectionEnabled()) return protectionState;
    if (protectionState.loaded && !force) return protectionState;
    const checkUrl = cfg.rest && cfg.rest.base ? (cfg.rest.base + "/protection/check") : "";
    if (!checkUrl) return protectionState;
    try {
      const res = await fetch(checkUrl, {
        method: "GET",
        headers: { "X-WP-Nonce": cfg.rest.nonce }
      }).then(async (r) => {
        const body = await r.json().catch(() => ({}));
        if (!r.ok) throw new Error((body && body.message) ? String(body.message) : ("Protection check failed (" + r.status + ")"));
        return body || {};
      });
      protectionState.enabled = !!Number(res.enabled || 0);
      protectionState.is_pro = !!Number(res.is_pro || 0);
      protectionState.trial_total = Math.max(0, Number(res.trial_total || protectionState.trial_total || 0) || 0);
      protectionState.trial_remaining = Math.max(0, Number(res.trial_remaining || 0) || 0);
      protectionState.loaded = true;
    } catch (e) {
      // Keep local defaults on transient network/API issues.
    }
    return protectionState;
  }

  async function ensureProtectedActionAllowed(action, consumeNow) {
    if (!isProtectionEnabled()) return true;
    const st = await refreshProtectionState(false);
    if (!st.enabled || st.is_pro) return true;
    const remaining = Math.max(0, Number(st.trial_remaining || 0) || 0);
    if (remaining <= 0) {
      setStatus("Trial limit reached. Upgrade to Pro to continue.");
      return false;
    }
    if (!consumeNow) return true;
    const consumeUrl = cfg.rest && cfg.rest.base ? (cfg.rest.base + "/protection/consume") : "";
    if (!consumeUrl) return true;
    try {
      const res = await fetch(consumeUrl, {
        method: "POST",
        headers: { "Content-Type": "application/json", "X-WP-Nonce": cfg.rest.nonce },
        body: JSON.stringify({ action: String(action || "") })
      }).then(async (r) => {
        const body = await r.json().catch(() => ({}));
        if (!r.ok) throw new Error((body && body.message) ? String(body.message) : ("Trial consume failed (" + r.status + ")"));
        return body || {};
      });
      if (Object.prototype.hasOwnProperty.call(res, "trial_remaining")) {
        protectionState.trial_remaining = Math.max(0, Number(res.trial_remaining || 0) || 0);
      } else {
        protectionState.trial_remaining = Math.max(0, protectionState.trial_remaining - 1);
      }
      protectionState.loaded = true;
      return true;
    } catch (e) {
      setStatus((e && e.message) ? e.message : "Action blocked by protection.");
      return false;
    }
  }

  function openPropsModal() {
    const obj = getSingleActive();
    if (!obj) {
      setStatus("Select one object, then click Object.");
      return;
    }
    if (!propsModalEl) return;
    propsModalEl.classList.add("is-open");
    propsModalEl.setAttribute("aria-hidden", "false");
    updateProps();
    updateLayerList();
  }

  function closePropsModal() {
    if (!propsModalEl) return;
    propsModalEl.classList.remove("is-open");
    propsModalEl.setAttribute("aria-hidden", "true");
  }

  function fetchJSON(url, options) {
    return fetch(url, options).then(async (r) => {
      let body = null;
      try { body = await r.json(); } catch (e) {}
      if (!r.ok) {
        const msg = body && body.message ? String(body.message) : ("Request failed (" + r.status + ")");
        throw new Error(msg);
      }
      return body || {};
    });
  }

  function buildEditorUrlForTemplate(templateId) {
    const base = cfg.editor_url || window.location.href;
    const url = new URL(base, window.location.origin);
    url.searchParams.set("ds_template", String(templateId));
    url.searchParams.set("ds_pick", "1");
    url.searchParams.set("_dsr", String(Date.now()));
    try {
      const keepFs = root.classList.contains("ds-fullscreen") || window.sessionStorage.getItem("ds_editor_fullscreen") === "1";
      if (keepFs) url.searchParams.set("ds_fullscreen", "1");
      else url.searchParams.delete("ds_fullscreen");
    } catch (e) {}
    const chosenDesign = designIdInput ? parseInt(designIdInput.value || "0", 10) : 0;
    if (chosenDesign > 0) url.searchParams.set("ds_design", String(chosenDesign));
    else url.searchParams.delete("ds_design");
    return url.toString();
  }
  function navigateToTemplateAndDesign(templateId, designId) {
    const tid = parseInt(templateId || "0", 10);
    const did = parseInt(designId || "0", 10);
    if (!tid || !did) return;
    persistTemplateOverride(tid);
    markStartupAutoOpenZonesForTemplate(tid);
    const base = cfg.editor_url || window.location.href;
    const nextUrl = new URL(base, window.location.origin);
    nextUrl.searchParams.set("ds_template", String(tid));
    nextUrl.searchParams.set("ds_pick", "1");
    nextUrl.searchParams.set("ds_design", String(did));
    nextUrl.searchParams.delete("ds_start");
    nextUrl.searchParams.delete("ds_zsel");
    nextUrl.searchParams.delete("ds_step");
    nextUrl.searchParams.delete("ds_open_zones");
    nextUrl.searchParams.set("_dsr", String(Date.now()));
    try { window.sessionStorage.setItem("ds_pending_design_id", String(did)); } catch (e) {}
    window.location.href = nextUrl.toString();
  }

  function persistTemplateOverride(templateId) {
    const id = parseInt(templateId || "0", 10);
    if (!id) return;
    try {
      window.localStorage.setItem("ds_template_override", String(id));
    } catch (e) {}
    try {
      document.cookie = "ds_template_override=" + encodeURIComponent(String(id)) + "; path=/; max-age=2592000; SameSite=Lax";
    } catch (e) {}
  }

  function populateTemplateSelect(items) {
    if (!templateSelectInput) return;
    const list = Array.isArray(items) ? items : [];
    templatesCache = list;
    const currentId = Number(cfg.id || cfg.template_id || 0);
      const options = ['<option value="">' + escHtml(tUi("choose_template_option", "Choose Template")) + '</option>'];
    list.forEach((t) => {
      const id = Number(t.id || 0);
      if (!id) return;
      const selected = id === currentId ? ' selected="selected"' : "";
      const title = String(t.title || ("Template #" + id));
      const size = (Number(t.width_mm) > 0 && Number(t.height_mm) > 0)
        ? (" (" + Number(t.width_mm) + "x" + Number(t.height_mm) + " mm)")
        : "";
      options.push('<option value="' + id + '"' + selected + '>' + escAttr(title + size) + "</option>");
    });
    templateSelectInput.innerHTML = options.join("");
  }

  function loadTemplateOptions() {
    if (!templateSelectInput) return;
    const hasServerOptions = templateSelectInput.options && templateSelectInput.options.length > 1;
    if (Array.isArray(cfg.templates) && cfg.templates.length) {
      populateTemplateSelect(cfg.templates);
    } else if (!hasServerOptions) {
      const currentId = Number(cfg.id || cfg.template_id || 0);
      const currentTitle = String(cfg.title || ("Template #" + currentId));
      templateSelectInput.innerHTML = '<option value="' + currentId + '" selected="selected">' + escAttr(currentTitle) + "</option>";
    }
    fetchJSON(cfg.rest.base + "/templates", {
      method: "GET",
      headers: { "X-WP-Nonce": cfg.rest.nonce }
    }).then((items) => {
      if (Array.isArray(items) && items.length) {
        populateTemplateSelect(items);
      }
    }).catch(() => {
      // keep already-populated fallback from DS_BOOT
    });
  }

  function ensureDesignLoadOverlay() {
    let overlay = root.querySelector('[data-role="ds-load-overlay"]');
    if (overlay) return overlay;
    overlay = document.createElement("div");
    overlay.className = "ds-load-overlay";
    overlay.setAttribute("data-role", "ds-load-overlay");
    overlay.setAttribute("aria-hidden", "true");
    overlay.innerHTML =
      '<div class="ds-load-overlay-card">' +
        '<div class="ds-load-overlay-main">' +
          '<span class="ds-load-overlay-spinner" aria-hidden="true"></span>' +
          '<span class="ds-load-overlay-text" data-role="ds-load-overlay-text">' + escHtml(tUi("loading_design", "Loading design...")) + '</span>' +
        '</div>' +
        '<a class="ds-load-overlay-powered" href="https://lorandit.com/" target="_blank" rel="noopener noreferrer nofollow" aria-label="Powered by Lorandit Design Studio Pro">' +
          '<img class="ds-load-overlay-powered-logo" data-role="ds-load-overlay-powered-logo" alt="Lorandit Design Studio Pro" loading="lazy" decoding="async" />' +
          '<span class="ds-load-overlay-powered-text">Powered by Lorandit Design Studio Pro</span>' +
        '</a>' +
      "</div>";
    const brandLogo = overlay.querySelector('[data-role="ds-load-overlay-powered-logo"]');
    if (brandLogo && statusPoweredLogoSrc) {
      brandLogo.setAttribute("src", statusPoweredLogoSrc);
    }
    root.appendChild(overlay);
    return overlay;
  }

  function setDesignLoadBusy(isBusy, label) {
    const overlay = ensureDesignLoadOverlay();
    if (!overlay) return;
    const txt = overlay.querySelector('[data-role="ds-load-overlay-text"]');
    if (txt && label) txt.textContent = tStatus(label);
    overlay.classList.toggle("is-visible", !!isBusy);
    overlay.setAttribute("aria-hidden", isBusy ? "false" : "true");
    root.classList.toggle("ds-load-busy", !!isBusy);
  }

  function loadDesignById(id) {
    const designId = parseInt(id || "0", 10);
    if (!designId) {
      setStatus("Enter a valid Design ID.");
      return Promise.resolve(false);
    }
    setDesignLoadBusy(true, tUi("loading_design", "Loading design..."));
    setStatus("Loading design...");
    return fetchJSON(cfg.rest.base + "/design/" + encodeURIComponent(designId), {
      method: "GET",
      headers: { "X-WP-Nonce": cfg.rest.nonce }
    }).then((res) => {
      const designTitle = normalizeDesignName(res && res.title ? res.title : tUi("design_fallback", "Design #%s", [designId]));
      const layers = (res && typeof res.layers !== "undefined") ? res.layers : null;
      if (!layers) {
        setStatus("Design JSON not found.");
        setDesignLoadBusy(false);
        return false;
      }
      const currentTemplateId = Number(cfg.id || cfg.template_id || 0);
      const designTemplateId = Number(res && res.template_id ? res.template_id : 0);
      if (currentTemplateId > 0 && designTemplateId > 0 && currentTemplateId !== designTemplateId) {
        setStatus("This design belongs to a different template. Select the matching template first.");
        setDesignLoadBusy(false);
        return false;
      }
      clearLocalDraft();
      applyDesignZoneSnapshot(res);
      const loadedZonesUsed = [];
      const pushLoadedZone = (zid) => {
        const key = String(zid || "").trim();
        if (!key || loadedZonesUsed.includes(key)) return;
        loadedZonesUsed.push(key);
      };
      if (res && Array.isArray(res.zones_used)) {
        res.zones_used.forEach((zid) => pushLoadedZone(zid));
      }
      if (!loadedZonesUsed.length && layers && typeof layers === "object" && String(layers.type || "") === "zoned") {
        if (Array.isArray(layers.active_zones)) {
          layers.active_zones.forEach((zid) => pushLoadedZone(zid));
        }
        if (!loadedZonesUsed.length && layers.zones && typeof layers.zones === "object") {
          Object.keys(layers.zones).forEach((zid) => {
            const snap = layers.zones[zid];
            const hasObjects = !!(snap && Array.isArray(snap.objects) && snap.objects.length > 0);
            if (hasObjects) pushLoadedZone(zid);
          });
        }
        if (!loadedZonesUsed.length) {
          pushLoadedZone(layers.active_zone || "");
        }
      }
      if (designIdInput) designIdInput.value = String(designId);
      try {
        window.DS_LAST_SAVED = {
          id: "design-" + designId,
          png: String((res && res.png) || ""),
          design_post_id: designId,
          design_title: designTitle
        };
      } catch (e0) {}
      return new Promise((resolve) => {
        loadPersistedLayersPayload(layers, () => {
          if (zoneState && zoneState.enabled) {
            applyLoadedZoneSelection(
              loadedZonesUsed,
              (layers && typeof layers === "object") ? String(layers.active_zone || "") : ""
            );
            // When loading an existing design we must bypass/close initial zone step.
            try { setZoneSelectionGate(false); } catch (e0) {}
            try {
              const modal = zoneState.zonePickerModal && zoneState.zonePickerModal.wrap ? zoneState.zonePickerModal.wrap : null;
              if (modal) {
                modal.classList.remove("is-open");
                modal.setAttribute("aria-hidden", "true");
              }
            } catch (e1) {}
          }
          setDesignLoadBusy(true, tUi("preparing_previews", "Preparing previews..."));
        verifyAndWarmLoadedZones(layers, loadedZonesUsed, () => {
          if (zoneState && zoneState.enabled) {
            const normalized = normalizeLoadedZoneSelectionIds(
              loadedZonesUsed,
              (layers && typeof layers === "object") ? String(layers.active_zone || "") : ""
            );
            if (normalized.length) zoneState.selectedIds = normalized.slice();
            zoneState.userSelectionReady = true;
            try { setZoneSelectionGate(false); } catch (e2) {}
            try {
              const modal = zoneState.zonePickerModal && zoneState.zonePickerModal.wrap ? zoneState.zonePickerModal.wrap : null;
              if (modal) {
                modal.classList.remove("is-open");
                modal.setAttribute("aria-hidden", "true");
              }
            } catch (e3) {}
          }
          resetHistory();
          setStatus(tUi("loaded_design_named", "Loaded design: %s", [designTitle]));
          setDesignLoadBusy(false);
          resolve(true);
        });
        });
      });
    }).catch((err) => {
      setStatus(err && err.message ? err.message : "Load error.");
      setDesignLoadBusy(false);
      return false;
    });
  }

  function verifyAndWarmLoadedZones(layersPayload, preferredZoneIds, done) {
    const finish = () => { if (typeof done === "function") done(); };
    if (!zoneState || !zoneState.enabled) return finish();
    if (!layersPayload || typeof layersPayload !== "object") return finish();
    if (String(layersPayload.type || "") !== "zoned") return finish();
    const zonesMap = (layersPayload.zones && typeof layersPayload.zones === "object") ? layersPayload.zones : null;
    if (!zonesMap) return finish();

    const normalizeZoneKey = (v) => String(v || "").trim();
    const normalizeZoneKeyLegacy = (v) => String(v || "").toLowerCase().replace(/[^a-z0-9_-]/g, "");
    const findLegacyUniqueMatch = (sourceMap, legacyKey) => {
      if (!sourceMap || typeof sourceMap !== "object" || !legacyKey) return null;
      const hits = Object.keys(sourceMap).filter((k) => normalizeZoneKeyLegacy(k) === legacyKey);
      return hits.length === 1 ? sourceMap[hits[0]] : null;
    };
    const findZonePayload = (zoneId) => {
      const directKey = normalizeZoneKey(zoneId);
      if (directKey && zonesMap[directKey]) return zonesMap[directKey];
      const legacyKey = normalizeZoneKeyLegacy(zoneId);
      if (legacyKey && zonesMap[legacyKey]) return zonesMap[legacyKey];
      const byLegacyUnique = findLegacyUniqueMatch(zonesMap, legacyKey);
      if (byLegacyUnique) return byLegacyUnique;
      return null;
    };

    const allIds = Array.isArray(zoneState.zones)
      ? zoneState.zones.map((z) => String((z && z.id) || "")).filter(Boolean)
      : [];
    const preferred = Array.isArray(preferredZoneIds)
      ? preferredZoneIds.map((z) => String(z || "")).filter((z) => allIds.includes(z))
      : [];
    const ordered = preferred.length ? preferred.slice() : allIds.slice();
    if (!ordered.length) return finish();

    // Verify each loaded zone snapshot quickly (count mismatch => restore from payload).
    ordered.forEach((zid) => {
      const expected = findZonePayload(zid);
      if (!expected || !Array.isArray(expected.objects)) return;
      const current = zoneState.snapshots[zid];
      const currCount = (current && Array.isArray(current.objects)) ? current.objects.length : -1;
      const expectedCount = expected.objects.length;
      if (currCount !== expectedCount) {
        zoneState.snapshots[zid] = expected;
      }
    });

    const finalZone = String((zoneState.currentId && ordered.includes(zoneState.currentId)) ? zoneState.currentId : ordered[0] || "");
    const warmOrder = ordered.filter((z) => z !== finalZone);
    const prevInternal = !!zoneState.internalSwitch;
    zoneState.internalSwitch = true;

    let idx = 0;
    const warmDelayMs = 42;
    const ensureCriticalZones = (cb) => {
      const critical = [];
      if (ordered[0]) critical.push(String(ordered[0]));
      if (finalZone && !critical.includes(finalZone)) critical.push(finalZone);
      let cidx = 0;
      const nextCritical = () => {
        if (cidx >= critical.length) {
          if (typeof cb === "function") cb();
          return;
        }
        const zid = String(critical[cidx++] || "");
        if (!zid) return nextCritical();
        const expected = findZonePayload(zid);
        if (!expected || !Array.isArray(expected.objects)) return nextCritical();
        const expectedCount = expected.objects.length;
        const current = zoneState.snapshots[zid];
        const currCount = (current && Array.isArray(current.objects)) ? current.objects.length : -1;
        // Force-restore critical zones if expected payload has content but state is empty/mismatched.
        if (expectedCount > 0 && currCount !== expectedCount) {
          zoneState.snapshots[zid] = expected;
          return window.setTimeout(() => {
            switchToZone(zid, nextCritical);
          }, warmDelayMs);
        }
        nextCritical();
      };
      nextCritical();
    };
    const next = () => {
      if (idx >= warmOrder.length) {
        ensureCriticalZones(() => {
          if (!finalZone) {
            zoneState.internalSwitch = prevInternal;
            return finish();
          }
          window.setTimeout(() => {
            switchToZone(finalZone, () => {
              zoneState.internalSwitch = prevInternal;
              finish();
            });
          }, warmDelayMs);
        });
        return;
      }
      const zid = warmOrder[idx++];
      window.setTimeout(() => {
        switchToZone(zid, next);
      }, warmDelayMs);
    };
    next();
  }

  function closeMyDesignsModal() {
    if (!myDesignsModalEl) return;
    myDesignsModalEl.classList.remove("is-open");
    myDesignsModalEl.setAttribute("aria-hidden", "true");
  }

  function renderMyDesigns(items, opts) {
    if (!myDesignsListEl) return;
    const mode = (opts && opts.mode) ? String(opts.mode) : "my";
    const arr = Array.isArray(items) ? items : [];
    if (!arr.length) {
      myDesignsListEl.innerHTML = '<div class="ds-my-designs-empty">' + escHtml(tUi("no_designs_found", "No designs found.")) + "</div>";
      return;
    }
    myDesignsListEl.innerHTML = arr.map((d) => {
      const id = Number(d.id) || 0;
      const designTitle = String(d && d.title ? d.title : tUi("design_fallback", "Design #%s", [id]));
      const vis = String(d.visibility || "private");
      const visLabel = vis === "shared"
        ? tUi("visibility_shared", "Shared")
        : tUi("visibility_private", "Private");
      const next = vis === "shared" ? "private" : "shared";
      const date = d.created_at ? new Date(String(d.created_at)).toLocaleString() : "";
      const template = d.template_title ? String(d.template_title) : tUi("template_fallback", "Template #%s", [Number(d.template_id) || 0]);
      const mismatch = Number(d.template_id) !== Number(cfg.id);
      const isOwner = Number(d.is_owner || 0) === 1;
      const png = d.png ? String(d.png) : "";
      const zonesUsed = Array.isArray(d.zones_used) ? d.zones_used.map((z) => String(z || "")).filter(Boolean) : [];
      const zonesPng = (d && d.zones_png && typeof d.zones_png === "object") ? d.zones_png : {};
      const setThumbs = zonesUsed.map((zid) => {
        const zpng = zonesPng[zid] ? String(zonesPng[zid]) : "";
        if (!zpng) return "";
        return '' +
          '<div class="ds-my-design-set-item">' +
            '<img src="' + escAttr(zpng) + '" alt="' + escAttr(tUi("zone_label", "Zone %s", [zid])) + '" loading="lazy" />' +
            '<span>' + escAttr(zid) + '</span>' +
          '</div>';
      }).filter(Boolean).join("");
      const setHtml = setThumbs
        ? ('<div class="ds-my-design-set"><div class="ds-my-design-set-grid">' + setThumbs + '</div></div>')
        : (png ? '<div class="ds-my-design-thumb"><img src="' + escAttr(png) + '" alt="' + escAttr(designTitle) + '" loading="lazy" /></div>' : '');
      const usePng = zonesUsed.length && zonesPng[zonesUsed[0]] ? String(zonesPng[zonesUsed[0]]) : png;
      let actionsHtml = '';
      if (mode === "start-load") {
        actionsHtml =
          '<button type="button" data-action="my-design-start-load" data-id="' + id + '" data-template-id="' + escAttr(String(Number(d.template_id) || 0)) + '" data-title="' + escAttr(designTitle) + '">' + escHtml(tUi("load", "Load")) + "</button>";
      } else {
        actionsHtml =
          '<button type="button" data-action="my-design-use" data-id="' + id + '" data-png="' + escAttr(usePng) + '" data-title="' + escAttr(designTitle) + '">' + escHtml(tUi("use_on_product", "Use on product")) + "</button>" +
          '<button type="button" data-action="my-design-load" data-id="' + id + '" data-template-id="' + escAttr(String(Number(d.template_id) || 0)) + '" data-title="' + escAttr(designTitle) + '">' + escHtml(tUi("load", "Load")) + "</button>" +
          (isOwner
            ? ('<button type="button" data-action="my-design-visibility" data-id="' + id + '" data-next="' + next + '">' + visLabel + '</button>')
            : '');
      }
      return '' +
        '<div class="ds-my-design-item' + (setThumbs ? ' ds-my-design-item-set' : '') + '" data-design-id="' + id + '">' +
          setHtml +
          '<div class="ds-my-design-meta">' +
            '<strong>' + escHtml(designTitle) + '</strong> <span class="ds-my-design-template">' + escHtml(template) + '</span>' +
            '<span>' + date + (mismatch ? (" | " + escHtml(tUi("different_template", "Different template"))) : "") + (zonesUsed.length ? (" | " + escHtml(tUi("set_zones_count", "Set zones: %s", [zonesUsed.length]))) : "") + '</span>' +
          '</div>' +
          '<div class="ds-my-design-actions">' +
            actionsHtml +
          '</div>' +
        '</div>';
    }).join("");
  }

  function useDesignOnProduct(designPostId, pngUrl, designTitle) {
    const postId = parseInt(designPostId || "0", 10);
    if (!postId) return;
    const normalizedTitle = normalizeDesignName(designTitle || tUi("design_fallback", "Design #%s", [postId]));
    const detail = {
      id: "design-" + postId,
      png: pngUrl ? String(pngUrl) : "",
      design_post_id: postId,
      design_title: normalizedTitle
    };
    window.DS_LAST_SAVED = detail;
    try {
      window.dispatchEvent(new CustomEvent("ds:designSaved", { detail: detail }));
    } catch (e) {}
    if (designIdInput) designIdInput.value = String(postId);
    setStatus(tUi("design_selected_for_product", 'Design "%s" is selected for this product.', [normalizedTitle]));
  }

  function loadMyDesigns() {
    if (!myDesignsListEl || myDesignsLoading) return;
    myDesignsLoading = true;
    myDesignsListEl.innerHTML = '<div class="ds-my-designs-empty">' + escHtml(tUi("loading", "Loading...")) + "</div>";
    fetchJSON(cfg.rest.base + "/my-designs", {
      method: "GET",
      headers: { "X-WP-Nonce": cfg.rest.nonce }
    }).then((res) => {
      renderMyDesigns(res && res.items ? res.items : [], { mode: "my" });
    }).catch((err) => {
      myDesignsListEl.innerHTML = '<div class="ds-my-designs-empty">' + escHtml(tUi("error_loading_designs", "Error loading designs.")) + "</div>";
      setStatus(err && err.message ? err.message : "My Designs load error.");
    }).finally(() => {
      myDesignsLoading = false;
    });
  }

  function loadDesignLibraryForStart() {
    if (!myDesignsListEl || myDesignsLoading) return;
    myDesignsLoading = true;
    myDesignsListEl.innerHTML = '<div class="ds-my-designs-empty">' + escHtml(tUi("loading", "Loading...")) + "</div>";
    fetchJSON(cfg.rest.base + "/design-library", {
      method: "GET",
      headers: { "X-WP-Nonce": cfg.rest.nonce }
    }).then((res) => {
      renderMyDesigns(res && res.items ? res.items : [], { mode: "start-load" });
    }).catch((err) => {
      myDesignsListEl.innerHTML = '<div class="ds-my-designs-empty">' + escHtml(tUi("error_loading_designs", "Error loading designs.")) + "</div>";
      setStatus(err && err.message ? err.message : "Load Design error.");
    }).finally(() => {
      myDesignsLoading = false;
    });
  }

  function openMyDesignsModal() {
    if (!canOutput) { requireAuthForOutput("My Designs"); return; }
    if (!myDesignsModalEl) return;
    myDesignsMode = "my";
    if (myDesignsTitleEl) myDesignsTitleEl.textContent = tUi("my_designs", "My Designs");
    myDesignsModalEl.classList.add("is-open");
    myDesignsModalEl.setAttribute("aria-hidden", "false");
    loadMyDesigns();
  }
  function openStartLoadDesignModal() {
    if (!myDesignsModalEl) return;
    myDesignsMode = "start-load";
    if (myDesignsTitleEl) myDesignsTitleEl.textContent = tUi("load_design", "Load Design");
    myDesignsModalEl.classList.add("is-open");
    myDesignsModalEl.setAttribute("aria-hidden", "false");
    loadDesignLibraryForStart();
  }

  function closeApplyProductModal() {
    if (!applyProductModalEl) return;
    applyProductModalEl.classList.remove("is-open");
    applyProductModalEl.setAttribute("aria-hidden", "true");
  }

  function getEmptySelectedZoneIds() {
    if (!zoneState || !zoneState.enabled) return [];
    const selected = getSelectedZoneIds();
    if (!selected.length) return [];
    const out = [];
    selected.forEach((zid) => {
      if (!zoneHasContent(zid)) out.push(String(zid || ""));
    });
    return out;
  }

  function openEmptyZonesGuardModal(opts) {
    const options = opts || {};
    const proceed = typeof options.onContinue === "function" ? options.onContinue : function () {};
    const empties = Array.isArray(options.emptyZoneIds) ? options.emptyZoneIds.map((z) => String(z || "")).filter(Boolean) : [];
    if (!empties.length) return false;
    let modal = root.querySelector('[data-role="empty-zones-guard-modal"]');
    const closeModal = () => {
      if (!modal) return;
      modal.classList.remove("is-open");
      modal.setAttribute("aria-hidden", "true");
    };
    if (!modal) {
      modal = document.createElement("div");
      modal.className = "ds-empty-zones-modal";
      modal.setAttribute("data-role", "empty-zones-guard-modal");
      modal.setAttribute("aria-hidden", "true");
      modal.innerHTML =
        '<div class="ds-empty-zones-dialog" role="dialog" aria-modal="true" aria-label="' + escAttr(tUi("empty_print_areas", "Empty Print Areas")) + '">' +
          '<div class="ds-empty-zones-head"><h3>' + escHtml(tUi("empty_print_areas", "Empty Print Areas")) + '</h3></div>' +
          '<p class="ds-empty-zones-help" data-role="empty-zones-help"></p>' +
          '<div class="ds-empty-zones-actions">' +
            '<button type="button" data-action="empty-zones-continue">' + escHtml(tUi("continue_anyway", "Continue Anyway")) + '</button>' +
            '<button type="button" data-action="empty-zones-go">' + escHtml(tUi("go_to_empty_zone", "Go To Empty Zone")) + '</button>' +
            '<button type="button" data-action="empty-zones-remove">' + escHtml(tUi("remove_empty_zones", "Remove Empty Zones")) + '</button>' +
            '<button type="button" data-action="empty-zones-cancel">' + escHtml(tUi("cancel", "Cancel")) + '</button>' +
          '</div>' +
        '</div>';
      modal.addEventListener("click", (e) => {
        if (e.target === modal) {
          closeModal();
          return;
        }
        const btn = e.target && e.target.closest ? e.target.closest("button[data-action]") : null;
        if (!btn) return;
        const action = String(btn.getAttribute("data-action") || "");
        const pending = Array.isArray(modal.__emptyZoneIds) ? modal.__emptyZoneIds : [];
        if (action === "empty-zones-cancel") {
          closeModal();
          return;
        }
        if (action === "empty-zones-go") {
          closeModal();
          const target = String(pending[0] || "");
          if (target) switchToZone(target);
          return;
        }
        if (action === "empty-zones-remove") {
          const selected = getSelectedZoneIds();
          const kept = selected.filter((zid) => pending.indexOf(String(zid || "")) === -1);
          if (!kept.length) {
            setStatus("At least one print area must remain selected.");
            closeModal();
            return;
          }
          closeModal();
          applyZoneSelection(kept);
          window.setTimeout(() => {
            try { proceed(); } catch (e2) {}
          }, 40);
          return;
        }
        if (action === "empty-zones-continue") {
          closeModal();
          proceed();
        }
      });
      root.appendChild(modal);
    }
    const names = empties.map((id) => getZoneLabel(id)).join(", ");
    const help = modal.querySelector('[data-role="empty-zones-help"]');
    if (help) {
      help.textContent = tUi("empty_print_areas_help", "Selected print areas are empty: %s.", [names]);
    }
    modal.__emptyZoneIds = empties.slice();
    modal.classList.add("is-open");
    modal.setAttribute("aria-hidden", "false");
    return true;
  }

  function currentSelectedDesign() {
    const fromInput = designIdInput ? parseInt(designIdInput.value || "0", 10) : 0;
    if (fromInput > 0) {
      const last = (window.DS_LAST_SAVED && typeof window.DS_LAST_SAVED === "object") ? window.DS_LAST_SAVED : null;
      const fromLastTitle = last && parseInt(last.design_post_id || "0", 10) === fromInput
        ? normalizeDesignName(last.design_title || "")
        : "";
      return {
        postId: fromInput,
        png: "",
        title: fromLastTitle,
        uuid: last && parseInt(last.design_post_id || "0", 10) === fromInput ? String(last.id || "") : ""
      };
    }
    const fromLast = (window.DS_LAST_SAVED && typeof window.DS_LAST_SAVED === "object") ? window.DS_LAST_SAVED : null;
    if (fromLast && parseInt(fromLast.design_post_id || "0", 10) > 0) {
      return {
        postId: parseInt(fromLast.design_post_id || "0", 10),
        png: String(fromLast.png || ""),
        title: normalizeDesignName(fromLast.design_title || ""),
        uuid: String(fromLast.id || "")
      };
    }
    return null;
  }

  function renderApplyProducts(items) {
    if (!applyProductListEl) return;
    const arr = Array.isArray(items) ? items : [];
    if (!arr.length) {
      applyProductListEl.innerHTML = '<div class="ds-my-designs-empty">' + escHtml(tUi("no_compatible_products", "No compatible products found for this template.")) + "</div>";
      return;
    }
    applyProductListEl.innerHTML = arr.map((p) => {
      const id = Number(p.id || 0);
      const title = String(p.title || tUi("product_fallback", "Product #%s", [id]));
      const url = String(p.url || "");
      const thumb = String(p.thumb || "");
      const price = String(p.price_html || "");
      return '' +
        '<div class="ds-my-design-item">' +
          (thumb ? '<div class="ds-my-design-thumb"><img src="' + escAttr(thumb) + '" alt="' + escAttr(title) + '" loading="lazy" /></div>' : '') +
          '<div class="ds-my-design-meta"><strong>' + escAttr(title) + '</strong>' +
          (price ? '<span>' + price + '</span>' : '') +
          '</div>' +
          '<div class="ds-my-design-actions">' +
            '<button type="button" data-action="apply-open-product" data-product-url="' + escAttr(url) + '" data-product-id="' + id + '">' + escHtml(tUi("open_product", "Open Product")) + '</button>' +
          '</div>' +
        '</div>';
    }).join("");
  }

  function loadApplyProducts() {
    if (!applyProductListEl || applyProductsLoading) return;
    applyProductsLoading = true;
    applyProductListEl.innerHTML = '<div class="ds-my-designs-empty">' + escHtml(tUi("loading", "Loading...")) + "</div>";
    fetchJSON(cfg.rest.base + "/products/by-template/" + encodeURIComponent(String(cfg.id || 0)), {
      method: "GET",
      headers: { "X-WP-Nonce": cfg.rest.nonce }
    }).then((res) => {
      renderApplyProducts(res && res.items ? res.items : []);
    }).catch((err) => {
      applyProductListEl.innerHTML = '<div class="ds-my-designs-empty">' + escHtml(tUi("error_loading_products", "Error loading products.")) + "</div>";
      setStatus(err && err.message ? err.message : "Product list load error.");
    }).finally(() => {
      applyProductsLoading = false;
    });
  }

  function openApplyProductModal(skipEmptyGuard) {
    if (!skipEmptyGuard && zoneState && zoneState.enabled && zoneState.currentId) {
      captureCurrentZone();
      const empties = getEmptySelectedZoneIds();
      if (empties.length) {
        openEmptyZonesGuardModal({
          emptyZoneIds: empties,
          onContinue: () => openApplyProductModal(true)
        });
        return;
      }
    }
    const selected = currentSelectedDesign();
    if (!selected || !selected.postId) {
      openSaveBeforeApplyModal();
      return;
    }
    if (!applyProductModalEl) return;
    applyProductModalEl.classList.add("is-open");
    applyProductModalEl.setAttribute("aria-hidden", "false");
    loadApplyProducts();
  }

  function svgDataUri(svg) {
    return "data:image/svg+xml;charset=UTF-8," + encodeURIComponent(String(svg || ""));
  }

  function getDefaultTextureLibrary() {
    return [
      { id: "tx-grid", label: "Grid", url: svgDataUri('<svg xmlns="http://www.w3.org/2000/svg" width="600" height="600"><rect width="100%" height="100%" fill="#f8fafc"/><g stroke="#d9e3ee" stroke-width="1"><path d="M0 50h600M0 100h600M0 150h600M0 200h600M0 250h600M0 300h600M0 350h600M0 400h600M0 450h600M0 500h600M0 550h600"/><path d="M50 0v600M100 0v600M150 0v600M200 0v600M250 0v600M300 0v600M350 0v600M400 0v600M450 0v600M500 0v600M550 0v600"/></g></svg>') },
      { id: "tx-dot", label: "Dots", url: svgDataUri('<svg xmlns="http://www.w3.org/2000/svg" width="600" height="600"><rect width="100%" height="100%" fill="#fffdf7"/><defs><pattern id="p" width="28" height="28" patternUnits="userSpaceOnUse"><circle cx="6" cy="6" r="3" fill="#f59e0b"/></pattern></defs><rect width="100%" height="100%" fill="url(#p)"/></svg>') },
      { id: "tx-diag", label: "Diagonal", url: svgDataUri('<svg xmlns="http://www.w3.org/2000/svg" width="600" height="600"><rect width="100%" height="100%" fill="#f8fbff"/><defs><pattern id="p" width="22" height="22" patternUnits="userSpaceOnUse" patternTransform="rotate(45)"><rect width="10" height="22" fill="#d5e4f3"/></pattern></defs><rect width="100%" height="100%" fill="url(#p)"/></svg>') },
      { id: "tx-hex", label: "Hex", url: svgDataUri('<svg xmlns="http://www.w3.org/2000/svg" width="600" height="600"><rect width="100%" height="100%" fill="#f7fafc"/><defs><pattern id="h" width="42" height="36.4" patternUnits="userSpaceOnUse"><path d="M10.5 1l21 0 10.5 17.2-10.5 17.2-21 0-10.5-17.2z" fill="none" stroke="#c9d8e8" stroke-width="1.2"/></pattern></defs><rect width="100%" height="100%" fill="url(#h)"/></svg>') },
      { id: "tx-wave", label: "Waves", url: svgDataUri('<svg xmlns="http://www.w3.org/2000/svg" width="600" height="600"><rect width="100%" height="100%" fill="#f8fbff"/><g fill="none" stroke="#b7d2ea" stroke-width="2"><path d="M0 60q30-24 60 0t60 0t60 0t60 0t60 0t60 0t60 0t60 0t60 0"/><path d="M0 140q30-24 60 0t60 0t60 0t60 0t60 0t60 0t60 0t60 0t60 0"/><path d="M0 220q30-24 60 0t60 0t60 0t60 0t60 0t60 0t60 0t60 0t60 0"/><path d="M0 300q30-24 60 0t60 0t60 0t60 0t60 0t60 0t60 0t60 0t60 0"/><path d="M0 380q30-24 60 0t60 0t60 0t60 0t60 0t60 0t60 0t60 0t60 0"/><path d="M0 460q30-24 60 0t60 0t60 0t60 0t60 0t60 0t60 0t60 0t60 0"/></g></svg>') },
      { id: "tx-paper", label: "Paper", url: svgDataUri('<svg xmlns="http://www.w3.org/2000/svg" width="600" height="600"><filter id="n"><feTurbulence type="fractalNoise" baseFrequency=".95" numOctaves="2" stitchTiles="stitch"/><feColorMatrix type="saturate" values="0"/></filter><rect width="100%" height="100%" fill="#fffef9"/><rect width="100%" height="100%" opacity=".08" filter="url(#n)"/></svg>') },
      { id: "tx-carbon", label: "Carbon", url: svgDataUri('<svg xmlns="http://www.w3.org/2000/svg" width="600" height="600"><rect width="100%" height="100%" fill="#1f2937"/><defs><pattern id="c" width="16" height="16" patternUnits="userSpaceOnUse"><rect width="8" height="8" fill="#263446"/><rect x="8" y="8" width="8" height="8" fill="#263446"/><rect x="8" width="8" height="8" fill="#1f2937"/><rect y="8" width="8" height="8" fill="#1f2937"/></pattern></defs><rect width="100%" height="100%" fill="url(#c)" opacity=".9"/></svg>') },
      { id: "tx-blueprint", label: "Blueprint", url: svgDataUri('<svg xmlns="http://www.w3.org/2000/svg" width="600" height="600"><rect width="100%" height="100%" fill="#0f4c81"/><g stroke="#d3e8ff" stroke-width="1" opacity=".5"><path d="M0 40h600M0 80h600M0 120h600M0 160h600M0 200h600M0 240h600M0 280h600M0 320h600M0 360h600M0 400h600M0 440h600M0 480h600M0 520h600M0 560h600"/><path d="M40 0v600M80 0v600M120 0v600M160 0v600M200 0v600M240 0v600M280 0v600M320 0v600M360 0v600M400 0v600M440 0v600M480 0v600M520 0v600M560 0v600"/></g></svg>') },
      { id: "tx-zig", label: "Zigzag", url: svgDataUri('<svg xmlns="http://www.w3.org/2000/svg" width="600" height="600"><rect width="100%" height="100%" fill="#fff"/><defs><pattern id="z" width="30" height="16" patternUnits="userSpaceOnUse"><path d="M0 8l8-8 8 8 8-8 8 8" fill="none" stroke="#ef4444" stroke-width="2"/></pattern></defs><rect width="100%" height="100%" fill="url(#z)"/></svg>') },
      { id: "tx-check", label: "Checkers", url: svgDataUri('<svg xmlns="http://www.w3.org/2000/svg" width="600" height="600"><defs><pattern id="q" width="40" height="40" patternUnits="userSpaceOnUse"><rect width="20" height="20" fill="#e9edf3"/><rect x="20" y="20" width="20" height="20" fill="#e9edf3"/><rect x="20" width="20" height="20" fill="#f8fbff"/><rect y="20" width="20" height="20" fill="#f8fbff"/></pattern></defs><rect width="100%" height="100%" fill="url(#q)"/></svg>') }
    ];
  }

  function getDefaultFrameLibrary() {
    return [
      { id: "fr-line", label: "Simple Line", url: svgDataUri('<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1200" viewBox="0 0 1200 1200"><rect x="32" y="32" width="1136" height="1136" fill="none" stroke="#0f172a" stroke-width="26"/></svg>') },
      { id: "fr-double", label: "Double Line", url: svgDataUri('<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1200" viewBox="0 0 1200 1200"><rect x="20" y="20" width="1160" height="1160" fill="none" stroke="#1e293b" stroke-width="14"/><rect x="64" y="64" width="1072" height="1072" fill="none" stroke="#1e293b" stroke-width="8"/></svg>') },
      { id: "fr-round", label: "Rounded", url: svgDataUri('<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1200" viewBox="0 0 1200 1200"><rect x="36" y="36" rx="100" ry="100" width="1128" height="1128" fill="none" stroke="#1d4ed8" stroke-width="24"/></svg>') },
      { id: "fr-dash", label: "Dashed", url: svgDataUri('<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1200" viewBox="0 0 1200 1200"><rect x="30" y="30" width="1140" height="1140" fill="none" stroke="#334155" stroke-width="20" stroke-dasharray="30 18"/></svg>') },
      { id: "fr-gold", label: "Gold", url: svgDataUri('<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1200" viewBox="0 0 1200 1200"><defs><linearGradient id="g" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#fbbf24"/><stop offset="45%" stop-color="#fde68a"/><stop offset="100%" stop-color="#b45309"/></linearGradient></defs><rect x="22" y="22" width="1156" height="1156" fill="none" stroke="url(#g)" stroke-width="28"/></svg>') },
      { id: "fr-corner", label: "Corner", url: svgDataUri('<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1200" viewBox="0 0 1200 1200"><g fill="none" stroke="#7c3aed" stroke-width="24"><path d="M120 300V120h180"/><path d="M900 120h180v180"/><path d="M1080 900v180H900"/><path d="M300 1080H120V900"/></g></svg>') },
      { id: "fr-vintage", label: "Vintage", url: svgDataUri('<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1200" viewBox="0 0 1200 1200"><rect x="40" y="40" width="1120" height="1120" fill="none" stroke="#7c2d12" stroke-width="16"/><rect x="66" y="66" width="1068" height="1068" fill="none" stroke="#7c2d12" stroke-width="6" stroke-dasharray="6 12"/></svg>') },
      { id: "fr-comic", label: "Comic", url: svgDataUri('<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1200" viewBox="0 0 1200 1200"><rect x="34" y="34" width="1132" height="1132" fill="none" stroke="#111827" stroke-width="26"/><rect x="66" y="66" width="1068" height="1068" fill="none" stroke="#111827" stroke-width="6"/></svg>') },
      { id: "fr-neon", label: "Neon", url: svgDataUri('<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1200" viewBox="0 0 1200 1200"><rect x="30" y="30" width="1140" height="1140" fill="none" stroke="#06b6d4" stroke-width="10"/><rect x="46" y="46" width="1108" height="1108" fill="none" stroke="#22d3ee" stroke-width="4"/></svg>') },
      { id: "fr-minimal", label: "Minimal", url: svgDataUri('<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1200" viewBox="0 0 1200 1200"><rect x="80" y="80" width="1040" height="1040" fill="none" stroke="#475569" stroke-width="6"/></svg>') }
    ];
  }

  function getDefaultShapeLibrary() {
    return [
      { id: "shape-rect", label: "Rectangle", shape: "rect", url: svgDataUri('<svg xmlns="http://www.w3.org/2000/svg" width="420" height="300" viewBox="0 0 420 300"><rect x="36" y="36" width="348" height="228" rx="8" fill="none" stroke="#0f172a" stroke-width="20"/></svg>') },
      { id: "shape-circle", label: "Circle", shape: "circle", url: svgDataUri('<svg xmlns="http://www.w3.org/2000/svg" width="420" height="300" viewBox="0 0 420 300"><circle cx="210" cy="150" r="110" fill="none" stroke="#0f172a" stroke-width="20"/></svg>') },
      { id: "shape-triangle", label: "Triangle", shape: "triangle", url: svgDataUri('<svg xmlns="http://www.w3.org/2000/svg" width="420" height="300" viewBox="0 0 420 300"><path d="M210 34 388 264H32z" fill="none" stroke="#0f172a" stroke-width="20" stroke-linejoin="round"/></svg>') },
      { id: "shape-star", label: "Star", shape: "star", url: svgDataUri('<svg xmlns="http://www.w3.org/2000/svg" width="420" height="300" viewBox="0 0 420 300"><path d="m210 26 44 88 98 14-71 69 17 97-88-46-88 46 17-97-71-69 98-14z" fill="none" stroke="#0f172a" stroke-width="18" stroke-linejoin="round"/></svg>') },
      { id: "shape-heart", label: "Heart", shape: "heart", url: svgDataUri('<svg xmlns="http://www.w3.org/2000/svg" width="420" height="300" viewBox="0 0 420 300"><path d="M210 262c-8 0-16-3-22-9L82 147c-42-42-42-110 0-152 40-40 106-40 146 0l-18 18 18-18c40-40 106-40 146 0 42 42 42 110 0 152L232 253c-6 6-14 9-22 9z" fill="none" stroke="#0f172a" stroke-width="16" stroke-linejoin="round"/></svg>') },
      { id: "shape-polygon", label: "Polygon", shape: "polygon", url: svgDataUri('<svg xmlns="http://www.w3.org/2000/svg" width="420" height="300" viewBox="0 0 420 300"><path d="m210 30 114 66v108l-114 66-114-66V96z" fill="none" stroke="#0f172a" stroke-width="20" stroke-linejoin="round"/></svg>') }
    ];
  }

  function getDefaultEmoticonLibrary() {
    return [
      { id: "emoji-smile", label: "Smile", emoji: "\uD83D\uDE42" },
      { id: "emoji-heart", label: "Heart", emoji: "\u2764\uFE0F" },
      { id: "emoji-fire", label: "Fire", emoji: "\uD83D\uDD25" },
      { id: "emoji-star", label: "Star", emoji: "\u2B50" },
      { id: "emoji-party", label: "Party", emoji: "\uD83C\uDF89" },
      { id: "emoji-check", label: "Check", emoji: "\u2705" },
      { id: "emoji-thumbs-up", label: "Thumbs Up", emoji: "\uD83D\uDC4D" },
      { id: "emoji-rocket", label: "Rocket", emoji: "\uD83D\uDE80" },
      { id: "emoji-camera", label: "Camera", emoji: "\uD83D\uDCF7" },
      { id: "emoji-sparkles", label: "Sparkles", emoji: "\u2728" },
      { id: "emoji-rainbow", label: "Rainbow", emoji: "\uD83C\uDF08" },
      { id: "emoji-coffee", label: "Coffee", emoji: "\u2615" },
      { id: "emoji-cool", label: "Cool", emoji: "\uD83D\uDE0E" },
      { id: "emoji-lightbulb", label: "Idea", emoji: "\uD83D\uDCA1" },
      { id: "emoji-sun", label: "Sun", emoji: "\u2600\uFE0F" },
      { id: "emoji-moon", label: "Moon", emoji: "\uD83C\uDF19" },
      { id: "emoji-flower", label: "Flower", emoji: "\uD83C\uDF38" },
      { id: "emoji-leaf", label: "Leaf", emoji: "\uD83C\uDF43" },
      { id: "emoji-music", label: "Music", emoji: "\uD83C\uDFB5" },
      { id: "emoji-headphones", label: "Headphones", emoji: "\uD83C\uDFA7" },
      { id: "emoji-lightning", label: "Lightning", emoji: "\u26A1" },
      { id: "emoji-target", label: "Target", emoji: "\uD83C\uDFAF" },
      { id: "emoji-trophy", label: "Trophy", emoji: "\uD83C\uDFC6" },
      { id: "emoji-clap", label: "Clap", emoji: "\uD83D\uDC4F" },
      { id: "emoji-pray", label: "Pray", emoji: "\uD83D\uDE4F" },
      { id: "emoji-laugh", label: "Laugh", emoji: "\uD83D\uDE02" },
      { id: "emoji-wink", label: "Wink", emoji: "\uD83D\uDE09" },
      { id: "emoji-thinking", label: "Thinking", emoji: "\uD83E\uDD14" },
      { id: "emoji-ok", label: "OK", emoji: "\uD83D\uDC4C" },
      { id: "emoji-100", label: "100", emoji: "\uD83D\uDCAF" },
      { id: "emoji-balloon", label: "Balloon", emoji: "\uD83C\uDF88" },
      { id: "emoji-palette", label: "Palette", emoji: "\uD83C\uDFA8" },
      { id: "emoji-pen", label: "Pen", emoji: "\u270D\uFE0F" },
      { id: "emoji-scissors", label: "Scissors", emoji: "\u2702\uFE0F" },
      { id: "emoji-magnet", label: "Magnet", emoji: "\uD83E\uDDF2" },
      { id: "emoji-diamond", label: "Diamond", emoji: "\uD83D\uDC8E" },
      { id: "emoji-crown", label: "Crown", emoji: "\uD83D\uDC51" },
      { id: "emoji-wave", label: "Wave", emoji: "\uD83C\uDF0A" },
      { id: "emoji-globe", label: "Globe", emoji: "\uD83C\uDF10" }
    ];
  }

  function getDefaultIconLibrary() {
    return [
      { id: "icon-heart", label: "Heart", icon: "heart", glyph: "\u2665" },
      { id: "icon-star", label: "Star", icon: "star", glyph: "\u2605" },
      { id: "icon-bolt", label: "Bolt", icon: "bolt", glyph: "\u26A1" },
      { id: "icon-check", label: "Check", icon: "check", glyph: "\u2713" },
      { id: "icon-arrow", label: "Arrow", icon: "arrow", glyph: "\u279C" },
      { id: "icon-pin", label: "Pin", icon: "pin", glyph: "\u2316" },
      { id: "icon-cart", label: "Cart", icon: "cart", glyph: "\uD83D\uDED2" },
      { id: "icon-user", label: "User", icon: "user", glyph: "\uD83D\uDC64" },
      { id: "icon-home", label: "Home", icon: "home", glyph: "\u2302" },
      { id: "icon-search", label: "Search", icon: "search", glyph: "\u2315" },
      { id: "icon-mail", label: "Mail", icon: "mail", glyph: "\u2709" },
      { id: "icon-gift", label: "Gift", icon: "gift", glyph: "\uD83C\uDF81" },
      { id: "icon-plus", label: "Plus", icon: "plus", glyph: "\uFF0B" },
      { id: "icon-minus", label: "Minus", icon: "minus", glyph: "\u2212" },
      { id: "icon-close", label: "Close", icon: "close", glyph: "\u2715" },
      { id: "icon-clock", label: "Clock", icon: "clock", glyph: "\uD83D\uDD52" },
      { id: "icon-camera", label: "Camera", icon: "camera", glyph: "\uD83D\uDCF7" },
      { id: "icon-phone", label: "Phone", icon: "phone", glyph: "\u260E" },
      { id: "icon-globe", label: "Globe", icon: "globe", glyph: "\uD83C\uDF10" },
      { id: "icon-tag", label: "Tag", icon: "tag", glyph: "\uD83C\uDFF7" },
      { id: "icon-lock", label: "Lock", icon: "lock", glyph: "\uD83D\uDD12" },
      { id: "icon-unlock", label: "Unlock", icon: "unlock", glyph: "\uD83D\uDD13" },
      { id: "icon-flag", label: "Flag", icon: "flag", glyph: "\u2691" },
      { id: "icon-location", label: "Location", icon: "location", glyph: "\uD83D\uDCCD" },
      { id: "icon-bell", label: "Bell", icon: "bell", glyph: "\uD83D\uDD14" },
      { id: "icon-settings", label: "Settings", icon: "settings", glyph: "\u2699" },
      { id: "icon-cloud", label: "Cloud", icon: "cloud", glyph: "\u2601" },
      { id: "icon-download", label: "Download", icon: "download", glyph: "\u2B07" },
      { id: "icon-upload", label: "Upload", icon: "upload", glyph: "\u2B06" },
      { id: "icon-play", label: "Play", icon: "play", glyph: "\u25B6" },
      { id: "icon-pause", label: "Pause", icon: "pause", glyph: "\u275A\u275A" },
      { id: "icon-refresh", label: "Refresh", icon: "refresh", glyph: "\u21BB" },
      { id: "icon-link", label: "Link", icon: "link", glyph: "\uD83D\uDD17" },
      { id: "icon-shield", label: "Shield", icon: "shield", glyph: "\uD83D\uDEE1" },
      { id: "icon-bookmark", label: "Bookmark", icon: "bookmark", glyph: "\uD83D\uDD16" },
      { id: "icon-chat", label: "Chat", icon: "chat", glyph: "\uD83D\uDCAC" },
      { id: "icon-calendar", label: "Calendar", icon: "calendar", glyph: "\uD83D\uDCC5" },
      { id: "icon-lightbulb", label: "Lightbulb", icon: "lightbulb", glyph: "\uD83D\uDCA1" },
      { id: "icon-print", label: "Print", icon: "print", glyph: "\uD83D\uDDA8" },
      { id: "icon-box", label: "Box", icon: "box", glyph: "\uD83D\uDCE6" }
    ];
  }

  function getCustomAssetGroups(kind) {
    const useKind = String(kind || "").toLowerCase() === "icons" ? "icons" : "emoticons";
    const key = useKind === "icons" ? "custom_icon_groups" : "custom_emoticon_groups";
    const raw = (libraryConfig && Array.isArray(libraryConfig[key])) ? libraryConfig[key] : [];
    if (!raw.length) return [];
    const groups = [];
    raw.forEach((group, groupIdx) => {
      if (!group || typeof group !== "object") return;
      const name = String(group.name || group.label || group.group || "").trim();
      if (!name) return;
      const rawItems = Array.isArray(group.items) ? group.items : [];
      const items = [];
      rawItems.forEach((item, itemIdx) => {
        if (!item || typeof item !== "object") return;
        if (useKind === "icons") {
          const icon = String(item.icon || item.name || "").trim().toLowerCase();
          if (!icon) return;
          const label = String(item.label || icon).trim() || icon;
          const glyph = String(item.glyph || "").trim();
          const id = String(item.id || ("icon-" + icon + "-" + itemIdx)).trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") || ("icon-" + groupIdx + "-" + itemIdx);
          items.push({ id: id, label: label, icon: icon, glyph: glyph });
        } else {
          const emoji = String(item.emoji || item.glyph || "").trim();
          if (!emoji) return;
          const label = String(item.label || emoji).trim() || emoji;
          const id = String(item.id || ("emoji-" + itemIdx)).trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") || ("emoji-" + groupIdx + "-" + itemIdx);
          items.push({ id: id, label: label, emoji: emoji });
        }
      });
      if (!items.length) return;
      const gid = String(group.id || ("group-" + groupIdx)).trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") || ("group-" + groupIdx);
      groups.push({ id: gid, name: name, items: items });
    });
    return groups;
  }

  function buildGroupedGlyphAssetLibrary(kind) {
    const useKind = String(kind || "").toLowerCase() === "icons" ? "icons" : "emoticons";
    const defaultItems = useKind === "icons" ? getDefaultIconLibrary() : getDefaultEmoticonLibrary();
    const customGroups = getCustomAssetGroups(useKind);
    const defaultGroup = { id: "default", name: tUi("default_group", "Default"), items: defaultItems };
    return [defaultGroup].concat(customGroups);
  }

  function renderGroupedGlyphAssetLibrary(kind) {
    const useKind = String(kind || "").toLowerCase() === "icons" ? "icons" : "emoticons";
    const groups = buildGroupedGlyphAssetLibrary(useKind);
    if (!groups.length) return '<div class="ds-my-designs-empty">' + escHtml(tUi("no_assets_available", "No assets available.")) + "</div>";
    const showHeaders = groups.length > 1;
    let html = "";
    groups.forEach((group) => {
      const groupTitle = String(group.name || tUi("group_label", "Group")).trim() || tUi("group_label", "Group");
      if (showHeaders) {
        html += '<div class="ds-asset-group-title" role="heading" aria-level="5">' + escHtml(groupTitle) + "</div>";
      }
      html += (Array.isArray(group.items) ? group.items : []).map((item) => {
        const label = String(item.label || (useKind === "icons" ? tUi("icon_label", "Icon") : tUi("emoticon_label", "Emoticon")));
        const actionId = String(item.id || "").trim().toLowerCase();
        if (useKind === "icons") {
          const icon = String(item.icon || "").trim().toLowerCase();
          if (!icon) return "";
          const glyph = String(item.glyph || "").trim() || "*";
          return '' +
            '<button type="button" class="ds-asset-card ds-asset-card-btn ds-asset-card-glyph" data-action="asset-icon" data-mobile-sheet-action="' + escAttr(actionId) + '" data-icon="' + escAttr(icon) + '" aria-label="' + escAttr(label) + '" title="' + escAttr(label) + '">' +
              '<span class="ds-asset-thumb ds-asset-thumb-glyph"><span class="ds-asset-glyph" aria-hidden="true">' + escHtml(glyph) + '</span></span>' +
              '<span class="ds-asset-meta">' + escAttr(label) + "</span>" +
            "</button>";
        }
        const emoji = String(item.emoji || "").trim();
        if (!emoji) return "";
        return '' +
          '<button type="button" class="ds-asset-card ds-asset-card-btn ds-asset-card-glyph" data-action="asset-emoji" data-mobile-sheet-action="' + escAttr(actionId) + '" data-emoji="' + escAttr(emoji) + '" aria-label="' + escAttr(label) + '" title="' + escAttr(label) + '">' +
            '<span class="ds-asset-thumb ds-asset-thumb-glyph"><span class="ds-asset-glyph" aria-hidden="true">' + escHtml(emoji) + '</span></span>' +
            '<span class="ds-asset-meta">' + escAttr(label) + "</span>" +
          "</button>";
      }).join("");
    });
    return html || ('<div class="ds-my-designs-empty">' + escHtml(tUi("no_assets_available", "No assets available.")) + "</div>");
  }

  function readCustomAssetUrls(key) {
    const raw = (libraryConfig && Array.isArray(libraryConfig[key])) ? libraryConfig[key] : [];
    return raw.map((u) => String(u || "").trim()).filter((u) => /^https?:\/\//i.test(u));
  }

  function buildAssetLibrary(kind) {
    const base = kind === "frames" ? getDefaultFrameLibrary() : getDefaultTextureLibrary();
    const custom = readCustomAssetUrls(kind).map((url, idx) => ({
      id: "custom-" + kind + "-" + idx,
      label: "Custom " + (idx + 1),
      url: url
    }));
    return base.concat(custom);
  }

  function closeAssetLibraryModal() {
    if (!assetLibraryModalEl) return;
    assetLibraryModalEl.classList.remove("is-open");
    assetLibraryModalEl.setAttribute("aria-hidden", "true");
  }

  function loadImageFromLibrary(url, cb) {
    const src = String(url || "").trim();
    if (!src) { cb(null); return; }
    const opts = /^data:/i.test(src) ? {} : { crossOrigin: "anonymous" };
    fabric.Image.fromURL(src, (img) => cb(img || null), opts);
  }

  function applyTextureAsBackground(url) {
    loadImageFromLibrary(url, (img) => {
      if (!img) { setStatus("Could not load texture."); return; }
      if (state.textureBgObj && canvas.getObjects().indexOf(state.textureBgObj) !== -1) {
        canvas.remove(state.textureBgObj);
      }
      const sx = widthPx / Math.max(1, img.width || 1);
      const sy = heightPx / Math.max(1, img.height || 1);
      const s = Math.max(sx, sy);
      img.set({
        left: widthPx / 2,
        top: heightPx / 2,
        originX: "center",
        originY: "center",
        selectable: false,
        evented: false,
        hasControls: false,
        lockMovementX: true,
        lockMovementY: true,
        lockScalingX: true,
        lockScalingY: true,
        lockRotation: true
      });
      img.scale(s);
      ensureUid(img);
      canvas.add(img);
      canvas.sendToBack(img);
      state.textureBgObj = img;
      canvas.requestRenderAll();
      pushHistory();
      setStatus("Texture set as canvas background.");
    });
  }

  function insertLibraryAsset(url, kind) {
    loadImageFromLibrary(url, (img) => {
      if (!img) { setStatus("Could not load asset."); return; }
      const scaleX = widthPx / Math.max(1, img.width || 1);
      const scaleY = heightPx / Math.max(1, img.height || 1);
      let scale = Math.min(scaleX, scaleY) * (kind === "frames" ? 0.95 : 0.62);
      if (!isFinite(scale) || scale <= 0) scale = 1;
      img.set({
        left: widthPx / 2,
        top: heightPx / 2,
        originX: "center",
        originY: "center"
      });
      img.scale(scale);
      tuneObjectForTouch(img);
      ensureUid(img);
      canvas.add(img).setActiveObject(img);
      canvas.requestRenderAll();
      pushHistory();
      setStatus(kind === "frames"
        ? tUi("frame_inserted", "Frame inserted.")
        : tUi("texture_inserted", "Texture inserted."));
    });
  }

  function openAssetLibraryModal(kind) {
    if (!assetLibraryModalEl || !assetLibraryListEl) return;
    const requestedKind = String(kind || "").toLowerCase();
    const useKind = (
      requestedKind === "frames" ||
      requestedKind === "textures" ||
      requestedKind === "shapes" ||
      requestedKind === "emoticons" ||
      requestedKind === "icons"
    ) ? requestedKind : "textures";
    const titleMap = {
      textures: tUi("textures_library", "Textures Library"),
      frames: tUi("frames_library", "Frames Library"),
      shapes: tUi("shapes_library", "Shapes Library"),
      emoticons: tUi("emoticons_library", "Emoticons Library"),
      icons: tUi("icons_library", "Icons Library")
    };
    const title = titleMap[useKind] || tUi("asset_library", "Asset Library");
    if (assetLibraryTitleEl) assetLibraryTitleEl.textContent = title;
    assetLibraryListEl.setAttribute("data-asset-kind", useKind);
    const items = (useKind === "textures" || useKind === "frames")
      ? buildAssetLibrary(useKind)
      : (useKind === "shapes"
        ? getDefaultShapeLibrary()
        : (useKind === "emoticons" ? getDefaultEmoticonLibrary() : getDefaultIconLibrary()));
    if (!items.length) {
      assetLibraryListEl.innerHTML = '<div class="ds-my-designs-empty">' + escHtml(tUi("no_assets_available", "No assets available.")) + "</div>";
    } else {
      if (useKind === "textures" || useKind === "frames") {
        assetLibraryListEl.innerHTML = items.map((item, idx) => {
          const id = String(item.id || ("asset-" + idx));
          const label = String(item.label || "Asset");
          const src = String(item.url || "");
          const insertCard = '' +
            '<button type="button" class="ds-asset-card ds-asset-card-btn ds-asset-card-glyph ds-asset-card-media" data-action="asset-insert" data-id="' + escAttr(id) + '" data-kind="' + useKind + '" data-src="' + escAttr(src) + '" aria-label="' + escAttr(label) + '" title="' + escAttr(label) + '">' +
              '<span class="ds-asset-thumb"><img src="' + escAttr(src) + '" alt="' + escAttr(label) + '" loading="lazy" /></span>' +
              '<span class="ds-asset-meta">' + escAttr(label) + '</span>' +
            '</button>';
          const bgBtn = (useKind === "textures")
            ? ('<button type="button" class="ds-asset-media-bg-btn" data-action="asset-bg" data-kind="' + useKind + '" data-src="' + escAttr(src) + '" title="' + escAttr(tUi("set_as_background", "Set as background")) + '" aria-label="' + escAttr(tUi("set_as_background", "Set as background")) + '">BG</button>')
            : "";
          return '' +
            '<div class="ds-asset-card-wrap ds-asset-card-wrap-media' + (useKind === "textures" ? ' is-texture' : '') + '">' +
              insertCard +
              bgBtn +
            '</div>';
        }).join("");
      } else if (useKind === "shapes") {
        assetLibraryListEl.innerHTML = items.map((item) => {
          const label = String(item.label || "Shape");
          const src = String(item.url || "");
          const shape = String(item.shape || "").trim().toLowerCase();
          return '' +
            '<button type="button" class="ds-asset-card ds-asset-card-btn ds-asset-card-glyph ds-asset-card-media ds-asset-card-shape" data-action="asset-shape" data-shape="' + escAttr(shape) + '" aria-label="' + escAttr(label) + '" title="' + escAttr(label) + '">' +
              '<span class="ds-asset-thumb"><img src="' + escAttr(src) + '" alt="' + escAttr(label) + '" loading="lazy" /></span>' +
              '<span class="ds-asset-meta">' + escAttr(label) + '</span>' +
            '</button>';
        }).join("");
      } else if (useKind === "emoticons") {
        assetLibraryListEl.innerHTML = renderGroupedGlyphAssetLibrary("emoticons");
      } else {
        assetLibraryListEl.innerHTML = renderGroupedGlyphAssetLibrary("icons");
      }
    }
    assetLibraryModalEl.classList.add("is-open");
    assetLibraryModalEl.setAttribute("aria-hidden", "false");
  }

  function setDesignVisibility(designId, nextVisibility) {
    const id = parseInt(designId || "0", 10);
    if (!id) return;
    fetchJSON(cfg.rest.base + "/design/" + encodeURIComponent(id) + "/visibility", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "X-WP-Nonce": cfg.rest.nonce
      },
      body: JSON.stringify({ visibility: nextVisibility })
    }).then(() => {
      setStatus(tUi(
        "design_visibility_now",
        "Design #%s is now %s.",
        [
          id,
          nextVisibility === "shared"
            ? tUi("visibility_shared", "Shared")
            : tUi("visibility_private", "Private")
        ]
      ));
      if (myDesignsMode === "start-load") loadDesignLibraryForStart();
      else loadMyDesigns();
    }).catch((err) => {
      setStatus(err && err.message ? err.message : "Visibility update failed.");
    });
  }

  function getPreviewTitle() {
    if (previewType === "tshirt") return tUi("tshirt_preview", "T-Shirt Preview");
    if (previewType === "bottle") return tUi("bottle_preview", "Bottle Preview");
    if (previewType === "mug") return tUi("mug_preview", "Mug Preview");
    return tUi("preview_3d", "3D Preview");
  }

  function createBasePreviewScene(targetEl, options) {
    if (!targetEl || typeof THREE === "undefined") return null;
    const opts = options || {};
    let renderer;
    try {
      renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
    } catch (e) {
      targetEl.style.display = "none";
      return null;
    }
    renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
    try {
      renderer.domElement.style.width = "100%";
      renderer.domElement.style.height = "100%";
      renderer.domElement.style.display = "block";
      renderer.domElement.style.touchAction = "none";
    } catch (e) {}
    targetEl.appendChild(renderer.domElement);

    const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera(opts.fov || 34, 1, 0.1, 100);
    const camPos = opts.cameraPosition || [0, -0.55, 3.85];
    const camLook = opts.cameraLookAt || [0, 0.25, 0];
    const lookAtPoint = new THREE.Vector3(camLook[0], camLook[1], camLook[2]);
    camera.position.set(camPos[0], camPos[1], camPos[2]);
    camera.lookAt(lookAtPoint);
    let camDistance = camera.position.distanceTo(lookAtPoint);
    const minDistance = Number(opts.minDistance || 1.65);
    const maxDistance = Number(opts.maxDistance || 8.5);
    function applyCameraDistance() {
      const dir = new THREE.Vector3().subVectors(camera.position, lookAtPoint).normalize();
      camera.position.copy(lookAtPoint).addScaledVector(dir, camDistance);
      camera.lookAt(lookAtPoint);
    }

    const hemi = new THREE.HemisphereLight(0xffffff, 0xc4ccd8, 1.05);
    scene.add(hemi);
    const key = new THREE.DirectionalLight(0xffffff, 1.1);
    key.position.set(2.3, 3.5, 2.1);
    scene.add(key);
    const rim = new THREE.DirectionalLight(0xbfd6ff, 0.7);
    rim.position.set(-2.1, 1.2, -2.2);
    scene.add(rim);

    const model = new THREE.Group();
    model.scale.setScalar(opts.scale || 0.8);
    scene.add(model);

    const view = {
      x: opts.viewPitch == null ? -0.16 : Number(opts.viewPitch),
      y: opts.viewYaw == null ? 0.68 : Number(opts.viewYaw),
      auto: opts.autoRotate == null ? 0.004 : opts.autoRotate
    };
    let dragging = false;
    let lastX = 0;
    let lastY = 0;

    function clampPitch(v) { return Math.max(-0.42, Math.min(0.42, v)); }
    function applyView() {
      model.rotation.y = view.y;
      model.rotation.x = clampPitch(view.x);
    }
    function resetView() {
      view.x = opts.resetPitch == null ? -0.2 : Number(opts.resetPitch);
      view.y = opts.resetYaw == null ? 0.72 : Number(opts.resetYaw);
      applyView();
    }
    resetView();

    if (opts.interactive) {
      let dragYawMul = 1;
      let dragPitchMul = 1;
      const coarsePointer = !!(window.matchMedia && window.matchMedia("(pointer: coarse)").matches);
      renderer.domElement.addEventListener("pointerdown", (e) => {
        dragging = true;
        lastX = e.clientX;
        lastY = e.clientY;
        const isTouchLike = (e && (e.pointerType === "touch" || e.pointerType === "pen")) || coarsePointer;
        dragYawMul = isTouchLike ? 3.6 : 1;
        dragPitchMul = isTouchLike ? 2.9 : 1;
        if (isTouchLike && e && e.cancelable) e.preventDefault();
        try { renderer.domElement.setPointerCapture(e.pointerId); } catch (err) {}
      });
      renderer.domElement.addEventListener("pointermove", (e) => {
        if (!dragging) return;
        if (e && e.cancelable) e.preventDefault();
        const dx = e.clientX - lastX;
        const dy = e.clientY - lastY;
        lastX = e.clientX;
        lastY = e.clientY;
        view.y += dx * 0.008 * dragYawMul;
        view.x = clampPitch(view.x + dy * 0.005 * dragPitchMul);
        applyView();
      });
      const endDrag = () => { dragging = false; dragYawMul = 1; dragPitchMul = 1; };
      renderer.domElement.addEventListener("pointerup", endDrag);
      renderer.domElement.addEventListener("pointerleave", endDrag);
      renderer.domElement.addEventListener("pointercancel", endDrag);
      renderer.domElement.addEventListener("wheel", (e) => {
        e.preventDefault();
        const factor = e.deltaY > 0 ? 1.08 : 0.92;
        camDistance = Math.max(minDistance, Math.min(maxDistance, camDistance * factor));
        applyCameraDistance();
      }, { passive: false });
    }

    function resize() {
      const w = Math.max(1, targetEl.clientWidth || 1);
      const h = Math.max(1, targetEl.clientHeight || 1);
      renderer.setSize(w, h, false);
      camera.aspect = w / h;
      camera.updateProjectionMatrix();
      renderer.render(scene, camera);
    }

    let raf = null;
    function tick() {
      if (!dragging && view.auto) {
        view.y += view.auto;
        applyView();
      }
      renderer.render(scene, camera);
      raf = window.requestAnimationFrame(tick);
    }
    resize();
    tick();

    return {
      scene: scene,
      camera: camera,
      model: model,
      renderer: renderer,
      resize: resize,
      reset: resetView,
      renderNow: () => renderer.render(scene, camera),
      getView: () => ({ x: view.x, y: view.y }),
      setView: (v) => {
        if (!v) return;
        view.x = clampPitch(Number(v.x) || view.x);
        view.y = Number(v.y) || view.y;
        applyView();
      },
      setAutoRotate: (speed) => {
        const next = Number(speed);
        view.auto = Number.isFinite(next) ? next : 0;
      },
      stop: () => { if (raf) window.cancelAnimationFrame(raf); }
    };
  }

  function ensureLargePreview() {
    if (!enablePreview3D) return null;
    if (largePreview || !mugLargeCanvasEl) return largePreview;
    try {
      largePreview = activePreviewFactory(mugLargeCanvasEl, {
        interactive: true,
        autoRotate: previewType === "tshirt" ? -0.0015 : -0.0032,
        scale: previewType === "tshirt" ? 0.74 : 0.66
      });
    } catch (e) {
      largePreview = null;
    }
    return largePreview;
  }

  function openPreviewModal() {
    if (!canOpenPreviewNow()) return;
    if (!enablePreview3D) return;
    if (!mugModalEl) return;
    const isCartPreview = !!(cfg && Number(cfg.cart_preview_mode || 0) === 1);
    mugModalEl.classList.add("is-open");
    mugModalEl.setAttribute("aria-hidden", "false");
    const large = ensureLargePreview();
    if (large && typeof large.setView === "function") {
      if (isCartPreview) {
        // Force centered product framing in cart preview.
        large.setView({ x: -0.10, y: 0.00 });
      } else if (miniPreview && typeof miniPreview.getView === "function") {
        large.setView(miniPreview.getView());
      }
    }
    if (large && typeof large.setAutoRotate === "function") {
      large.setAutoRotate(previewType === "tshirt" ? -0.0015 : -0.0032);
    }
    if (large && typeof large.resize === "function") large.resize();
  }

  function closePreviewModal() {
    if (!mugModalEl) return;
    mugModalEl.classList.remove("is-open");
    mugModalEl.setAttribute("aria-hidden", "true");
    if (largePreview && typeof largePreview.setAutoRotate === "function") {
      largePreview.setAutoRotate(0);
    }
  }

  function closeProductPreview2DModal() {
    if (!productPreview2DModal || !productPreview2DModal.wrap) return;
    productPreview2DModal.wrap.classList.remove("is-open");
    productPreview2DModal.wrap.setAttribute("aria-hidden", "true");
  }

  function buildProductPreview2DModal() {
    if (productPreview2DModal && productPreview2DModal.wrap) return productPreview2DModal;
    const wrap = document.createElement("div");
    wrap.className = "ds-zone-picker-modal ds-product-preview-modal";
    wrap.setAttribute("data-role", "product-preview-2d-modal");
    wrap.setAttribute("aria-hidden", "true");
    const dialog = document.createElement("div");
    dialog.className = "ds-zone-picker-dialog ds-product-preview-dialog";
    dialog.setAttribute("role", "dialog");
    dialog.setAttribute("aria-modal", "true");
    dialog.setAttribute("aria-label", tUi("preview_product", "Preview product"));
    dialog.innerHTML = ''
      + '<div class="ds-zone-picker-top">'
      + '  <h3>' + escHtml(tUi("preview_product_title", "Preview Product")) + '</h3>'
      + '  <button type="button" class="ds-woo-btn" data-action="product-preview-close">' + escHtml(tUi("close", "Close")) + '</button>'
      + '</div>'
      + '<div class="ds-zone-picker-help">' + escHtml(tUi("showing_active_print_areas", "Showing active print areas on selected mockups.")) + '</div>'
      + '<div class="ds-zone-picker-list" data-role="product-preview-list"></div>'
      + '<p class="ds-zone-picker-note">' + escHtml(tUi("preview_approx_note", "Preview is approximate and for print placement guidance only. Final product may differ.")) + '</p>';
    wrap.appendChild(dialog);
    root.appendChild(wrap);
    productPreview2DModal = {
      wrap: wrap,
      dialog: dialog,
      list: dialog.querySelector('[data-role="product-preview-list"]')
    };
    wrap.addEventListener("click", (e) => {
      if (e.target === wrap) closeProductPreview2DModal();
    });
    dialog.addEventListener("click", (e) => {
      const t = e.target && e.target.closest ? e.target.closest("[data-action]") : null;
      if (!t) return;
      if (t.getAttribute("data-action") === "product-preview-close") closeProductPreview2DModal();
    });
    return productPreview2DModal;
  }

  function renderProductPreview2DMockup(mockup, selectedIds, done) {
    const fallback = String((mockup && mockup.image_url) || "");
    if (!mockup || !fallback) { done(fallback); return; }
    const selectedSet = {};
    (selectedIds || []).forEach((id) => { selectedSet[String(id || "")] = true; });
    const areas = Array.isArray(mockup.areas) ? mockup.areas : [];
    const zoneById = {};
    if (zoneState && Array.isArray(zoneState.zones)) {
      zoneState.zones.forEach((z) => {
        const zid = String((z && z.id) || "");
        if (!zid) return;
        zoneById[zid] = z;
      });
    }
    const renderComposed = (baseImg) => {
      if (!baseImg) { done(fallback); return; }
      const bw = Math.max(1, Number(baseImg.naturalWidth || baseImg.width || 1));
      const bh = Math.max(1, Number(baseImg.naturalHeight || baseImg.height || 1));
      const targetW = 540;
      const tw = targetW;
      const th = Math.max(1, Math.round((targetW * bh) / bw));
      const stage = document.createElement("canvas");
      stage.width = tw;
      stage.height = th;
      const sctx = stage.getContext("2d");
      if (!sctx) { done(fallback); return; }
      sctx.clearRect(0, 0, tw, th);
      sctx.drawImage(baseImg, 0, 0, tw, th);
      const usedAuto = {};
      areas.forEach((a) => {
        const explicitZoneId = String((a && (a.zone_id || a.zoneId)) || "").trim();
        let zid = "";
        if (explicitZoneId && zoneById[explicitZoneId] && !usedAuto[explicitZoneId]) {
          zid = explicitZoneId;
          usedAuto[explicitZoneId] = true;
        } else {
          zid = resolveAreaZoneId(a, zoneById, usedAuto, mockup.side) || String((a && a.id) || "");
          if (zid) usedAuto[zid] = true;
        }
        if (!zid || !selectedSet[zid]) return;
        const zc = getZonePreviewCanvasForDisplay(zid);
        if (!zc) return;
        const zx = Math.max(0, Math.min(1, Number((a && a.x_pct) || 0)));
        const zy = Math.max(0, Math.min(1, Number((a && a.y_pct) || 0)));
        const zw = Math.max(0.01, Math.min(1, Number((a && a.w_pct) || 0.25)));
        const zh = Math.max(0.01, Math.min(1, Number((a && a.h_pct) || 0.25)));
        const rx = Math.round(zx * tw);
        const ry = Math.round(zy * th);
        const rw = Math.max(1, Math.round(zw * tw));
        const rh = Math.max(1, Math.round(zh * th));
        sctx.drawImage(zc, rx, ry, rw, rh);
      });
      try { done(stage.toDataURL("image/png")); } catch (e) { done(fallback); }
    };
    const cacheKey = String(fallback);
    const cached = productPreview2DImageCache[cacheKey];
    if (cached && cached.complete) {
      renderComposed(cached);
      return;
    }
    const im = new Image();
    im.onload = function() {
      productPreview2DImageCache[cacheKey] = im;
      renderComposed(im);
    };
    im.onerror = function() { done(fallback); };
    im.src = fallback;
  }

  function renderProductPreview2DModal() {
    const modal = buildProductPreview2DModal();
    if (!modal || !modal.list) return;
    modal.list.innerHTML = "";
    if (!zoneState || !zoneState.enabled) {
      modal.list.innerHTML = '<div class="ds-zone-picker-empty">' + escHtml(tUi("product_preview_after_template_init", "Product preview is available after template initialization.")) + '</div>';
      return;
    }
    const selectedIds = (() => {
      let ids = getSelectedZoneIds();
      const isCartPreview = !!(cfg && Number(cfg.cart_preview_mode || 0) === 1);
      if (isCartPreview || !ids.length) {
        const used = collectUsedZonesForSave();
        if (Array.isArray(used) && used.length) ids = used.slice();
      }
      const uniq = {};
      ids = (ids || []).map((id) => String(id || "")).filter(Boolean).filter((id) => {
        if (uniq[id]) return false;
        uniq[id] = true;
        return true;
      });
      return ids;
    })();
    if (!selectedIds.length) {
      modal.list.innerHTML = '<div class="ds-zone-picker-empty">' + escHtml(tUi("no_active_print_areas_selected", "No active print areas selected yet.")) + '</div>';
      return;
    }
    const selectedSet = {};
    selectedIds.forEach((id) => { selectedSet[String(id || "")] = true; });
    const zoneById = {};
    (zoneState.zones || []).forEach((z) => { zoneById[String((z && z.id) || "")] = z; });
    const mockups = getTemplateMockups().filter((m) => {
      if (!m || !m.image_url) return false;
      const areas = Array.isArray(m.areas) ? m.areas : [];
      return areas.some((a) => {
        const zid = resolveAreaZoneId(a, zoneById, null, m.side) || String((a && a.id) || "");
        return !!selectedSet[zid];
      });
    });
    if (!mockups.length) {
      modal.list.innerHTML = '<div class="ds-zone-picker-empty">' + escHtml(tUi("no_active_mockups_to_preview", "No active mockups to preview.")) + '</div>';
      return;
    }
    mockups.forEach((m, i) => {
      const group = document.createElement("div");
      group.className = "ds-zone-picker-group";
      const head = document.createElement("div");
      head.className = "ds-zone-picker-group-head";
      const title = document.createElement("div");
      title.className = "ds-zone-picker-title";
      title.textContent = String(m.name || tUi("mockup_numbered", "Mockup %s", [i + 1]));
      head.appendChild(title);
      const preview = document.createElement("div");
      preview.className = "ds-zone-picker-preview";
      const stage = document.createElement("div");
      stage.className = "ds-zone-picker-stage";
      const img = document.createElement("img");
      img.className = "ds-zone-picker-preview-img";
      img.alt = title.textContent;
      img.src = String(m.image_url || "");
      stage.appendChild(img);
      preview.appendChild(stage);
      group.appendChild(head);
      group.appendChild(preview);
      modal.list.appendChild(group);
      renderProductPreview2DMockup(m, selectedIds, (src) => {
        if (!src) return;
        img.src = src;
      });
    });
  }

  function openProductPreview2DModal() {
    if (!canOpenPreviewNow()) return;
    const modal = buildProductPreview2DModal();
    if (!modal || !modal.wrap) return;
    renderProductPreview2DModal();
    modal.wrap.classList.add("is-open");
    modal.wrap.setAttribute("aria-hidden", "false");
  }

  function markPreviewTexturesDirty() {
    if (zoneState && zoneState.enabled && zoneState.currentId) {
      copyMainCanvasToZone(zoneState.currentId);
    }
    if (miniPreview && typeof miniPreview.markDirty === "function") miniPreview.markDirty();
    if (largePreview && typeof largePreview.markDirty === "function") largePreview.markDirty();
  }

  function initMugPreview(targetEl, options) {
    const opts = options || {};
    const base = createBasePreviewScene(targetEl, {
      interactive: opts.interactive,
      autoRotate: opts.autoRotate,
      scale: opts.scale,
      cameraPosition: [0, -0.35, 5.25],
      cameraLookAt: [0, 0.08, 0],
      resetPitch: -0.12,
      resetYaw: 0
    });
    if (!base) return null;

    const templateWidthMm = Math.max(1, Number(cfg.width_mm) || 200);
    const templateHeightMm = Math.max(1, Number(cfg.height_mm) || 90);
    const cfgRadius = Number(previewConfig.model_radius_mm);
    const cfgModelHeight = Number(previewConfig.model_height_mm);
    const cfgWall = Number(previewConfig.model_wall_thickness_mm);
    const cfgCirc = Number(previewConfig.circumference_mm);
    const cfgHeight = Number(previewConfig.height_mm);
    const cfgBlank = Number(previewConfig.blank_handle_mm);
    const cfgStart = Number(previewConfig.canvas_start_offset_mm);
    const cfgHeightPct = Number(previewConfig.canvas_height_pct);

    const defaultCircMm = (previewType === "bottle" ? 250 : 230);
    const cupRadiusMm = cfgRadius > 0 ? cfgRadius : (cfgCirc > 0 ? (cfgCirc / (Math.PI * 2)) : (defaultCircMm / (Math.PI * 2)));
    const cupCircMm = cupRadiusMm * Math.PI * 2;
    const cupHeightMm = cfgModelHeight > 0 ? cfgModelHeight : (cfgHeight > 0 ? cfgHeight : templateHeightMm);
    const wallMm = cfgWall > 0 ? cfgWall : (previewType === "bottle" ? 2.6 : 3.2);
    const blankMm = cfgBlank >= 0 ? cfgBlank : Math.max(0, cupCircMm - templateWidthMm);
    const printMm = Math.max(1, cupCircMm - blankMm);
    const startOffsetMm = cfgStart >= 0 ? cfgStart : Math.max(0, (blankMm / 2));
    const labelHeightPct = (cfgHeightPct > 0 && cfgHeightPct <= 1) ? cfgHeightPct : 0.95;

    const mugTextureCanvas = document.createElement("canvas");
    const mugTextureCtx = mugTextureCanvas.getContext("2d");
    function refreshMugTextureSource() {
      const src = (canvas && canvas.lowerCanvasEl) ? canvas.lowerCanvasEl : null;
      if (!src || !mugTextureCtx) return;
      const sw = Math.max(1, Number(src.width) || 1);
      const sh = Math.max(1, Number(src.height) || 1);
      if (mugTextureCanvas.width !== sw || mugTextureCanvas.height !== sh) {
        mugTextureCanvas.width = sw;
        mugTextureCanvas.height = sh;
      }
      mugTextureCtx.setTransform(1, 0, 0, 1, 0, 0);
      mugTextureCtx.globalAlpha = 1;
      mugTextureCtx.globalCompositeOperation = "source-over";
      mugTextureCtx.clearRect(0, 0, sw, sh);
      mugTextureCtx.fillStyle = "#ffffff";
      mugTextureCtx.fillRect(0, 0, sw, sh);
      mugTextureCtx.drawImage(src, 0, 0, sw, sh);
    }

    refreshMugTextureSource();
    const tex = new THREE.CanvasTexture(mugTextureCanvas);
    tex.wrapS = THREE.ClampToEdgeWrapping;
    tex.wrapT = THREE.ClampToEdgeWrapping;
    tex.repeat.set(1, 1);
    tex.offset.set(0, 0);
    tex.needsUpdate = true;

    const mugMat = new THREE.MeshStandardMaterial({
      color: 0xffffff,
      roughness: 0.55,
      metalness: 0.08,
      map: tex
    });
    const ceramicMat = new THREE.MeshStandardMaterial({
      color: 0xf8f8fb,
      roughness: 0.62,
      metalness: 0.05
    });

    const mmToUnit = 1 / 48;
    const outerR = cupRadiusMm * mmToUnit;
    const wallUnit = Math.max(0.002, wallMm * mmToUnit);
    const innerR = Math.max(outerR * 0.45, outerR - wallUnit);
    const bodyHeight = cupHeightMm * mmToUnit;
    const bodyHalf = bodyHeight * 0.5;

    const body = new THREE.Mesh(
      new THREE.CylinderGeometry(outerR, previewType === "bottle" ? (innerR * 0.98) : innerR, bodyHeight, 96, 1, true),
      ceramicMat
    );
    base.model.add(body);

    const thetaPerMm = (Math.PI * 2) / cupCircMm;
    const startTheta = 0 - (startOffsetMm * thetaPerMm);
    const thetaLength = printMm * thetaPerMm;
    const thetaStart = startTheta - thetaLength;
    const labelHeight = bodyHeight * labelHeightPct;
    const label = new THREE.Mesh(
      new THREE.CylinderGeometry(outerR + 0.006, outerR + 0.006, labelHeight, 128, 1, true, thetaStart, thetaLength),
      mugMat
    );
    label.position.y = 0;
    base.model.add(label);

    const topCap = new THREE.Mesh(new THREE.CylinderGeometry(innerR * 0.99, innerR * 0.99, 0.05, 96), ceramicMat);
    topCap.position.y = bodyHalf + 0.025;
    base.model.add(topCap);

    const bottomCap = new THREE.Mesh(new THREE.CylinderGeometry(innerR * 0.92, innerR * 0.92, 0.08, 96), ceramicMat);
    bottomCap.position.y = -bodyHalf - 0.04;
    base.model.add(bottomCap);

    if (previewType === "bottle") {
      const neck = new THREE.Mesh(
        new THREE.CylinderGeometry(innerR * 0.48, innerR * 0.55, bodyHeight * 0.32, 64, 1, true),
        ceramicMat
      );
      neck.position.y = bodyHalf + (bodyHeight * 0.16);
      base.model.add(neck);
    }

    const shadow = new THREE.Mesh(
      new THREE.CircleGeometry(outerR * 1.18, 64),
      new THREE.MeshBasicMaterial({ color: 0xb9c7d8, transparent: true, opacity: 0.28 })
    );
    shadow.rotation.x = -Math.PI / 2;
    shadow.position.y = -bodyHalf - (bodyHeight * 0.05);
    base.scene.add(shadow);

    return {
      resize: base.resize,
      reset: base.reset,
      getView: base.getView,
      setView: base.setView,
      stop: base.stop,
      markDirty: () => { refreshMugTextureSource(); tex.needsUpdate = true; }
    };
  }

  function initTshirtFlatPreview(targetEl, options) {
    if (!targetEl) return null;
    const opts = options || {};
    targetEl.innerHTML = "";
    const c = document.createElement("canvas");
    c.style.width = "100%";
    c.style.height = "100%";
    c.style.display = "block";
    targetEl.appendChild(c);
    const ctx = c.getContext("2d");
    if (!ctx) return null;
    let raf = null;
    let dirty = true;
    const drag = {
      active: false,
      zoneId: "",
      side: "front",
      startPointerX: 0,
      startPointerY: 0,
      startX: 0,
      startY: 0,
      previewW: 1,
      previewH: 1
    };
    const metrics = { w: 1, h: 1, activeZoneId: "", activeRect: null };

    function activeZoneId() {
      return (zoneState && zoneState.enabled && zoneState.currentId) ? String(zoneState.currentId) : "front_full";
    }
    function activeZoneCanvas(zoneId) {
      const display = getZonePreviewCanvasForDisplay(zoneId);
      if (display) return display;
      return canvas.lowerCanvasEl;
    }
    const modelBase = String(cfg.model_assets_base || "").replace(/\/$/, "");
    const twoD = (previewConfig && previewConfig.mockup_2d && typeof previewConfig.mockup_2d === "object") ? previewConfig.mockup_2d : {};
    const imageUrls = {
      front: String(twoD.front || (modelBase ? (modelBase + "/tshirt-front.png") : "")),
      back: String(twoD.back || (modelBase ? (modelBase + "/tshirt-back.png") : "")),
      left: String(twoD.left || (modelBase ? (modelBase + "/tshirt-left.png") : "")),
      right: String(twoD.right || (modelBase ? (modelBase + "/tshirt-right.png") : ""))
    };
    const loaded = { front: null, back: null, left: null, right: null };
    Object.keys(imageUrls).forEach((k) => {
      const src = imageUrls[k];
      if (!src) return;
      const img = new Image();
      img.onload = function() {
        loaded[k] = img;
        tshirtContourMasks[k] = buildContourMaskFromImage(img);
        dirty = true;
      };
      img.onerror = function() { loaded[k] = null; dirty = true; };
      img.src = src;
    });
    function mockupForZone(zoneId) {
      const id = String(zoneId || "");
      if (id === "main_back") return "back";
      if (id === "main_front" || id === "top_left" || id === "top_right" || id === "top_center") return "front";
      if (isPlacementZoneId(id) && zoneState && zoneState.placements && zoneState.placements[id]) {
        const s = String(zoneState.placements[id].side || "front");
        if (s === "back") return "back";
        return "front";
      }
      if (id === "back_full") return "back";
      if (id === "sleeve_left" || id === "side_left") return "left";
      if (id === "sleeve_right" || id === "side_right") return "right";
      return "front";
    }
    function zoneRect(zoneId, mockupKey, w, h) {
      const garmentFrontMmW = Number(twoD.garment_front_width_mm || 500);
      const garmentFrontMmH = Number(twoD.garment_front_height_mm || 750);
      const garmentBackMmW = Number(twoD.garment_back_width_mm || garmentFrontMmW || 500);
      const garmentBackMmH = Number(twoD.garment_back_height_mm || garmentFrontMmH || 750);
      const garmentBounds = {
        // Bounding box of printable garment body inside each mockup image (as % of preview canvas).
        // Can be overridden from preview_config.mockup_2d with:
        // front_body_x/y/w/h and back_body_x/y/w/h
        front: {
          x: Number(twoD.front_body_x != null ? twoD.front_body_x : 0.13),
          y: Number(twoD.front_body_y != null ? twoD.front_body_y : 0.15),
          w: Number(twoD.front_body_w != null ? twoD.front_body_w : 0.74),
          h: Number(twoD.front_body_h != null ? twoD.front_body_h : 0.80)
        },
        back: {
          x: Number(twoD.back_body_x != null ? twoD.back_body_x : 0.13),
          y: Number(twoD.back_body_y != null ? twoD.back_body_y : 0.15),
          w: Number(twoD.back_body_w != null ? twoD.back_body_w : 0.74),
          h: Number(twoD.back_body_h != null ? twoD.back_body_h : 0.80)
        }
      };
      const maps = {
        front: {
          main_front: [0.30, 0.30, 0.40, 0.40],
          top_left: [0.24, 0.18, 0.16, 0.16],
          top_right: [0.60, 0.18, 0.16, 0.16],
          top_center: [0.44, 0.18, 0.16, 0.40],
          front_full: [0.30, 0.30, 0.40, 0.50],
          top: [0.31, 0.21, 0.38, 0.18],
          chest_left: [0.36, 0.24, 0.14, 0.14],
          chest_right: [0.50, 0.24, 0.14, 0.14],
          sleeve_left: [0.15, 0.30, 0.16, 0.20],
          sleeve_right: [0.69, 0.30, 0.16, 0.20],
          side_left: [0.17, 0.40, 0.12, 0.30],
          side_right: [0.71, 0.40, 0.12, 0.30]
        },
        back: {
          main_back: [0.30, 0.30, 0.40, 0.40],
          back_full: [0.30, 0.30, 0.40, 0.50],
          top: [0.31, 0.20, 0.38, 0.17]
        },
        left: {
          sleeve_left: [0.34, 0.08, 0.42, 0.18],
          side_left: [0.30, 0.30, 0.44, 0.58]
        },
        right: {
          sleeve_right: [0.24, 0.08, 0.42, 0.18],
          side_right: [0.26, 0.30, 0.44, 0.58]
        }
      };
      const primary = (maps[mockupKey] && maps[mockupKey][String(zoneId || "")]) || null;
      const fallback = (maps.front && maps.front[String(zoneId || "")]) || [0.30, 0.30, 0.40, 0.50];
      let r = primary || fallback;
      const zid = String(zoneId || "");
      if (isPlacementZoneId(zid) && zoneState && zoneState.placements && zoneState.placements[zid]) {
        const p = zoneState.placements[zid];
        r = [Number(p.x || 0.3), Number(p.y || 0.3), Number(p.w || 0.2), Number(p.h || 0.2)];
      }
      if (zid === "main_front" || zid === "main_back" || zid === "top_left" || zid === "top_right" || zid === "top_center" || zid === "front_full" || zid === "back_full") {
        const mm = getZoneCanvasMm(zid);
        const isBack = (zid === "main_back" || zid === "back_full");
        const gw = isBack ? garmentBackMmW : garmentFrontMmW;
        const gh = isBack ? garmentBackMmH : garmentFrontMmH;
        if (gw > 0 && gh > 0 && mm && mm.w > 0 && mm.h > 0) {
          const body = isBack ? garmentBounds.back : garmentBounds.front;
          const bw = Math.max(0.2, Math.min(0.95, Number(body.w) || 0.74));
          const bh = Math.max(0.2, Math.min(0.95, Number(body.h) || 0.80));
          const bx = Math.max(0, Math.min(1 - bw, Number(body.x) || 0.13));
          const by = Math.max(0, Math.min(1 - bh, Number(body.y) || 0.15));
          const zoneScale = Math.max(0.2, Math.min(1, Number(twoD.full_zone_scale != null ? twoD.full_zone_scale : 1)));
          // Reserved printable area for main zones is fixed 297x297 mm regardless of A4 orientation.
          const areaWmm = (zid === "main_front" || zid === "main_back") ? 297 : mm.w;
          const areaHmm = (zid === "main_front" || zid === "main_back") ? 297 : mm.h;
          const rw = Math.max(0.04, Math.min(bw, bw * (areaWmm / gw) * zoneScale));
          const rh = Math.max(0.04, Math.min(bh, bh * (areaHmm / gh) * zoneScale));
          let cx = (bx + (bw / 2));
          let cy = (by + (bh / 2));
          const topShift = Number(twoD.top_lr_shift_rel != null ? twoD.top_lr_shift_rel : 0.03); // ~15mm over 500mm width
          if (zid === "top_left") { cx = bx + (rw * 0.6) + topShift; cy = by + (rh * 0.6); }
          else if (zid === "top_right") { cx = bx + bw - (rw * 0.6) - topShift; cy = by + (rh * 0.6); }
          else if (zid === "top_center") { cx = bx + (bw / 2); cy = by + (rh * 0.6); }
          else {
            cx = Number(twoD.full_zone_center_x || cx);
            cy = Number(twoD.full_zone_center_y || cy);
          }
          r = [cx - (rw / 2), cy - (rh / 2), rw, rh];
        }
      }
      return { x: w * r[0], y: h * r[1], rw: w * r[2], rh: h * r[3] };
    }
    function drawImageContain(img, x, y, rw, rh) {
      if (!img || rw <= 0 || rh <= 0) return;
      const sw = Math.max(1, img.width || img.naturalWidth || 1);
      const sh = Math.max(1, img.height || img.naturalHeight || 1);
      const s = Math.min(rw / sw, rh / sh);
      const dw = Math.max(1, sw * s);
      const dh = Math.max(1, sh * s);
      const dx = x + ((rw - dw) / 2);
      const dy = y + ((rh - dh) / 2);
      ctx.drawImage(img, dx, dy, dw, dh);
    }
    function render() {
      const w = Math.max(1, targetEl.clientWidth || 1);
      const h = Math.max(1, targetEl.clientHeight || 1);
      const pr = Math.min(window.devicePixelRatio || 1, 2);
      const pw = Math.max(1, Math.round(w * pr));
      const ph = Math.max(1, Math.round(h * pr));
      if (c.width !== pw || c.height !== ph) {
        c.width = pw;
        c.height = ph;
        dirty = true;
      }
      if (!dirty) return;
      ctx.setTransform(1, 0, 0, 1, 0, 0);
      ctx.clearRect(0, 0, pw, ph);
      ctx.scale(pr, pr);
      const zid = activeZoneId();
      const activeSide = zoneSide(zid);
      const mockupKey = mockupForZone(zid);
      const mockup = loaded[mockupKey];
      if (mockup) {
        const iw = mockup.naturalWidth || 1;
        const ih = mockup.naturalHeight || 1;
        const scale = Math.min(w / iw, h / ih);
        const dw = Math.max(1, Math.round(iw * scale));
        const dh = Math.max(1, Math.round(ih * scale));
        const dx = Math.round((w - dw) / 2);
        const dy = Math.round((h - dh) / 2);
        ctx.drawImage(mockup, dx, dy, dw, dh);
      } else {
        ctx.fillStyle = "#eef3f8";
        ctx.fillRect(0, 0, w, h);
      }
      const drawZoneIds = [];
      if (zoneState && zoneState.enabled && (activeSide === "front" || activeSide === "back")) {
        zoneState.zones.forEach((z) => {
          const id = String(z.id || "");
          if (zoneSide(id) !== activeSide) return;
          if (isPlacementZoneId(id)) drawZoneIds.push(id);
        });
      }
      if (!drawZoneIds.includes(zid)) drawZoneIds.push(zid);
      metrics.activeRect = null;
      drawZoneIds.forEach((id) => {
        const src = activeZoneCanvas(id);
        const r = zoneRect(id, mockupKey, w, h);
        if (id === zid) metrics.activeRect = { x: r.x, y: r.y, rw: r.rw, rh: r.rh };
        if (src) {
          ctx.save();
          ctx.beginPath();
          ctx.rect(r.x, r.y, r.rw, r.rh);
          ctx.clip();
          drawImageContain(src, r.x, r.y, r.rw, r.rh);
          ctx.restore();
        }
        const isActive = id === zid;
        ctx.strokeStyle = isActive ? "#ff9b2f" : "#8ca0b8";
        ctx.lineWidth = isActive ? 1.7 : 1.0;
        ctx.setLineDash(isActive ? [6, 4] : [3, 3]);
        ctx.strokeRect(r.x, r.y, r.rw, r.rh);
      });
      ctx.setLineDash([]);
      metrics.w = w;
      metrics.h = h;
      metrics.activeZoneId = zid;
      dirty = false;
    }

    function pointerToLocal(ev) {
      const rect = c.getBoundingClientRect();
      return { x: ev.clientX - rect.left, y: ev.clientY - rect.top };
    }
    function isInsideRect(pt, r) {
      if (!pt || !r) return false;
      return pt.x >= r.x && pt.x <= (r.x + r.rw) && pt.y >= r.y && pt.y <= (r.y + r.rh);
    }
    function clampPlacementToBody(side, x, y, wRel, hRel) {
      const b = getTshirtBodyBounds(side);
      const nx = Math.max(b.x, Math.min(b.x + b.w - wRel, x));
      const ny = Math.max(b.y, Math.min(b.y + b.h - hRel, y));
      return { x: nx, y: ny };
    }

    if (opts && opts.interactive) {
      c.style.touchAction = "none";
      c.addEventListener("pointerdown", (ev) => {
        const zid = metrics.activeZoneId || activeZoneId();
        if (!isPlacementZoneId(zid)) return;
        if (!zoneState || !zoneState.placements || !zoneState.placements[zid]) return;
        const pt = pointerToLocal(ev);
        if (!isInsideRect(pt, metrics.activeRect)) return;
        const p = zoneState.placements[zid];
        drag.active = true;
        drag.zoneId = zid;
        drag.side = String(p.side || zoneSide(zid));
        drag.startPointerX = pt.x;
        drag.startPointerY = pt.y;
        drag.startX = Number(p.x || 0);
        drag.startY = Number(p.y || 0);
        drag.previewW = Math.max(1, metrics.w || 1);
        drag.previewH = Math.max(1, metrics.h || 1);
        try { c.setPointerCapture(ev.pointerId); } catch (e) {}
      });
      c.addEventListener("pointermove", (ev) => {
        if (!drag.active) return;
        const p = zoneState && zoneState.placements ? zoneState.placements[drag.zoneId] : null;
        if (!p) return;
        const pt = pointerToLocal(ev);
        const dxRel = (pt.x - drag.startPointerX) / Math.max(1, drag.previewW);
        const dyRel = (pt.y - drag.startPointerY) / Math.max(1, drag.previewH);
        const cand = clampPlacementToBody(drag.side, drag.startX + dxRel, drag.startY + dyRel, Number(p.w || 0.2), Number(p.h || 0.2));
        if (!placementOverlaps(drag.side, cand.x, cand.y, Number(p.w || 0.2), Number(p.h || 0.2), drag.zoneId)
          && rectInsideContour(drag.side, cand.x, cand.y, Number(p.w || 0.2), Number(p.h || 0.2))) {
          p.x = cand.x;
          p.y = cand.y;
          dirty = true;
          if (miniPreview && typeof miniPreview.markDirty === "function") miniPreview.markDirty();
          if (largePreview && typeof largePreview.markDirty === "function") largePreview.markDirty();
        }
      });
      const endDrag = () => {
        if (!drag.active) return;
        drag.active = false;
        setStatus(setZoneStatusPrefix() + tUi("placement_moved", "Placement moved."));
      };
      c.addEventListener("pointerup", endDrag);
      c.addEventListener("pointercancel", endDrag);
      c.addEventListener("pointerleave", endDrag);
    }
    function tick() {
      render();
      raf = window.requestAnimationFrame(tick);
    }
    if (opts.autoRotate !== 0) tick(); else render();
    return {
      resize: () => { dirty = true; render(); },
      reset: () => { dirty = true; render(); },
      getView: () => ({ x: 0, y: 0 }),
      setView: () => {},
      stop: () => { if (raf) window.cancelAnimationFrame(raf); },
      markDirty: () => { dirty = true; }
    };
  }

  function buildTshirtZoneDescriptors() {
    const map = {};
    templateZones.forEach((zone) => {
      if (!zone || !zone.id) return;
      map[String(zone.id)] = zone;
    });
    const cfgZones = (previewConfig && previewConfig.zones && typeof previewConfig.zones === "object")
      ? previewConfig.zones
      : {};
    const fallbackAnchors = {
      front_full: "front",
      top: "front-top",
      back_full: "back",
      chest_left: "front-left-chest",
      chest_right: "front-right-chest",
      sleeve_left: "left-sleeve",
      sleeve_right: "right-sleeve",
      side_left: "left-side",
      side_right: "right-side"
    };
    const out = [];
    const defaultSize = {
      front_full: { w: 0.42, h: 0.52 },
      top: { w: 0.40, h: 0.20 },
      back_full: { w: 0.42, h: 0.52 },
      chest_left: { w: 0.18, h: 0.18 },
      chest_right: { w: 0.18, h: 0.18 },
      sleeve_left: { w: 0.18, h: 0.18 },
      sleeve_right: { w: 0.18, h: 0.18 }
    };
    const zoneIds = [];
    Object.keys(map).forEach((id) => { if (!zoneIds.includes(id)) zoneIds.push(id); });
    Object.keys(cfgZones).forEach((id) => { if (!zoneIds.includes(id)) zoneIds.push(id); });
    zoneIds.forEach((zoneId) => {
      const cfgZ = cfgZones[zoneId] || {};
      const d = defaultSize[zoneId] || { w: 0.22, h: 0.22 };
      let widthPct = Number(cfgZ.width_pct || d.w);
      let heightPct = Number(cfgZ.height_pct || d.h);
      // Keep full zones realistic on garment surface unless explicitly overridden.
      if ((zoneId === "front_full" || zoneId === "back_full") && !previewConfig.keep_large_zones) {
        if (widthPct > 0.48) widthPct = 0.42;
        if (heightPct > 0.62) heightPct = 0.52;
      }
      out.push({
        id: zoneId,
        anchor: String(cfgZ.anchor || fallbackAnchors[zoneId] || "front"),
        widthPct: widthPct,
        heightPct: heightPct,
        label: (map[zoneId] && map[zoneId].label) ? String(map[zoneId].label) : zoneId
      });
    });
    if (!out.length) {
      out.push({ id: "front_full", anchor: "front", widthPct: 0.42, heightPct: 0.52, label: "Front (Full)" });
      out.push({ id: "top", anchor: "front-top", widthPct: 0.42, heightPct: 0.22, label: "Top" });
      out.push({ id: "back_full", anchor: "back", widthPct: 0.42, heightPct: 0.52, label: "Back (Full)" });
      out.push({ id: "chest_left", anchor: "front-left-chest", widthPct: 0.18, heightPct: 0.18, label: "Chest Left" });
      out.push({ id: "chest_right", anchor: "front-right-chest", widthPct: 0.18, heightPct: 0.18, label: "Chest Right" });
    }
    return out;
  }

  function tshirtAnchorTransform(anchor, w, h) {
    const a = String(anchor || "front");
    if (a === "back") return { x: 0, y: 0.18, z: -0.52, ry: Math.PI, rz: 0 };
    if (a === "front-top") return { x: 0, y: 0.86, z: 0.52, ry: 0, rz: 0 };
    if (a === "front-left-chest") return { x: -0.34, y: 0.86, z: 0.52, ry: 0, rz: 0 };
    if (a === "front-right-chest") return { x: 0.34, y: 0.86, z: 0.52, ry: 0, rz: 0 };
    if (a === "left-sleeve") return { x: -1.08, y: 0.58, z: 0.18, ry: -Math.PI / 2.4, rz: 0 };
    if (a === "right-sleeve") return { x: 1.08, y: 0.58, z: 0.18, ry: Math.PI / 2.4, rz: 0 };
    if (a === "left-side") return { x: -1.02, y: 0.16, z: 0.0, ry: -Math.PI / 2, rz: 0 };
    if (a === "right-side") return { x: 1.02, y: 0.16, z: 0.0, ry: Math.PI / 2, rz: 0 };
    return { x: 0, y: 0.18, z: 0.52, ry: 0, rz: 0 };
  }

  function initTshirtPreview(targetEl, options) {
    const opts = options || {};
    const base = createBasePreviewScene(targetEl, {
      interactive: opts.interactive,
      autoRotate: opts.autoRotate,
      scale: opts.scale,
      fov: 30,
      cameraPosition: [0, -0.25, 4.25],
      cameraLookAt: [0, 0.45, 0],
      resetPitch: -0.1,
      resetYaw: 0.52
    });
    if (!base) return null;

    const shirtColor = String(previewConfig.color || "#ffffff");
    const shirtMat = new THREE.MeshStandardMaterial({
      color: shirtColor,
      roughness: 0.76,
      metalness: 0.04
    });
    const fallbackMeshes = [];

    function addFallbackGeometry() {
      const torso = new THREE.Mesh(new THREE.BoxGeometry(2.2, 2.8, 0.84), shirtMat);
      torso.position.y = 0.2;
      base.model.add(torso);
      fallbackMeshes.push(torso);

      const leftSleeve = new THREE.Mesh(new THREE.CylinderGeometry(0.34, 0.38, 0.9, 24), shirtMat);
      leftSleeve.rotation.z = Math.PI / 2.6;
      leftSleeve.position.set(-1.25, 0.62, 0.0);
      base.model.add(leftSleeve);
      fallbackMeshes.push(leftSleeve);

      const rightSleeve = new THREE.Mesh(new THREE.CylinderGeometry(0.34, 0.38, 0.9, 24), shirtMat);
      rightSleeve.rotation.z = -Math.PI / 2.6;
      rightSleeve.position.set(1.25, 0.62, 0.0);
      base.model.add(rightSleeve);
      fallbackMeshes.push(rightSleeve);

      const neck = new THREE.Mesh(new THREE.TorusGeometry(0.33, 0.1, 16, 48), shirtMat);
      neck.rotation.x = Math.PI / 2;
      neck.position.set(0, 1.52, 0);
      base.model.add(neck);
      fallbackMeshes.push(neck);
    }
    addFallbackGeometry();

    const shadow = new THREE.Mesh(
      new THREE.CircleGeometry(1.55, 64),
      new THREE.MeshBasicMaterial({ color: 0xb9c7d8, transparent: true, opacity: 0.28 })
    );
    shadow.rotation.x = -Math.PI / 2;
    shadow.position.y = -1.28;
    base.scene.add(shadow);

    const zones = buildTshirtZoneDescriptors();
    const zoneTextures = [];
    const zonePlanes = [];
    const zoneFrames = [];
    const zoneEntries = [];
    const zoneDecals = [];
    let glbMeshTarget = null;
    let glbMeshTargets = [];
    let glbUsingDecals = false;
    let glbHasWrappedPlanes = false;
    let glbWorldCenter = new THREE.Vector3(0, 0.2, 0);
    const surfaceOffset = (function() {
      const v = Number(previewConfig.print_surface_offset || 0.028);
      if (v > 0.001 && v < 0.2) return v;
      return 0.028;
    })();
    function applyZoneTransforms(scaleX, scaleY, scaleZ) {
      for (let i = 0; i < zones.length; i++) {
        const zone = zones[i];
        const w = Math.max(0.12, Math.min(0.95, Number(zone.widthPct) || 0.2)) * 2.2;
        const h = Math.max(0.12, Math.min(0.95, Number(zone.heightPct) || 0.2)) * 2.8;
        const t = tshirtAnchorTransform(zone.anchor, w, h);
        const p = zonePlanes[i];
        const f = zoneFrames[i];
        if (!p || !f) continue;
        const px = t.x * scaleX;
        const py = t.y * scaleY;
        const pz = t.z * scaleZ;
        const normal = new THREE.Vector3(0, 0, 1).applyEuler(new THREE.Euler(0, t.ry, t.rz, "XYZ"));
        p.position.set(px + (normal.x * surfaceOffset), py + (normal.y * surfaceOffset), pz + (normal.z * surfaceOffset));
        p.rotation.y = t.ry;
        p.rotation.z = t.rz;
        p.__dsWrapped = false;
        f.position.copy(p.position);
        f.rotation.copy(p.rotation);
        f.translateZ(0.004);
      }
    }

    function projectPointToMesh(meshes, worldPoint, worldNormal) {
      if (!meshes || !meshes.length) return null;
      const ray = new THREE.Raycaster();
      const toCenter = glbWorldCenter.clone().sub(worldPoint).normalize();
      const dirs = [
        worldNormal.clone().normalize().multiplyScalar(-1),
        worldNormal.clone().normalize(),
        toCenter,
        toCenter.clone().multiplyScalar(-1),
        new THREE.Vector3(0, 0, 1),
        new THREE.Vector3(0, 0, -1)
      ];
      let bestHit = null;
      for (let i = 0; i < dirs.length; i++) {
        const dir = dirs[i];
        const from = worldPoint.clone().addScaledVector(dir, -12.0);
        ray.set(from, dir);
        ray.far = 25.0;
        const hits = ray.intersectObjects(meshes, true);
        if (!hits || !hits.length) continue;
        for (let h = 0; h < hits.length; h++) {
          const hit = hits[h];
          if (!bestHit || hit.distance < bestHit.distance) bestHit = hit;
        }
      }
      return bestHit ? bestHit.point : null;
    }

    function wrapZonePlanesToMeshes(meshes) {
      if (!meshes || !meshes.length) return false;
      const q = new THREE.Quaternion();
      const normalWorld = new THREE.Vector3();
      const vLocal = new THREE.Vector3();
      const vWorld = new THREE.Vector3();
      const vTarget = new THREE.Vector3();
      const inv = new THREE.Matrix4();
      let changed = false;
      zoneEntries.forEach((entry) => {
        if (!entry || !entry.plane) return;
        const plane = entry.plane;
        const segX = 16;
        const segY = 16;
        const desiredW = Math.max(0.01, Number(entry.w) || 0.25);
        const desiredH = Math.max(0.01, Number(entry.h) || 0.25);
        if (!plane.geometry || !plane.geometry.attributes || !plane.geometry.attributes.position || (plane.geometry.parameters && (plane.geometry.parameters.width !== desiredW || plane.geometry.parameters.height !== desiredH))) {
          if (plane.geometry) plane.geometry.dispose();
          plane.geometry = new THREE.PlaneGeometry(desiredW, desiredH, segX, segY);
        }
        plane.updateMatrixWorld(true);
        plane.getWorldQuaternion(q);
        normalWorld.set(0, 0, 1).applyQuaternion(q).normalize();
        inv.copy(plane.matrixWorld).invert();
        const posAttr = plane.geometry.attributes.position;
        let zoneChanged = false;
        const maxDepth = 0.11;
        const minDepth = -0.018;
        const depthBlend = 0.72;
        for (let i = 0; i < posAttr.count; i++) {
          vLocal.fromBufferAttribute(posAttr, i);
          vWorld.copy(vLocal).applyMatrix4(plane.matrixWorld);
          const projected = projectPointToMesh(meshes, vWorld, normalWorld);
          if (!projected) continue;
          vTarget.copy(projected).addScaledVector(normalWorld, 0.0016).applyMatrix4(inv);
          // Keep XY grid stable; only push/pull depth to avoid "knife cut" deformation.
          const projectedZ = Math.max(minDepth, Math.min(maxDepth, vTarget.z));
          const nextZ = (vLocal.z * (1 - depthBlend)) + (projectedZ * depthBlend);
          posAttr.setXYZ(i, vLocal.x, vLocal.y, nextZ);
          zoneChanged = true;
        }
        if (!zoneChanged) {
          const centerWorld = plane.getWorldPosition(new THREE.Vector3());
          const centerHit = projectPointToMesh(meshes, centerWorld, normalWorld);
          if (centerHit) {
            plane.position.copy(centerHit.clone().addScaledVector(normalWorld, 0.0012));
            zoneChanged = true;
          }
        }
        if (zoneChanged) {
          posAttr.needsUpdate = true;
          plane.geometry.computeVertexNormals();
          changed = true;
        }
      });
      return changed;
    }

    function wrapSingleZonePlaneToMesh(entry, meshes) {
      if (!entry || !entry.plane || !meshes || !meshes.length) return false;
      const plane = entry.plane;
      const segX = 14;
      const segY = 14;
      const desiredW = Math.max(0.01, Number(entry.w) || 0.25);
      const desiredH = Math.max(0.01, Number(entry.h) || 0.25);
      if (!plane.geometry || !plane.geometry.attributes || !plane.geometry.attributes.position || (plane.geometry.parameters && (plane.geometry.parameters.width !== desiredW || plane.geometry.parameters.height !== desiredH || plane.geometry.parameters.widthSegments !== segX || plane.geometry.parameters.heightSegments !== segY))) {
        if (plane.geometry) plane.geometry.dispose();
        plane.geometry = new THREE.PlaneGeometry(desiredW, desiredH, segX, segY);
      }
      const q = new THREE.Quaternion();
      const normalWorld = new THREE.Vector3();
      const vLocal = new THREE.Vector3();
      const vWorld = new THREE.Vector3();
      const vTarget = new THREE.Vector3();
      const inv = new THREE.Matrix4();
      const maxDepth = 0.11;
      const minDepth = -0.018;
      const depthBlend = 0.72;
      plane.updateMatrixWorld(true);
      plane.getWorldQuaternion(q);
      normalWorld.set(0, 0, 1).applyQuaternion(q).normalize();
      inv.copy(plane.matrixWorld).invert();
      const posAttr = plane.geometry.attributes.position;
      let changed = false;
      for (let i = 0; i < posAttr.count; i++) {
        vLocal.fromBufferAttribute(posAttr, i);
        vWorld.copy(vLocal).applyMatrix4(plane.matrixWorld);
        const projected = projectPointToMesh(meshes, vWorld, normalWorld);
        if (!projected) continue;
        vTarget.copy(projected).addScaledVector(normalWorld, 0.0016).applyMatrix4(inv);
        const projectedZ = Math.max(minDepth, Math.min(maxDepth, vTarget.z));
        const nextZ = (vLocal.z * (1 - depthBlend)) + (projectedZ * depthBlend);
        posAttr.setXYZ(i, vLocal.x, vLocal.y, nextZ);
        changed = true;
      }
      if (changed) {
        posAttr.needsUpdate = true;
        plane.geometry.computeVertexNormals();
      }
      return changed;
    }

    function clearZoneDecals() {
      for (let i = 0; i < zoneDecals.length; i++) {
        const d = zoneDecals[i];
        if (!d) continue;
        if (d.geometry) d.geometry.dispose();
        if (d.material && d.material.dispose) d.material.dispose();
        if (d.parent) d.parent.remove(d);
      }
      zoneDecals.length = 0;
    }

    function collectDecalTargets(rootNode) {
      const list = [];
      if (!rootNode) return list;
      rootNode.traverse((node) => {
        if (!node || (!node.isMesh && !node.isSkinnedMesh) || !node.geometry) return;
        try {
          const g = node.geometry;
          const tri = (g && g.index && g.index.count)
            ? Math.round(g.index.count / 3)
            : ((g && g.attributes && g.attributes.position && g.attributes.position.count) ? Math.round(g.attributes.position.count / 3) : 0);
          node.__dsTriCount = tri;
        } catch (e) {
          node.__dsTriCount = 0;
        }
        list.push(node);
      });
      const stitch = list.filter((m) => /^StitchMatShape_/i.test(String(m.name || "")));
      const pool = stitch.length ? stitch : list;
      return pool.sort((a, b) => (Number(b.__dsTriCount) || 0) - (Number(a.__dsTriCount) || 0));
    }

    function debugExposeTshirtMeshes(meshes) {
      try {
        const items = (meshes || []).map((m, i) => {
          const g = m && m.geometry ? m.geometry : null;
          let tri = 0;
          try {
            if (g && g.index && g.index.count) tri = Math.round(g.index.count / 3);
            else if (g && g.attributes && g.attributes.position && g.attributes.position.count) tri = Math.round(g.attributes.position.count / 3);
          } catch (e) {}
          return {
            i: i,
            name: m && m.name ? String(m.name) : ("mesh_" + i),
            type: m && m.type ? String(m.type) : "",
            triangles: tri
          };
        });
        if (typeof window !== "undefined") window.__DS_TSHIRT_MESHES__ = items;
        if (typeof globalThis !== "undefined") globalThis.__DS_TSHIRT_MESHES__ = items;
        if (typeof console !== "undefined") console.info("[DS] T-shirt meshes:", items);
      } catch (e) {}
    }

    function pickDefaultDecalTarget(meshes) {
      let best = null;
      let bestScore = 0;
      (meshes || []).forEach((node) => {
        const geom = node.geometry;
        if (!geom.boundingBox) geom.computeBoundingBox();
        if (!geom.boundingBox) return;
        const size = geom.boundingBox.getSize(new THREE.Vector3());
        const score = Math.abs(size.x * size.y * size.z);
        if (score > bestScore) {
          bestScore = score;
          best = node;
        }
      });
      if (!best && meshes && meshes.length) best = meshes[0];
      return best;
    }

    function buildBestZoneDecalGeometry(DecalGeometryCtor, meshes, fallbackTarget, pos, rot, size) {
      let bestGeo = null;
      let bestCount = 0;
      const allCandidates = (meshes && meshes.length) ? meshes : (fallbackTarget ? [fallbackTarget] : []);
      const candidates = allCandidates.length > 8 ? allCandidates.slice(0, 8) : allCandidates;
      for (let i = 0; i < candidates.length; i++) {
        const mesh = candidates[i];
        if (!mesh) continue;
        try {
          const geo = new DecalGeometryCtor(mesh, pos, rot, size);
          const posAttr = geo && geo.attributes ? geo.attributes.position : null;
          const count = (posAttr && posAttr.count) ? posAttr.count : 0;
          if (count > bestCount) {
            if (bestGeo && bestGeo.dispose) bestGeo.dispose();
            bestGeo = geo;
            bestCount = count;
          } else if (geo && geo.dispose) {
            geo.dispose();
          }
        } catch (e) {}
      }
      return (bestGeo && bestCount > 0) ? bestGeo : null;
    }

    function rebuildZoneDecals(DecalGeometryCtor, targetMeshes, defaultTarget, scaleX, scaleY, scaleZ) {
      if (!DecalGeometryCtor || !defaultTarget) return false;
      clearZoneDecals();
      const useMultiMesh = (previewConfig.decal_multi_mesh == null) ? true : !!previewConfig.decal_multi_mesh;
      const decalMeshes = useMultiMesh ? targetMeshes : [defaultTarget];
      const decalDepth = (function() {
        const v = Number(previewConfig.decal_depth || 2.2);
        return (v > 0.08 && v < 6) ? v : 2.2;
      })();
      for (let i = 0; i < zones.length; i++) {
        const zone = zones[i];
        const w = Math.max(0.12, Math.min(0.95, Number(zone.widthPct) || 0.2)) * 2.2;
        const h = Math.max(0.12, Math.min(0.95, Number(zone.heightPct) || 0.2)) * 2.8;
        const t = tshirtAnchorTransform(zone.anchor, w, h);
        const normal = new THREE.Vector3(0, 0, 1).applyEuler(new THREE.Euler(0, t.ry, t.rz, "XYZ")).normalize();
        const pos = new THREE.Vector3(t.x * scaleX, t.y * scaleY, t.z * scaleZ).addScaledVector(normal, -decalDepth * 0.35);
        const rot = new THREE.Euler(0, t.ry, t.rz, "XYZ");
        const size = new THREE.Vector3(Math.abs(w * scaleX), Math.abs(h * scaleY), decalDepth);
        try {
          const geo = buildBestZoneDecalGeometry(DecalGeometryCtor, decalMeshes, defaultTarget, pos, rot, size);
          if (!geo) {
            zoneDecals[i] = null;
            continue;
          }
          const posAttr = geo && geo.attributes ? geo.attributes.position : null;
          if (!posAttr || !posAttr.count) {
            if (geo && geo.dispose) geo.dispose();
            zoneDecals[i] = null;
            continue;
          }
          const mat = new THREE.MeshStandardMaterial({
            map: zoneTextures[i],
            transparent: true,
            opacity: 0.985,
            roughness: 0.62,
            metalness: 0.0,
            side: THREE.DoubleSide,
            depthWrite: false,
            polygonOffset: true,
            polygonOffsetFactor: -1,
            polygonOffsetUnits: -1
          });
          const decalMesh = new THREE.Mesh(geo, mat);
          decalMesh.renderOrder = 24;
          base.model.add(decalMesh);
          zoneDecals[i] = decalMesh;
        } catch (e) {
          zoneDecals[i] = null;
        }
      }
      return zoneDecals.some((d) => !!d);
    }

    function createOpaqueTextureSource(sourceCanvas) {
      const textureCanvas = document.createElement("canvas");
      const textureCtx = textureCanvas.getContext("2d");
      function refresh() {
        const src = sourceCanvas;
        if (!src || !textureCtx) return;
        const sw = Math.max(1, Number(src.width) || 1);
        const sh = Math.max(1, Number(src.height) || 1);
        if (textureCanvas.width !== sw || textureCanvas.height !== sh) {
          textureCanvas.width = sw;
          textureCanvas.height = sh;
        }
        textureCtx.setTransform(1, 0, 0, 1, 0, 0);
        textureCtx.globalAlpha = 1;
        textureCtx.globalCompositeOperation = "source-over";
        textureCtx.clearRect(0, 0, sw, sh);
        textureCtx.fillStyle = "#ffffff";
        textureCtx.fillRect(0, 0, sw, sh);
        textureCtx.drawImage(src, 0, 0, sw, sh);
      }
      refresh();
      return { canvas: textureCanvas, refresh: refresh };
    }

    zones.forEach((zone) => {
      const w = Math.max(0.12, Math.min(0.95, Number(zone.widthPct) || 0.2)) * 2.2;
      const h = Math.max(0.12, Math.min(0.95, Number(zone.heightPct) || 0.2)) * 2.8;
      const t = tshirtAnchorTransform(zone.anchor, w, h);
      const sourceCanvas = (zoneState && zoneState.previewCanvases && zoneState.previewCanvases[zone.id])
        ? zoneState.previewCanvases[zone.id]
        : canvas.lowerCanvasEl;
      const zoneTextureSource = createOpaqueTextureSource(sourceCanvas);
      const zoneTexture = new THREE.CanvasTexture(zoneTextureSource.canvas);
      zoneTexture.wrapS = THREE.ClampToEdgeWrapping;
      zoneTexture.wrapT = THREE.ClampToEdgeWrapping;
      zoneTexture.needsUpdate = true;
      zoneTextures.push(zoneTexture);

      const zoneMat = new THREE.MeshStandardMaterial({
        map: zoneTexture,
        transparent: true,
        opacity: 0.98,
        roughness: 0.5,
        metalness: 0.02,
        side: THREE.DoubleSide,
        depthWrite: false,
        polygonOffset: true,
        polygonOffsetFactor: -2,
        polygonOffsetUnits: -2
      });
      const zonePlane = new THREE.Mesh(new THREE.PlaneGeometry(w, h, 1, 1), zoneMat);
      zonePlane.position.set(t.x, t.y, t.z);
      zonePlane.rotation.y = t.ry;
      zonePlane.rotation.z = t.rz;
      zonePlane.renderOrder = 20;
      base.model.add(zonePlane);
      zonePlanes.push(zonePlane);

      const frame = new THREE.LineSegments(
        new THREE.EdgesGeometry(new THREE.PlaneGeometry(w, h)),
        new THREE.LineBasicMaterial({ color: 0xff9b2f, transparent: true, opacity: 0.95 })
      );
      frame.position.copy(zonePlane.position);
      frame.rotation.copy(zonePlane.rotation);
      frame.translateZ(0.002);
      frame.renderOrder = 22;
      base.model.add(frame);
      zoneFrames.push(frame);
      zoneEntries.push({ id: zone.id, plane: zonePlane, frame: frame, texture: zoneTexture, textureSource: zoneTextureSource, w: w, h: h });
    });

    function applyActiveZoneVisibility() {
      const activeId = (zoneState && zoneState.currentId) ? String(zoneState.currentId) : "";
      const forceWrapActive = (previewConfig.tshirt_force_wrap_active == null) ? true : !!previewConfig.tshirt_force_wrap_active;
      zoneEntries.forEach((entry, idx) => {
        const visible = !activeId || entry.id === activeId;
        const hasDecal = !!zoneDecals[idx];
        if (visible && glbMeshTargets && glbMeshTargets.length && (!entry.plane.__dsWrapped) && (forceWrapActive || !hasDecal)) {
          if (wrapSingleZonePlaneToMesh(entry, glbMeshTargets)) {
            entry.plane.__dsWrapped = true;
            glbHasWrappedPlanes = true;
            setProjectionModeDebug("wrap-active");
          }
        }
        if (entry.plane) entry.plane.visible = visible && (forceWrapActive || !glbUsingDecals || !hasDecal);
        if (entry.frame) entry.frame.visible = visible && !glbHasWrappedPlanes;
        if (zoneDecals[idx]) zoneDecals[idx].visible = visible && glbUsingDecals && !forceWrapActive;
      });
    }

    const modelBase = String(cfg.model_assets_base || "");
    const modelUrl = String(previewConfig.model_url || (modelBase ? (modelBase.replace(/\/$/, "") + "/tshirt.glb") : ""));
    if (modelUrl && typeof THREE !== "undefined") {
      Promise.all([resolveGLTFLoaderCtor(), resolveDecalGeometryCtor()]).then((ctors) => {
        const GLTFLoaderCtor = ctors[0];
        const DecalGeometryCtor = ctors[1];
        if (!GLTFLoaderCtor) {
          try { console.warn("[DS] GLTFLoader unavailable, using fallback T-shirt preview."); } catch (e) {}
          return;
        }
        try {
          const loader = new GLTFLoaderCtor();
          loader.load(modelUrl, (gltf) => {
          if (!gltf || !gltf.scene) return;
          const shirtRoot = gltf.scene;
          const makeClothMat = () => new THREE.MeshStandardMaterial({
            color: shirtColor,
            roughness: 0.82,
            metalness: 0.0,
            side: THREE.DoubleSide
          });
          shirtRoot.traverse((node) => {
            if (!node || !node.isMesh) return;
            if (Array.isArray(node.material)) {
              node.material = node.material.map(() => makeClothMat());
            } else {
              node.material = makeClothMat();
            }
            node.castShadow = false;
            node.receiveShadow = true;
          });

          const preBox = new THREE.Box3().setFromObject(shirtRoot);
          const preSize = preBox.getSize(new THREE.Vector3());
          const targetHeight = 2.9;
          const scale = preSize.y > 0 ? (targetHeight / preSize.y) : 1;
          shirtRoot.scale.setScalar(scale);
          const postBox = new THREE.Box3().setFromObject(shirtRoot);
          const postSize = postBox.getSize(new THREE.Vector3());
          const postCenter = postBox.getCenter(new THREE.Vector3());
          shirtRoot.position.x += -postCenter.x;
          shirtRoot.position.z += -postCenter.z;
          shirtRoot.position.y += (-1.2 - postBox.min.y);
          shirtRoot.updateMatrixWorld(true);
          const centeredBox = new THREE.Box3().setFromObject(shirtRoot);
          glbWorldCenter.copy(centeredBox.getCenter(new THREE.Vector3()));

          fallbackMeshes.forEach((m) => base.model.remove(m));
          fallbackMeshes.length = 0;
          base.model.add(shirtRoot);
          glbMeshTargets = collectDecalTargets(shirtRoot);
          debugExposeTshirtMeshes(glbMeshTargets);
          glbMeshTarget = pickDefaultDecalTarget(glbMeshTargets);

          const zoneFit = Number(previewConfig.zone_fit || 0.66);
          const fit = (zoneFit > 0.3 && zoneFit <= 1.2) ? zoneFit : 0.66;
          const sx = (postSize.x > 0 ? (postSize.x / 2.2) : 1) * fit;
          const sy = (postSize.y > 0 ? (postSize.y / 2.8) : 1) * fit;
          const sz = (postSize.z > 0 ? (postSize.z / 0.84) : 1) * fit;
          const projectionMode = String(previewConfig.tshirt_projection || "decal").toLowerCase();
          glbUsingDecals = rebuildZoneDecals(DecalGeometryCtor, glbMeshTargets, glbMeshTarget, sx, sy, sz);
          applyZoneTransforms(sx, sy, sz);
          let wrapped = false;
          if (projectionMode === "wrap") {
            glbUsingDecals = false;
            clearZoneDecals();
            wrapped = wrapZonePlanesToMeshes(glbMeshTargets);
          } else if (projectionMode === "auto") {
            if (!glbUsingDecals) wrapped = wrapZonePlanesToMeshes(glbMeshTargets);
          } else if (!glbUsingDecals) {
            setStatus("Decal projection not available for this model. Using plane fallback.");
          }
          glbHasWrappedPlanes = wrapped;
          applyActiveZoneVisibility();
          try {
            console.info("[DS] T-shirt GLB loaded:", modelUrl);
            if (glbUsingDecals) {
              setProjectionModeDebug("decal");
              setStatus("3D model loaded (decal mode).");
            } else if (wrapped) {
              setProjectionModeDebug("mesh-wrap");
              setStatus("3D model loaded (mesh-wrap mode).");
            } else {
              setProjectionModeDebug("plane-fallback");
              setStatus("3D model loaded (plane fallback).");
            }
          } catch (e) {}
          }, undefined, (err) => {
            try {
              console.warn("[DS] T-shirt GLB load failed:", modelUrl, err);
              setStatus("3D model load failed. Using fallback.");
            } catch (e) {}
          });
        } catch (e) {
          try {
            console.warn("[DS] GLTF init error:", e);
            setStatus("3D model init error. Using fallback.");
          } catch (x) {}
        }
      });
    }

    applyActiveZoneVisibility();

    return {
      resize: base.resize,
      reset: base.reset,
      getView: base.getView,
      setView: base.setView,
      stop: base.stop,
      markDirty: () => {
        applyActiveZoneVisibility();
        const activeId = (zoneState && zoneState.currentId) ? String(zoneState.currentId) : "";
        zoneEntries.forEach((entry) => {
          if (!entry.texture) return;
          if (entry.textureSource && typeof entry.textureSource.refresh === "function") {
            entry.textureSource.refresh();
          }
          if (!activeId || entry.id === activeId) {
            entry.texture.needsUpdate = true;
          }
        });
      }
    };
  }

  function mmToPx(mm, dpi) {
    return Math.round((mm / 25.4) * dpi);
  }

  const previewDpi = Math.min(150, Number(cfg.dpi) || 300);
  let widthPx = mmToPx(Number(cfg.width_mm) || 200, previewDpi);
  let heightPx = mmToPx(Number(cfg.height_mm) || 100, previewDpi);
  const serialProps = ["clipPath", "selectable", "evented", "hasControls", "dsSystem", "dsGrid", "excludeFromExport", "dsUid", "paintFirst"];
  const gridStep = 20;

  canvasEl.width = widthPx;
  canvasEl.height = heightPx;
  const canvas = new fabric.Canvas(canvasEl, {
    preserveObjectStacking: true,
    backgroundColor: "#fff",
    targetFindTolerance: isTouchDevice ? 18 : 6,
    selection: true,
    selectionKey: ["shiftKey", "ctrlKey", "metaKey"],
    selectionColor: "rgba(0,0,0,0)",
    selectionBorderColor: "rgba(0,0,0,0)",
    selectionLineWidth: 0,
    // Keep object selection/drag deterministic on touch; mobile navigation is handled by custom 2-finger gestures.
    allowTouchScrolling: false
  });
  try {
    if (wrapEl) {
      wrapEl.style.display = "block";
      wrapEl.style.visibility = "visible";
      wrapEl.style.opacity = "1";
    }
    if (viewportEl) {
      viewportEl.style.display = "flex";
      viewportEl.style.visibility = "visible";
      viewportEl.style.opacity = "1";
      if (!viewportEl.style.minHeight) viewportEl.style.minHeight = "360px";
    }
    if (canvasEl) {
      canvasEl.style.display = "block";
      canvasEl.style.visibility = "visible";
      canvasEl.style.opacity = "1";
    }
    if (canvas && canvas.lowerCanvasEl) {
      canvas.lowerCanvasEl.style.display = "block";
      canvas.lowerCanvasEl.style.visibility = "visible";
      canvas.lowerCanvasEl.style.opacity = "1";
    }
    if (canvas && canvas.upperCanvasEl) {
      canvas.upperCanvasEl.style.display = "block";
      canvas.upperCanvasEl.style.visibility = "visible";
      canvas.upperCanvasEl.style.opacity = "1";
    }
  } catch (e) {}
  try {
    const normalizedZones = normalizeZoneList();
    const singleZoneId = normalizedZones.length === 1 ? String(normalizedZones[0].id || "") : "";
    zoneState = {
      enabled: shouldEnableZones(normalizedZones),
      zones: normalizedZones,
      currentId: normalizedZones[0] ? normalizedZones[0].id : "front_full",
      selectedIds: singleZoneId ? [singleZoneId] : [],
      userSelectionReady: !!singleZoneId,
      snapshots: {},
      previewCanvases: {},
      previewTransparentCanvases: {},
      previewCanvasStamp: {},
      orientations: {},
      placements: {},
      buttonsWrap: null,
      internalSwitch: false
    };
    zoneState.zones.forEach((z) => {
      const mmRaw = getZoneMeta(z.id);
      const zw = mmRaw ? Number(mmRaw.width_mm || mmRaw.widthMm || mmRaw.width || mmRaw.w_mm || mmRaw.w || 0) : 0;
      const zh = mmRaw ? Number(mmRaw.height_mm || mmRaw.heightMm || mmRaw.height || mmRaw.h_mm || mmRaw.h || 0) : 0;
      zoneState.orientations[z.id] = (zw > 0 && zh > 0 && zw > zh) ? "landscape" : "portrait";
      zoneState.snapshots[z.id] = { version: fabric.version, objects: [] };
      zoneState.previewCanvases[z.id] = createBlankZoneCanvas(z.id);
      zoneState.previewCanvasStamp[z.id] = 0;
    });
    buildZoneToolbar();
    buildOrientationToolbar();
  } catch (e) {
    zoneState = {
      enabled: false,
      zones: [{ id: "front_full", label: "Front" }],
      currentId: "front_full",
      selectedIds: ["front_full"],
      userSelectionReady: true,
      snapshots: {},
      previewCanvases: {},
      previewTransparentCanvases: {},
      previewCanvasStamp: {},
      orientations: { front_full: "portrait" },
      placements: {},
      buttonsWrap: null,
      internalSwitch: false
    };
    buildOrientationToolbar();
  }
  if (previewTitleEl) previewTitleEl.textContent = getPreviewTitle();
  try {
    miniPreview = (enablePreview3D && (!activePreviewNeedsThree || typeof THREE !== "undefined"))
      ? activePreviewFactory(mugMiniCanvasEl, { interactive: false, autoRotate: previewType === "tshirt" ? 0.0022 : 0, scale: previewType === "tshirt" ? 0.62 : 0.56 })
      : null;
  } catch (e) {
    miniPreview = null;
  }
  if (mugOpenBtn && enablePreview3D) {
    if (previewType === "mug") mugOpenBtn.textContent = tUi("mug_preview_short", "Mug");
    else if (previewType === "bottle") mugOpenBtn.textContent = tUi("bottle_preview_short", "Bottle");
    else mugOpenBtn.textContent = tUi("preview", "Preview");
  }
  if (!enablePreview3D || (activePreviewNeedsThree && typeof THREE === "undefined")) {
    if (mugMiniGroupEl) mugMiniGroupEl.style.display = "none";
    if (mugModalEl) mugModalEl.style.display = "none";
  }

  const state = {
    zoom: 1,
    fitScale: 1,
    syncProps: false,
    draggedUid: null,
    uidCounter: 1,
    recentSelection: [],
    cropSession: null,
    aiBgRemoving: false,
    objectQuickDock: null,
    mobileActiveToolTab: "",
    textureBgObj: null,
    gridEnabled: !gridToggle || !!gridToggle.checked,
    snapEnabled: !snapToggle || !!snapToggle.checked,
    saving: false,
    suppressPreviewOpenUntil: 0
    ,lastSelectedText: null
    ,lastSelectedObject: null
    ,gestureLastTapTs: 0
    ,gestureLastTapUid: ""
    ,gestureTapDown: null
    ,gestureTapMoved: false
    ,gestureTapTransforming: false
    ,gestureDoubleTapMs: 300
    ,gestureTapMoveMax: 16
    ,longPressTimer: null
    ,longPressStart: null
    ,longPressMenu: null
    ,touchLastTapTs: 0
    ,touchLastTapUid: ""
    ,touchStartX: 0
    ,touchStartY: 0
    ,lastObjectUiValues: null
  };
  const editorUiState = {
    mobileMode: "canvas",
    selectedObjectType: null
  };
  root.__dsEditorUiState = editorUiState;

  const history = { undo: [], redo: [], max: 100, last: "" };
  let restoring = false;

  function isSystemObject(o) { return !!(o && o.dsSystem); }
  function isCropFrameObject(o) { return !!(o && o.dsCropFrame); }
  function isTextObject(o) {
    if (!o) return false;
    const t = String(o.type || "").toLowerCase();
    if (t === "i-text" || t === "textbox" || t === "text" || t === "itext" || t.indexOf("text") >= 0) return true;
    if (typeof o.isType === "function") {
      try {
        if (o.isType("i-text") || o.isType("textbox") || o.isType("text")) return true;
      } catch (e) {}
    }
    return (typeof o.text === "string" && t !== "image");
  }
  function isStickerObject(o) {
    if (!o) return false;
    return !!(o.dsSticker || o.dsSourceType === "emoji" || o.dsSourceType === "icon");
  }
  function isShapeObject(o) {
    if (!o) return false;
    if (isTextObject(o) || o.type === "image" || o.type === "group" || o.type === "activeSelection") return false;
    return true;
  }
  function ensureUid(o) { if (!o.dsUid) o.dsUid = "obj_" + (state.uidCounter++); return o.dsUid; }
  function nudgePreviewGuard(ms) {
    const wait = Number(ms) || 400;
    state.suppressPreviewOpenUntil = Date.now() + Math.max(120, wait);
  }
  function canOpenPreviewNow() {
    return Date.now() >= Number(state.suppressPreviewOpenUntil || 0);
  }
  function applyTextFontNow(obj, fontStack) {
    if (!obj || !isTextObject(obj)) return;
    const ff = String(fontStack || "Arial");
    const prevCenter = (typeof obj.getCenterPoint === "function") ? obj.getCenterPoint() : null;
    obj.set("fontFamily", ff);
    // For textbox objects, keep selection width synced to actual text width after font change.
    if (obj.type === "textbox" && typeof obj.calcTextWidth === "function") {
      const measured = Number(obj.calcTextWidth()) || 0;
      if (measured > 0) {
        const pad = Number(obj.padding) || 0;
        const minW = Number(obj.minWidth) || 2;
        obj.set("width", Math.max(minW, measured + (pad * 2) + 2));
      }
    }
    if (typeof obj.initDimensions === "function") obj.initDimensions();
    if (prevCenter && typeof obj.setPositionByOrigin === "function") {
      obj.setPositionByOrigin(prevCenter, "center", "center");
    }
    obj.set("dirty", true);
    obj.setCoords();
    canvas.requestRenderAll();
  }

  function bindMobileTextEditingGuards(o) {
    if (!o || !isTextObject(o) || isStickerObject(o) || o.__dsTextEditGuardsBound) return;
    o.__dsTextEditGuardsBound = true;
    if (typeof o.on === "function") {
      o.on("editing:entered", () => {
        o.dsForceMobileEdit = true;
      });
      o.on("editing:exited", () => {
        o.dsForceMobileEdit = false;
        if (isMobileViewport()) {
          o.set("editable", false);
          o.setCoords();
          canvas.requestRenderAll();
        }
      });
    }
  }

  function renderRotateHandleIcon(ctx, left, top, styleOverride, fabricObject) {
    try {
      const size = Math.max(14, Number((styleOverride && styleOverride.cornerSize) || (fabricObject && fabricObject.cornerSize) || 16));
      const r = size * 0.34;
      ctx.save();
      ctx.translate(left, top);
      ctx.rotate(fabric.util.degreesToRadians((fabricObject && fabricObject.angle) || 0));
      ctx.strokeStyle = "#18bfa1";
      ctx.lineWidth = 2;
      ctx.lineCap = "round";
      ctx.beginPath();
      ctx.arc(0, 0, r, Math.PI * 0.25, Math.PI * 1.55, false);
      ctx.stroke();
      // Arrow head
      const ax = r * Math.cos(Math.PI * 0.25);
      const ay = r * Math.sin(Math.PI * 0.25);
      ctx.beginPath();
      ctx.moveTo(ax, ay);
      ctx.lineTo(ax - 5, ay - 1);
      ctx.moveTo(ax, ay);
      ctx.lineTo(ax - 1, ay + 5);
      ctx.stroke();
      ctx.restore();
    } catch (e) {}
  }
  function getViewportHandleBasePx() {
    const vw = (typeof window !== "undefined") ? Math.max(1, Number(window.innerWidth) || 0) : 360;
    const vh = (typeof window !== "undefined") ? Math.max(1, Number(window.innerHeight) || 0) : 640;
    // 2% of screen's longer side, clamped for touch usability.
    return Math.max(12, Math.min(34, Math.round(Math.max(vw, vh) * 0.02)));
  }

  function getCanvasCssScale() {
    const fallback = Math.max(0.05, Number(state.fitScale || 1) * Number(state.zoom || 1));
    if (!wrapEl || typeof window === "undefined") return fallback;
    try {
      const cs = window.getComputedStyle(wrapEl);
      const tf = String((cs && cs.transform) || "");
      if (!tf || tf === "none") return fallback;
      const m2d = tf.match(/^matrix\(([^)]+)\)$/);
      if (m2d && m2d[1]) {
        const parts = m2d[1].split(",").map((x) => Number(String(x).trim()));
        if (parts.length >= 2 && isFinite(parts[0]) && isFinite(parts[1])) {
          const s = Math.sqrt(parts[0] * parts[0] + parts[1] * parts[1]);
          if (isFinite(s) && s > 0) return Math.max(0.05, s);
        }
      }
    } catch (e) {}
    return fallback;
  }

  function tuneObjectForTouch(o) {
    if (!isTouchDevice || !o || isSystemObject(o)) return;
    const baseCornerPx = getViewportHandleBasePx();
    const touchCornerPx = Math.max(baseCornerPx + 12, Math.min(84, Math.round(baseCornerPx * 2.2)));
    const basePaddingPx = Math.max(4, Math.min(14, Math.round(baseCornerPx * 0.32)));
    const cssScale = getCanvasCssScale();
    const rotateOffset = Math.max(14, Math.min(96, Math.round((baseCornerPx * 1.6) / cssScale)));
    o.set({
      hasControls: true,
      hasBorders: true,
      cornerSize: Math.max(12, Math.min(220, Math.round(baseCornerPx / cssScale))),
      touchCornerSize: Math.max(20, Math.min(280, Math.round(touchCornerPx / cssScale))),
      padding: Math.max(4, Math.min(72, Math.round(basePaddingPx / cssScale))),
      borderScaleFactor: Math.max(1.1, (2.2 / cssScale)),
      transparentCorners: false,
      cornerStyle: "rect",
      cornerColor: "#2adfbe",
      cornerStrokeColor: "#ffffff",
      borderColor: "#16c6a7"
    });
    if (o.controls && o.controls.mtr) {
      o.controls.mtr.render = renderRotateHandleIcon;
      o.controls.mtr.offsetY = -rotateOffset;
    }
    if (isTextObject(o)) {
      bindMobileTextEditingGuards(o);
    }
    if (isTextObject(o) && isMobileViewport()) {
      o.set({ editable: false });
    }
  }

  function syncActiveControlHandleSize() {
    if (!canvas) return;
    const active = canvas.getActiveObject();
    if (!active || isSystemObject(active)) return;
    const isMobileUi = !!(root && root.getAttribute && root.getAttribute("data-vp-mobile") === "1");

    // Desktop: keep selector handles fixed to 1% of viewport (screen), independent from canvas zoom.
    if (!isMobileUi) {
      const basePx = Math.max(10, Math.min(32, Math.round(Math.min(window.innerWidth || 0, window.innerHeight || 0) * 0.011)));
      const cssScale = Math.max(0.05, getCanvasCssScale());
      const cornerSize = Math.max(8, Math.min(220, Math.round(basePx / cssScale)));
      const touchCornerSize = Math.max(cornerSize + 6, Math.min(240, Math.round((basePx * 1.4) / cssScale)));
      const padding = Math.max(1, Math.min(48, Math.round((basePx * 0.18) / cssScale)));
      const rotateOffset = Math.max(12, Math.min(96, Math.round((basePx * 1.35) / cssScale)));
      const applyToDesktop = (obj) => {
        if (!obj || isSystemObject(obj)) return;
        obj.set({
          cornerSize: cornerSize,
          touchCornerSize: touchCornerSize,
          padding: padding,
          borderScaleFactor: 1.2,
          transparentCorners: false,
          cornerStyle: "rect"
        });
        if (obj.controls && obj.controls.mtr) {
          obj.controls.mtr.render = renderRotateHandleIcon;
          obj.controls.mtr.offsetY = -rotateOffset;
        }
        obj.setCoords();
      };
      if (active.type === "activeSelection" && typeof active.getObjects === "function") {
        active.getObjects().forEach((obj) => applyToDesktop(obj));
      }
      applyToDesktop(active);
      canvas.requestRenderAll();
      return;
    }

    if (!isTouchDevice) return;
    const baseCornerPx = getViewportHandleBasePx();
    const baseTouchCornerPx = Math.max(baseCornerPx + 10, Math.min(72, Math.round(baseCornerPx * 2.2)));
    const basePaddingPx = Math.max(4, Math.min(14, Math.round(baseCornerPx * 0.32)));
    const cssScale = getCanvasCssScale();
    const rotateOffset = Math.max(14, Math.min(96, Math.round((baseCornerPx * 1.6) / cssScale)));
    const targetCornerPx = baseCornerPx;
    const targetTouchCornerPx = baseTouchCornerPx;
    const targetPaddingPx = basePaddingPx;

    const cornerSize = Math.max(14, Math.min(140, Math.round(targetCornerPx / cssScale)));
    const touchCornerSize = Math.max(cornerSize + 8, Math.min(220, Math.round(targetTouchCornerPx / cssScale)));
    const padding = Math.max(4, Math.min(64, Math.round(targetPaddingPx / cssScale)));

    const applyTo = (obj) => {
      if (!obj || isSystemObject(obj)) return;
      obj.set({
        cornerSize: cornerSize,
        touchCornerSize: touchCornerSize,
        padding: padding,
        borderScaleFactor: Math.max(1.2, (2.2 / cssScale)),
        transparentCorners: false,
        cornerStyle: "rect"
      });
      if (obj.controls && obj.controls.mtr) obj.controls.mtr.render = renderRotateHandleIcon;
      if (obj.controls && obj.controls.mtr) obj.controls.mtr.offsetY = -rotateOffset;
      obj.setCoords();
    };

    if (active.type === "activeSelection" && typeof active.getObjects === "function") {
      active.getObjects().forEach((obj) => applyTo(obj));
    }
    applyTo(active);
    canvas.requestRenderAll();
  }

  function clearSelectionMarqueeArtifact() {
    try {
      if (canvas && canvas._groupSelector) {
        canvas._groupSelector = null;
      }
      if (canvas) canvas.requestRenderAll();
    } catch (e) {}
  }

  function syncTextEditabilityByViewport() {
    const allowInlineTextEditing = !isMobileViewport();
    getDesignObjects().forEach((obj) => {
      if (!isTextObject(obj) || isStickerObject(obj)) return;
      const shouldAllow = allowInlineTextEditing || !!obj.dsForceMobileEdit || !!obj.isEditing;
      if (!!obj.editable === shouldAllow) return;
      obj.set("editable", shouldAllow);
      obj.setCoords();
    });
  }
  function getDesignObjects() { return canvas.getObjects().filter((o) => !isSystemObject(o)); }
  function getSingleActive() {
    const a = canvas.getActiveObject();
    if (!a || a.type === "activeSelection" || isSystemObject(a)) return null;
    return a;
  }

  function designJSON() {
    return {
      version: fabric.version,
      objects: getDesignObjects().map((o) => o.toObject(serialProps))
    };
  }

  function serialize() { return JSON.stringify(designJSON()); }
  function syncUndoButtons() {
    const u = root.querySelector('[data-action="undo"]');
    const r = root.querySelector('[data-action="redo"]');
    if (u) u.disabled = history.undo.length <= 1;
    if (r) r.disabled = history.redo.length === 0;
  }
  function pushHistory(clearRedo) {
    if (restoring) return;
    const snap = serialize();
    if (snap === history.last) return;
    history.undo.push(snap);
    if (history.undo.length > history.max) history.undo.shift();
    history.last = snap;
    if (clearRedo !== false) history.redo = [];
    syncUndoButtons();
  }
  function resetHistory() {
    const snap = serialize();
    history.undo = [snap];
    history.redo = [];
    history.last = snap;
    syncUndoButtons();
  }

  function getZoneCanvasMm(zoneId) {
    const z = getZoneMeta(zoneId);
    const zoneW = z ? Number(z.width_mm || z.widthMm || z.width || z.w_mm || z.w || 0) : 0;
    const zoneH = z ? Number(z.height_mm || z.heightMm || z.height || z.h_mm || z.h || 0) : 0;
    let w = zoneW > 0 ? zoneW : (Number(cfg.width_mm) || 200);
    let h = zoneH > 0 ? zoneH : (Number(cfg.height_mm) || 100);
    if (z && z.allow_subcanvas) {
      const orient = getCurrentZoneOrientation(zoneId);
      if (orient === "landscape") {
        const lw = Number(z.landscape_width_mm || 0);
        const lh = Number(z.landscape_height_mm || 0);
        w = lw > 0 ? lw : w;
        h = lh > 0 ? lh : h;
      } else {
        const pw = Number(z.portrait_width_mm || 0);
        const ph = Number(z.portrait_height_mm || 0);
        w = pw > 0 ? pw : w;
        h = ph > 0 ? ph : h;
      }
    }
    return { w: w, h: h };
  }

  function applyCanvasPixelSizeForZone(zoneId) {
    const mm = getZoneCanvasMm(zoneId);
    const nextW = Math.max(1, mmToPx(mm.w, previewDpi));
    const nextH = Math.max(1, mmToPx(mm.h, previewDpi));
    widthPx = nextW;
    heightPx = nextH;
    if (canvasEl) {
      canvasEl.width = nextW;
      canvasEl.height = nextH;
    }
    try { canvas.setWidth(nextW); } catch (e) {}
    try { canvas.setHeight(nextH); } catch (e) {}
    try { canvas.calcOffset(); } catch (e) {}
    try {
      root.setAttribute("data-ds-zone-size", String(nextW) + "x" + String(nextH));
    } catch (e) {}
  }

  function createBlankZoneCanvas(zoneId) {
    const mm = getZoneCanvasMm(zoneId);
    const zoneW = Math.max(1, mmToPx(mm.w, previewDpi));
    const zoneH = Math.max(1, mmToPx(mm.h, previewDpi));
    const el = document.createElement("canvas");
    el.width = zoneW;
    el.height = zoneH;
    const ctx = el.getContext("2d");
    if (ctx) {
      ctx.fillStyle = "#ffffff";
      ctx.fillRect(0, 0, zoneW, zoneH);
    }
    return el;
  }

  function copyMainCanvasToZone(zoneId) {
    if (!zoneState || !zoneState.previewCanvases || !zoneState.previewCanvases[zoneId]) return;
    const zoneCanvas = zoneState.previewCanvases[zoneId];
    const ctx = zoneCanvas.getContext("2d");
    if (!ctx) return;
    ctx.clearRect(0, 0, zoneCanvas.width, zoneCanvas.height);
    ctx.fillStyle = "#ffffff";
    ctx.fillRect(0, 0, zoneCanvas.width, zoneCanvas.height);
    if (canvas.lowerCanvasEl) ctx.drawImage(canvas.lowerCanvasEl, 0, 0, zoneCanvas.width, zoneCanvas.height);
    if (zoneState.previewCanvasStamp) {
      zoneState.previewCanvasStamp[zoneId] = Number(zoneState.previewCanvasStamp[zoneId] || 0) + 1;
    }
    if (zoneState.previewTransparentCanvases) {
      delete zoneState.previewTransparentCanvases[zoneId];
    }
    refreshZonePickerAreaDesigns();
    updateZonePreviewUi();
  }

  function updateZoneToolbarUi() {
    if (!zoneState || !zoneState.enabled || !zoneState.buttonsWrap) return;
    zoneState.buttonsWrap.querySelectorAll("[data-zone-id]").forEach((btn) => {
      const zoneId = String(btn.getAttribute("data-zone-id") || "");
      btn.classList.toggle("is-active", zoneId === zoneState.currentId);
    });
    updateZonePreviewUi();
  }

  function updateOrientationToolbarUi() {
    if (!orientationState || !orientationState.wrap) return;
    const currentZoneId = zoneState && zoneState.currentId ? zoneState.currentId : "";
    const enabledForZone = zoneSupportsOrientation(currentZoneId);
    if (orientationState.block) {
      orientationState.block.style.display = enabledForZone ? "" : "none";
    }
    if (!enabledForZone) return;
    const mode = getCurrentZoneOrientation(currentZoneId);
    orientationState.wrap.querySelectorAll("[data-orient]").forEach((btn) => {
      const m = String(btn.getAttribute("data-orient") || "");
      btn.classList.toggle("is-active", m === mode);
    });
  }

  function setCurrentZoneOrientation(mode) {
    const currentZoneId = zoneState && zoneState.currentId ? zoneState.currentId : "";
    if (!zoneSupportsOrientation(currentZoneId)) return;
    const next = normalizeOrientation(mode);
    if (zoneState && zoneState.enabled && zoneState.currentId) {
      zoneState.orientations[zoneState.currentId] = next;
    } else {
      orientationState.mode = next;
    }
    applyCanvasPixelSizeForZone(zoneState && zoneState.currentId ? zoneState.currentId : "");
    fitCanvas(true);
    redrawSystem();
    canvas.requestRenderAll();
    markPreviewTexturesDirty();
    updateOrientationToolbarUi();
    setStatus(
      setZoneStatusPrefix() +
      tUi(
        "canvas_orientation",
        "Canvas orientation: %s",
        [next === "landscape" ? tUi("landscape", "Landscape") : tUi("portrait", "Portrait")]
      )
    );
  }

  function buildOrientationToolbar() {
    const toolbar = root.querySelector(".ds-toolbar");
    if (!toolbar) return;
    const block = document.createElement("div");
    block.className = "ds-toolblock ds-orientblock";
    const title = document.createElement("div");
    title.className = "ds-block-title";
    title.textContent = tUi("canvas", "Canvas");
    const group = document.createElement("div");
    group.className = "ds-group ds-orient-buttons";
    const mkBtn = (id, label) => {
      const b = document.createElement("button");
      b.type = "button";
      b.setAttribute("data-action", "set-orientation");
      b.setAttribute("data-orient", id);
      b.textContent = label;
      return b;
    };
    group.appendChild(mkBtn("portrait", tUi("portrait", "Portrait")));
    group.appendChild(mkBtn("landscape", tUi("landscape", "Landscape")));
    block.appendChild(title);
    block.appendChild(group);
    const anchor = toolbar.querySelector(".ds-zoneblock") || toolbar.querySelector(".ds-template-block");
    if (anchor && anchor.parentNode === toolbar) {
      if (anchor.nextSibling) toolbar.insertBefore(block, anchor.nextSibling);
      else toolbar.appendChild(block);
    } else {
      toolbar.insertBefore(block, toolbar.firstChild);
    }
    orientationState = { wrap: group, block: block, mode: "portrait" };
    group.addEventListener("click", (e) => {
      const t = e.target && e.target.closest ? e.target.closest("[data-orient]") : null;
      if (!t) return;
      const m = t.getAttribute("data-orient");
      if (!m) return;
      setCurrentZoneOrientation(m);
    });
    updateOrientationToolbarUi();
  }

  function readToolLabel(el) {
    if (!el) return "";
    if (el.tagName && el.tagName.toLowerCase() === "button") {
      return String(el.textContent || "").trim();
    }
    if (el.tagName && el.tagName.toLowerCase() === "label") {
      const parts = [];
      el.childNodes.forEach((n) => {
        if (n && n.nodeType === 3) {
          const t = String(n.textContent || "").trim();
          if (t) parts.push(t);
        }
      });
      return parts.join(" ").trim();
    }
    return String(el.textContent || "").trim();
  }

  function inferToolLabelFallback(el) {
    if (!el) return "Tool";
    const explicit =
      String(el.getAttribute("data-title") || "").trim() ||
      String(el.getAttribute("aria-label") || "").trim() ||
      String(el.getAttribute("title") || "").trim();
    if (explicit) return explicit;
    const action = String(el.getAttribute("data-action") || "").trim();
    const shape = String(el.getAttribute("data-shape") || "").trim();
    const align = String(el.getAttribute("data-align") || "").trim();
    const orient = String(el.getAttribute("data-orient") || "").trim();
    const raw = action || shape || align || orient;
    if (!raw) return "Tool";
    return raw
      .replace(/[_-]+/g, " ")
      .replace(/\b\w/g, function (m) { return m.toUpperCase(); });
  }

    function getToolMeta(el) {
    const action = String(el.getAttribute("data-action") || "").toLowerCase();
    const shape = String(el.getAttribute("data-shape") || "").toLowerCase();
    const align = String(el.getAttribute("data-align") || "").toLowerCase();
    const orient = String(el.getAttribute("data-orient") || "").toLowerCase();
    const uploadInput = el.querySelector ? el.querySelector("input[data-action]") : null;
    const uploadAction = uploadInput ? String(uploadInput.getAttribute("data-action") || "").toLowerCase() : "";

    const iconSvg = function (paths) {
      return (
        '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' +
        paths +
        '</svg>'
      );
    };

    const icons = {
      text: iconSvg('<path d="M4 6h16"/><path d="M12 6v12"/><path d="M8 18h8"/>'),
      image: iconSvg('<rect x="3" y="4" width="18" height="16" rx="2"/><circle cx="9" cy="10" r="1.5"/><path d="M21 16l-5-5-7 7"/>'),
      svg: iconSvg('<path d="M12 3l8 5v8l-8 5-8-5V8z"/>'),
      undo: iconSvg('<path d="M9 14L4 9l5-5"/><path d="M20 20a8 8 0 0 0-11-11L4 9"/>'),
      redo: iconSvg('<path d="M15 14l5-5-5-5"/><path d="M4 20a8 8 0 0 1 11-11l5 0"/>'),
      copy: iconSvg('<rect x="9" y="9" width="11" height="11" rx="2"/><rect x="4" y="4" width="11" height="11" rx="2"/>'),
      crop: iconSvg('<path d="M6 2v14a2 2 0 0 0 2 2h14"/><path d="M18 22V8a2 2 0 0 0-2-2H2"/>'),
      cut: iconSvg('<circle cx="6" cy="6" r="2"/><circle cx="6" cy="18" r="2"/><path d="M20 4L8.5 15.5"/><path d="M8.5 8.5L20 20"/>'),
      x: iconSvg('<path d="M18 6L6 18"/><path d="M6 6l12 12"/>'),
      settings: iconSvg('<circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.2a1.7 1.7 0 0 0-1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.2a1.7 1.7 0 0 0 1.5-1 1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3h.1a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.2a1.7 1.7 0 0 0 1 1.5h.1a1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8v.1a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.2a1.7 1.7 0 0 0-1.5 1z"/>'),
      mask: iconSvg('<circle cx="12" cy="12" r="8"/><path d="M12 4a8 8 0 0 0 0 16z"/>'),
      group: iconSvg('<rect x="3" y="5" width="8" height="8" rx="1"/><rect x="13" y="11" width="8" height="8" rx="1"/>'),
      ungroup: iconSvg('<rect x="3" y="5" width="8" height="8" rx="1"/><rect x="13" y="11" width="8" height="8" rx="1"/><path d="M11 11h2"/>'),
      up: iconSvg('<path d="M12 19V5"/><path d="M6 11l6-6 6 6"/>'),
      down: iconSvg('<path d="M12 5v14"/><path d="M18 13l-6 6-6-6"/>'),
      fit: iconSvg('<path d="M8 3H3v5"/><path d="M16 3h5v5"/><path d="M21 16v5h-5"/><path d="M3 16v5h5"/>'),
      zoom: iconSvg('<circle cx="11" cy="11" r="6"/><path d="M21 21l-4.3-4.3"/>'),
      moon: iconSvg('<path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/>'),
      export: iconSvg('<path d="M12 3v12"/><path d="M7 10l5 5 5-5"/><path d="M5 21h14"/>'),
      exportPdf: iconSvg('<path d="M6 2h9l5 5v15H6z"/><path d="M15 2v5h5"/><path d="M9 16h6"/><path d="M9 12h3"/>'),
      save: iconSvg('<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><path d="M17 21v-8H7v8"/><path d="M7 3v5h8"/>'),
      savePdf: iconSvg('<path d="M6 2h9l5 5v15H6z"/><path d="M15 2v5h5"/><path d="M8.5 14.5h7"/><path d="M8.5 11.5h5"/><path d="M9 18h6"/>'),
      load: iconSvg('<path d="M12 21V9"/><path d="M17 14l-5-5-5 5"/><path d="M5 3h14"/>'),
      list: iconSvg('<path d="M8 6h13"/><path d="M8 12h13"/><path d="M8 18h13"/><circle cx="3.5" cy="6" r="1"/><circle cx="3.5" cy="12" r="1"/><circle cx="3.5" cy="18" r="1"/>'),
      check: iconSvg('<path d="M20 6L9 17l-5-5"/>'),
      layerFront: iconSvg('<path d="M8 12l4-4 4 4"/><path d="M12 8v11"/><rect x="5" y="3" width="14" height="4" rx="1"/>'),
      layerBack: iconSvg('<path d="M8 15l4 4 4-4"/><path d="M12 5v14"/><rect x="5" y="17" width="14" height="4" rx="1"/>'),
      alignLeft: iconSvg('<path d="M4 6h16"/><path d="M4 12h10"/><path d="M4 18h16"/>'),
      alignCenter: iconSvg('<path d="M4 6h16"/><path d="M7 12h10"/><path d="M4 18h16"/>'),
      alignRight: iconSvg('<path d="M4 6h16"/><path d="M10 12h10"/><path d="M4 18h16"/>'),
      alignTop: iconSvg('<path d="M6 4v16"/><path d="M12 4v10"/><path d="M18 4v16"/>'),
      alignMiddle: iconSvg('<path d="M6 4v16"/><path d="M12 7v10"/><path d="M18 4v16"/>'),
      alignBottom: iconSvg('<path d="M6 4v16"/><path d="M12 10v10"/><path d="M18 4v16"/>'),
      rect: iconSvg('<rect x="4" y="6" width="16" height="12" rx="1"/>'),
      circle: iconSvg('<circle cx="12" cy="12" r="8"/>'),
      triangle: iconSvg('<path d="M12 5l8 14H4z"/>'),
      star: iconSvg('<path d="M12 3l2.9 6 6.6.9-4.8 4.7 1.2 6.6L12 18l-5.9 3.2 1.2-6.6L2.5 9.9l6.6-.9z"/>'),
      heart: iconSvg('<path d="M20.8 8.6a5.5 5.5 0 0 0-7.8 0L12 9.6l-1-1a5.5 5.5 0 0 0-7.8 7.8l1 1L12 22l7.8-4.6 1-1a5.5 5.5 0 0 0 0-7.8z"/>'),
      polygon: iconSvg('<path d="M12 3l8 5v8l-8 5-8-5V8z"/>')
      ,
      texture: iconSvg('<rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M3 15h18M9 3v18M15 3v18"/>'),
      frame: iconSvg('<rect x="3" y="3" width="18" height="18" rx="2"/><rect x="7" y="7" width="10" height="10" rx="1"/>')
    };

    const map = {
      "add-text": { short: "T", label: tUi("text", "Text"), symbol: "T", svg: icons.text },
      "upload-image": { short: "I", label: tUi("image", "Image"), symbol: "I", svg: icons.image },
      "upload-svg": { short: "S", label: "SVG", symbol: "S", svg: icons.svg },
      "open-textures": { short: "T", label: tUi("textures_library", "Textures"), symbol: "TX", svg: icons.texture },
      "open-frames": { short: "F", label: tUi("frames_library", "Frames"), symbol: "FR", svg: icons.frame },
      "undo": { short: "U", label: tUi("undo", "Undo"), symbol: "U", svg: icons.undo },
      "redo": { short: "R", label: tUi("redo", "Redo"), symbol: "R", svg: icons.redo },
      "duplicate-object": { short: "D", label: tUi("duplicate", "Duplicate"), symbol: "D", svg: icons.copy },
      "crop-image": { short: "C", label: tUi("crop", "Crop"), symbol: "C", svg: icons.crop },
      "crop-apply": { short: "A", label: tUi("apply_crop", "Apply Crop"), symbol: "A", svg: icons.check },
      "ai-remove-bg": { short: "A", label: tUi("ai_background_remove", "AI Background Remove"), symbol: "BG", svg: icons.cut },
      "crop-cancel": { short: "X", label: tUi("crop_cancel", "Crop Cancel"), symbol: "X", svg: icons.x },
      "delete-object": { short: "X", label: tUi("delete", "Delete"), symbol: "X", svg: icons.x },
      "object-props": { short: "O", label: tUi("object", "Object"), symbol: "O", svg: icons.settings },
      "mask": { short: "M", label: tUi("mask", "Mask"), symbol: "M", svg: icons.mask },
      "mask-bake": { short: "B", label: tUi("mask_bake", "Mask Bake"), symbol: "B", svg: icons.mask },
      "group": { short: "G", label: tUi("group", "Group"), symbol: "G", svg: icons.group },
      "ungroup": { short: "U", label: tUi("ungroup", "Ungroup"), symbol: "U", svg: icons.ungroup },
      "layer-up": { short: "U", label: tUi("layer_up", "Layer Up"), symbol: "U", svg: icons.up },
      "layer-down": { short: "D", label: tUi("layer_down", "Layer Down"), symbol: "D", svg: icons.down },
      "layer-front": { short: "F", label: tUi("bring_front", "Bring Front"), symbol: "F", svg: icons.layerFront },
      "layer-back": { short: "B", label: tUi("send_back", "Send Back"), symbol: "B", svg: icons.layerBack },
      "fit-canvas": { short: "F", label: tUi("fit_canvas", "Fit Canvas"), symbol: "F", svg: icons.fit },
      "zoom-reset": { short: "1", label: tUi("zoom_reset_100", "100% Zoom"), symbol: "100", svg: icons.zoom },
      "toggle-fullscreen": { short: "F", label: tUi("fullscreen", "Fullscreen"), symbol: "F", svg: icons.fit },
      "export": { short: "P", label: tUi("export_png", "Export PNG"), symbol: "P", svg: icons.export },
      "save": { short: "S", label: tUi("save_zone", "Save Zone"), symbol: "S", svg: icons.save },
      "final-save": { short: "F", label: tUi("final_save", "Final Save"), symbol: "F", svg: icons.check },
      "apply-product": { short: "P", label: tUi("apply_to_product", "Apply to Product"), symbol: "AP", svg: icons.check },
      "load-design": { short: "L", label: tUi("load", "Load"), symbol: "L", svg: icons.load },
      "my-designs": { short: "M", label: tUi("my_designs", "My Designs"), symbol: "M", svg: icons.list },
      "template-apply": { short: "A", label: tUi("apply_template", "Apply Template"), symbol: "A", svg: icons.check },
      "mug-open": { short: "3", label: tUi("preview_3d", "3D Preview"), symbol: "3D", svg: icons.fit },
      "open-zones": { short: "Z", label: tUi("zone_tools", "Zone Tools"), symbol: "Z", svg: icons.list },
      "mug-close": { short: "X", label: tUi("close_preview", "Close Preview"), symbol: "X", svg: icons.x },
      "mug-reset": { short: "R", label: tUi("reset_view", "Reset View"), symbol: "R", svg: icons.redo },
      "close-object-props": { short: "X", label: tUi("close", "Close"), symbol: "X", svg: icons.x },
      "close-my-designs": { short: "X", label: tUi("close", "Close"), symbol: "X", svg: icons.x },
      "close-asset-library": { short: "X", label: tUi("close", "Close"), symbol: "X", svg: icons.x }
    };

    if (action && map[action]) return { short: map[action].short, label: map[action].label, symbol: map[action].symbol, svg: map[action].svg, fallback: false };
    if (uploadAction && map[uploadAction]) return { short: map[uploadAction].short, label: map[uploadAction].label, symbol: map[uploadAction].symbol, svg: map[uploadAction].svg, fallback: false };

    if (shape) {
      const shapeMap = {
        rect: { short: "R", svg: icons.rect },
        circle: { short: "C", svg: icons.circle },
        triangle: { short: "T", svg: icons.triangle },
        star: { short: "S", svg: icons.star },
        heart: { short: "H", svg: icons.heart },
        polygon: { short: "P", svg: icons.polygon }
      };
      const item = shapeMap[shape] || { short: shape.charAt(0).toUpperCase(), svg: "" };
      return { short: item.short, label: shape.charAt(0).toUpperCase() + shape.slice(1), symbol: item.short, svg: item.svg, fallback: false };
    }
    if (align) {
      const alignMap = {
        left: { short: "L", label: tUi("align_left", "Align Left"), svg: icons.alignLeft },
        center: { short: "C", label: tUi("align_center", "Align Center"), svg: icons.alignCenter },
        right: { short: "R", label: tUi("align_right", "Align Right"), svg: icons.alignRight },
        top: { short: "T", label: tUi("align_top", "Align Top"), svg: icons.alignTop },
        middle: { short: "M", label: tUi("align_middle", "Align Middle"), svg: icons.alignMiddle },
        bottom: { short: "B", label: tUi("align_bottom", "Align Bottom"), svg: icons.alignBottom }
      };
      const a = alignMap[align] || { short: "A", label: tUi("align", "Align"), svg: "" };
      return { short: a.short, label: a.label, symbol: a.short, svg: a.svg, fallback: false };
    }
    if (orient) {
      return { short: orient === "landscape" ? "L" : "P", label: orient === "landscape" ? tUi("landscape", "Landscape") : tUi("portrait", "Portrait"), symbol: orient === "landscape" ? "L" : "P", svg: "", fallback: true };
    }

    const label = readToolLabel(el);
    const short = label ? String(label).trim().charAt(0).toUpperCase() : "*";
    return { short: short || "*", label: label || tUi("tool", "Tool"), symbol: short || "*", svg: "", fallback: true };
  }

  function decorateIconButtons(scope) {
    if (!scope || !scope.querySelectorAll) return;
    const nodes = scope.querySelectorAll("button, .ds-upload");
    nodes.forEach((el) => {
      if (!el || !el.classList) return;
      const inMobileBottomNav = !!(el.closest && el.closest('[data-role="mobile-bottom-nav"]'));
      const inMobileSheets = !!(el.closest && el.closest('[data-role="mobile-sheets"]'));
      const isPortraitMobileViewport = !!(typeof window !== "undefined" && window.matchMedia && window.matchMedia("(max-width: 1023px) and (orientation: portrait)").matches);
      // Portrait mobile uses dedicated CSS icon rendering for nav + sheet actions.
      // Skip JS icon decoration there to avoid duplicate icons.
      if (inMobileBottomNav || (inMobileSheets && isPortraitMobileViewport)) {
        el.classList.remove("ds-icon-only", "has-svg", "ds-inline-svg-mode", "ds-icon-fallback", "ds-needs-login");
        try { el.style.removeProperty("--ds-icon-svg"); } catch (e) {}
        const inline = el.querySelector && el.querySelector(".ds-inline-svg");
        if (inline && inline.parentNode) inline.parentNode.removeChild(inline);
        return;
      }
      const meta = getToolMeta(el);
      let label = meta && meta.label ? String(meta.label) : readToolLabel(el);
      if (!label) label = inferToolLabelFallback(el);
      const short = meta && meta.short ? String(meta.short).slice(0, 1).toUpperCase() : "T";
      const symbol = meta && meta.symbol ? String(meta.symbol) : short;
      const svg = meta && meta.svg ? String(meta.svg) : "";
      el.setAttribute("data-title", label || tUi("tool", "Tool"));
      if (!el.getAttribute("aria-label")) el.setAttribute("aria-label", label || tUi("tool", "Tool"));
      el.setAttribute("data-short", short || "T");
      el.setAttribute("data-symbol", symbol || short || "T");
      if (el.querySelector && el.querySelector(".ds-zone-ic")) {
        el.classList.add("ds-icon-zone");
        return;
      }
      el.classList.add("ds-icon-only");
      if (svg) {
        const svgUrl = 'url("data:image/svg+xml,' + encodeURIComponent(svg) + '")';
        el.style.setProperty("--ds-icon-svg", svgUrl);
        el.classList.add("has-svg");
        if (isModernUi()) {
          let inline = el.querySelector(".ds-inline-svg");
          if (!inline) {
            inline = document.createElement("span");
            inline.className = "ds-inline-svg";
            el.appendChild(inline);
          }
          inline.innerHTML = svg;
          el.classList.add("ds-inline-svg-mode");
        }
      } else {
        el.style.removeProperty("--ds-icon-svg");
        el.classList.remove("has-svg");
        const inline = el.querySelector(".ds-inline-svg");
        if (inline && inline.parentNode) inline.parentNode.removeChild(inline);
        el.classList.remove("ds-inline-svg-mode");
      }
      if ((meta && meta.fallback) || !svg) el.classList.add("ds-icon-fallback");
      else el.classList.remove("ds-icon-fallback");
    });
  }

  function uiIconSvg(paths) {
    return (
      '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' +
      paths +
      "</svg>"
    );
  }

  function getUiIconByName(name) {
    const icons = {
      "arrow-left": uiIconSvg('<path d="M19 12H5"/><path d="m12 19-7-7 7-7"/>'),
      "undo-2": uiIconSvg('<path d="M9 14 4 9l5-5"/><path d="M20 20a8 8 0 0 0-11-11L4 9"/>'),
      "redo-2": uiIconSvg('<path d="m15 14 5-5-5-5"/><path d="M4 20a8 8 0 0 1 11-11h5"/>'),
      "rotate-cw": uiIconSvg('<path d="M23 4v6h-6"/><path d="M20.5 15a9 9 0 1 1-2.1-9.4L23 10"/>'),
      "save": uiIconSvg('<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><path d="M17 21v-8H7v8"/><path d="M7 3v5h8"/>'),
      "package-check": uiIconSvg('<path d="m16 16 2 2 4-4"/><path d="M21 8a2 2 0 0 0-1-1.73L13 2.27a2 2 0 0 0-2 0L4 6.27A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l3-1.71"/><path d="M3.3 7 12 12l8.7-5"/><path d="M12 22V12"/>'),
      "image": uiIconSvg('<rect x="3" y="4" width="18" height="16" rx="2"/><circle cx="9" cy="10" r="1.5"/><path d="M21 16l-5-5-7 7"/>'),
      "type": uiIconSvg('<path d="M4 6h16"/><path d="M12 6v12"/><path d="M8 18h8"/>'),
      "file-image": uiIconSvg('<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/><circle cx="10" cy="13" r="2"/><path d="m8 20 3.5-3.5 2.5 2.5 2-2"/>'),
      "shapes": uiIconSvg('<circle cx="7.5" cy="7.5" r="3.5"/><rect x="13" y="4" width="7" height="7" rx="1.5"/><path d="m7 20 4-7h-8z"/>'),
      "sparkles": uiIconSvg('<path d="M12 3l1.4 3.6L17 8l-3.6 1.4L12 13l-1.4-3.6L7 8l3.6-1.4z"/><path d="M19 14l.9 2.1L22 17l-2.1.9L19 20l-.9-2.1L16 17l2.1-.9z"/><path d="M5 14l.8 1.8L8 16.6l-2.2.8L5 20l-.8-2.6L2 16.6l2.2-.8z"/>'),
      "square-stack": uiIconSvg('<rect x="3" y="3" width="14" height="14" rx="2"/><rect x="7" y="7" width="14" height="14" rx="2"/>'),
      "grid-2x2": uiIconSvg('<rect x="3" y="3" width="8" height="8" rx="1"/><rect x="13" y="3" width="8" height="8" rx="1"/><rect x="3" y="13" width="8" height="8" rx="1"/><rect x="13" y="13" width="8" height="8" rx="1"/>'),
      "smile": uiIconSvg('<circle cx="12" cy="12" r="9"/><path d="M8 10h.01"/><path d="M16 10h.01"/><path d="M8 15a6 6 0 0 0 8 0"/>'),
      "maximize": uiIconSvg('<path d="M8 3H3v5"/><path d="M16 3h5v5"/><path d="M21 16v5h-5"/><path d="M3 16v5h5"/>'),
      "search": uiIconSvg('<circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/>'),
      "grid-3x3": uiIconSvg('<path d="M3 3h18v18H3z"/><path d="M9 3v18M15 3v18M3 9h18M3 15h18"/>'),
      "magnet": uiIconSvg('<path d="M6 8v6a6 6 0 0 0 12 0V8"/><path d="M6 8h4"/><path d="M14 8h4"/><path d="M6 4v4"/><path d="M18 4v4"/>'),
      "scan-line": uiIconSvg('<path d="M4 7V5a2 2 0 0 1 2-2h2"/><path d="M16 3h2a2 2 0 0 1 2 2v2"/><path d="M20 17v2a2 2 0 0 1-2 2h-2"/><path d="M8 21H6a2 2 0 0 1-2-2v-2"/><path d="M4 12h16"/>'),
      "moon-star": uiIconSvg('<path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/><path d="M18 2v4"/><path d="M16 4h4"/>'),
      "expand": uiIconSvg('<path d="m15 3 6 0 0 6"/><path d="M21 3 14 10"/><path d="m9 21-6 0 0-6"/><path d="M3 21 10 14"/>'),
      "download": uiIconSvg('<path d="M12 3v12"/><path d="m7 10 5 5 5-5"/><path d="M5 21h14"/>'),
      "badge-check": uiIconSvg('<path d="m9 12 2 2 4-4"/><path d="M12 3l2.2 2.2 3.1-.5.5 3.1L20 10l-2.2 2.2.5 3.1-3.1.5L12 18l-2.2-2.2-3.1.5-.5-3.1L4 10l2.2-2.2-.5-3.1 3.1-.5z"/>'),
      "folder-heart": uiIconSvg('<path d="M3 6a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v1H3z"/><path d="M3 9h20v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><path d="M12 17s-2-1.2-2-2.6c0-1 .8-1.7 1.7-1.7.6 0 1.1.3 1.3.8.3-.5.8-.8 1.4-.8.9 0 1.7.8 1.7 1.7 0 1.4-2.1 2.6-2.1 2.6z"/>'),
      "cloud-download": uiIconSvg('<path d="M20 17.5A4.5 4.5 0 0 0 18 9a6 6 0 0 0-11.5 1.8A4 4 0 0 0 7 18h10"/><path d="M12 12v8"/><path d="m8.5 16.5 3.5 3.5 3.5-3.5"/>'),
      "layout-template": uiIconSvg('<rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18"/><path d="M9 9v12"/>')
    };
    return icons[name] || "";
  }

  function applyUnifiedUiIcons(scope) {
    const host = scope && scope.querySelectorAll ? scope : root;
    if (!host || !host.querySelectorAll) return;
    const byAction = {
      "add-text": "type",
      "upload-image": "image",
      "upload-svg": "file-image",
      "open-shapes": "shapes",
      "open-icons": "sparkles",
      "open-frames": "square-stack",
      "open-textures": "grid-2x2",
      "open-emoticons": "smile",
      "fit-canvas": "maximize",
      "zoom-reset": "search",
      "toggle-fullscreen": "expand",
      "export": "download",
      "save": "save",
      "final-save": "badge-check",
      "apply-product": "package-check",
      "my-designs": "folder-heart",
      "load-design": "cloud-download",
      "template-apply": "layout-template"
    };
    const byMobileAction = {
      "undo": "undo-2",
      "hard-refresh": "rotate-cw",
      "redo": "redo-2",
      "final-save": "save",
      "apply": "package-check",
      "insert": "sparkles",
      "canvas": "maximize",
      "library": "folder-heart",
      "preview": "search",
      "open-zones": "layout-template"
    };
    const byMobileSheetAction = {
      "exit-flow": "arrow-left",
      "mug-open": "search",
      "upload-image": "image",
      "add-text": "type",
      "upload-svg": "file-image",
      "open-shapes": "shapes",
      "open-icons": "sparkles",
      "open-frames": "square-stack",
      "open-textures": "grid-2x2",
      "open-emoticons": "smile",
      "fit-canvas": "maximize",
      "zoom-reset": "search",
      "toggle-grid": "grid-3x3",
      "toggle-snap": "magnet",
      "toggle-bleed-marks": "scan-line",
      "toggle-fullscreen": "expand",
      "toggle-theme": "moon-star",
      "set-theme-dark": "moon-star",
      "set-theme-light": "moon-star",
      "open-arrange": "layout-template",
      "my-designs": "folder-heart",
      "load-design": "cloud-download",
      "template-apply": "layout-template",
      "open-zones": "layout-template",
      "save": "save",
      "final-save": "badge-check",
      "apply": "package-check"
    };

    host.querySelectorAll("button, a, .ds-upload").forEach((el) => {
      if (!el || !el.classList || el.classList.contains("ds-mobile-icon-tile")) return;
      if (el.classList.contains("ds-dc-zone-preview-btn")) {
        try {
          el.classList.remove("ds-ui-iconized");
          el.querySelectorAll(".ds-ui-icon, .dashicons, .ds-inline-svg").forEach((n) => {
            if (n && n.parentNode) n.parentNode.removeChild(n);
          });
        } catch (e) {}
        return;
      }
      let iconName = "";
      const action = String(el.getAttribute("data-action") || "").toLowerCase();
      const mobileAction = String(el.getAttribute("data-mobile-action") || "").toLowerCase();
      const sheetAction = String(el.getAttribute("data-mobile-sheet-action") || "").toLowerCase();
      if (action && byAction[action]) iconName = byAction[action];
      else if (mobileAction && byMobileAction[mobileAction]) iconName = byMobileAction[mobileAction];
      else if (sheetAction && byMobileSheetAction[sheetAction]) iconName = byMobileSheetAction[sheetAction];
      else if (el.classList.contains("ds-topbar-back") || el.classList.contains("ds-mobile-topbar-back")) iconName = "arrow-left";
      else if (el.classList.contains("ds-mobile-apply-cta")) iconName = "package-check";
      if (!iconName) return;
      const svg = getUiIconByName(iconName);
      if (!svg) return;

      let iconHost = el.querySelector(".ds-mobile-btn-ic");
      if (!iconHost) {
        iconHost = el.querySelector(".ds-desktop-sheet-nav-ic");
      }
      if (iconHost) {
        iconHost.classList.add("ds-ui-icon");
        iconHost.innerHTML = svg;
      } else {
        let iconEl = el.querySelector(".ds-ui-icon");
        if (!iconEl) {
          iconEl = document.createElement("span");
          iconEl.className = "ds-ui-icon";
          el.insertBefore(iconEl, el.firstChild);
        }
        iconEl.innerHTML = svg;
      }
      // Prevent double icons (legacy dashicons + injected ui-icon / duplicate ui-icons).
      try {
        const allUiIcons = Array.from(el.querySelectorAll(":scope > .ds-ui-icon, :scope > .ds-mobile-btn-ic.ds-ui-icon, :scope > .ds-desktop-sheet-nav-ic.ds-ui-icon"));
        if (allUiIcons.length > 1) {
          allUiIcons.slice(1).forEach((n) => { if (n && n.parentNode) n.parentNode.removeChild(n); });
        }
        // If we have modern ui-icon, remove legacy dashicons in the same button/link.
        if (el.querySelector(".ds-ui-icon")) {
          el.querySelectorAll(".dashicons").forEach((d) => {
            if (d && d.parentNode) d.parentNode.removeChild(d);
          });
        }
      } catch (e) {}
      el.classList.add("ds-ui-iconized");
    });
  }
  function dockToolbarBlocksToSides() {
    const toolbar = root.querySelector(".ds-toolbar");
    const topWrap = root.querySelector('[data-role="top-tools"]');
    const leftWrap = root.querySelector('[data-role="left-tools"]');
    const rightWrap = root.querySelector('[data-role="right-tools"]');
    if (!toolbar || !topWrap || !leftWrap || !rightWrap) return;
    const blocks = Array.from(toolbar.querySelectorAll(":scope > .ds-toolblock"));
    if (!blocks.length) return;
    const topTitles = { arrange: true, template: true };
    const rightTitles = { zones: true, view: true, output: true };
    const isMobile = typeof window !== "undefined" &&
      window.matchMedia &&
      window.matchMedia("(max-width: 1023px)").matches;
    const isModernLandscape = isModernUi() && isLandscapeMobileViewport();
    const isMobileDock = isMobile && !isModernLandscape;
    function normalizeTopBlock(block) {
      if (!block || !block.querySelectorAll) return;
      const groups = Array.from(block.querySelectorAll(":scope > .ds-group"));
      if (groups.length <= 1) return;
      const merged = document.createElement("div");
      merged.className = "ds-group";
      groups.forEach((g) => {
        while (g.firstChild) merged.appendChild(g.firstChild);
        if (g.parentNode) g.parentNode.removeChild(g);
      });
      block.appendChild(merged);
    }
    blocks.forEach((block) => {
      const titleEl = block.querySelector(".ds-block-title");
      const title = titleEl ? String(titleEl.textContent || "").trim().toLowerCase() : "";
      if (isMobileDock) {
        rightWrap.appendChild(block);
      } else if (topTitles[title]) {
        normalizeTopBlock(block);
        topWrap.appendChild(block);
      }
      else if (rightTitles[title]) rightWrap.appendChild(block);
      else leftWrap.appendChild(block);
    });
    initModernSidebarLayout();
    decorateIconButtons(topWrap);
    decorateIconButtons(leftWrap);
    decorateIconButtons(rightWrap);
    applyUnifiedUiIcons(root);
    toolbar.style.display = "none";
    initCollapsibleToolblocks();
    initMobileToolTabs();
    updateFullscreenOffsets();
  }

  function isModernUi() {
    if (!root) return false;
    return root.classList.contains("ds-ui-modern") || String(root.getAttribute("data-ds-ui-variant") || "") === "modern";
  }

  function isModernRightPanelLeft() {
    if (!root) return false;
    return root.classList.contains("ds-modern-right-left");
  }

  function initModernSidebarLayout() {
    if (!isModernUi()) return;
    if (isMobileViewport() && !isLandscapeMobileViewport()) return;
    const topWrap = root.querySelector('[data-role="top-tools"]');
    const leftWrap = root.querySelector('[data-role="left-tools"]');
    const rightWrap = root.querySelector('[data-role="right-tools"]');
    const leftAside = root.querySelector(".ds-canvas-side-left");
    if (!topWrap || !leftWrap || !leftAside || !rightWrap) return;

    const blocks = [];
    Array.from(leftWrap.querySelectorAll(":scope > .ds-toolblock")).forEach((b) => blocks.push(b));
    Array.from(topWrap.querySelectorAll(":scope > .ds-toolblock")).forEach((b) => blocks.push(b));
    const leftMode = isModernRightPanelLeft();
    if (leftMode) {
      const rightBlocks = Array.from(rightWrap.querySelectorAll(":scope > .ds-toolblock"));
      const zonesHostClass = "ds-modern-zones-top";
      let zonesHost = leftAside.querySelector("." + zonesHostClass);
      if (!zonesHost) {
        zonesHost = document.createElement("div");
        zonesHost.className = zonesHostClass;
      }
      rightBlocks.forEach((block) => {
        const title = String(block.querySelector(".ds-block-title")?.textContent || "").trim().toLowerCase();
        const looksLikeView =
          title === "view" ||
          !!block.querySelector('[data-action="fit-canvas"], [data-action="zoom-reset"], [data-action="toggle-fullscreen"]');
        if (looksLikeView) {
          blocks.push(block);
        } else if (title === "zones" || title === "preview") {
          zonesHost.appendChild(block);
        } else {
          blocks.push(block);
        }
      });
      if (zonesHost.childElementCount > 0) {
        leftAside.insertBefore(zonesHost, leftAside.firstChild);
      }
    }
    if (!blocks.length) return;

    const order = { template: 10, insert: 20, edit: 30, arrange: 40, view: 45, output: 50 };
    blocks.sort((a, b) => {
      const ta = String(a.querySelector(".ds-block-title")?.textContent || "").trim().toLowerCase();
      const tb = String(b.querySelector(".ds-block-title")?.textContent || "").trim().toLowerCase();
      const oa = Object.prototype.hasOwnProperty.call(order, ta) ? order[ta] : 999;
      const ob = Object.prototype.hasOwnProperty.call(order, tb) ? order[tb] : 999;
      if (oa !== ob) return oa - ob;
      return ta.localeCompare(tb);
    });

    let shell = leftAside.querySelector(".ds-modern-shell");
    if (!shell) {
      shell = document.createElement("div");
      shell.className = "ds-modern-shell";
      const rail = document.createElement("div");
      rail.className = "ds-modern-rail";
      const stage = document.createElement("div");
      stage.className = "ds-modern-stage";
      shell.appendChild(rail);
      shell.appendChild(stage);
      leftAside.appendChild(shell);
    }
    const rail = shell.querySelector(".ds-modern-rail");
    const stage = shell.querySelector(".ds-modern-stage");
    if (!rail || !stage) return;

    leftWrap.style.display = "none";
    topWrap.style.display = "none";

    blocks.forEach((block) => stage.appendChild(block));
    rail.innerHTML = "";
    root.classList.remove("ds-modern-collapsed");
    try { window.sessionStorage.removeItem("ds_modern_collapsed"); } catch (e) {}
    decorateIconButtons(stage);

    function setActiveBlock(id) {
      Array.from(stage.querySelectorAll(":scope > .ds-toolblock")).forEach((b) => {
        const bid = String(b.getAttribute("data-modern-block-id") || "");
        b.classList.toggle("is-modern-active", bid === id);
        if (bid === id) {
          decorateIconButtons(b);
        }
      });
      Array.from(rail.querySelectorAll(".ds-modern-tab")).forEach((t) => {
        const on = String(t.getAttribute("data-modern-tab-id") || "") === id;
        t.classList.toggle("is-active", on);
        t.setAttribute("aria-selected", on ? "true" : "false");
      });
    }

    const tabs = [];
    Array.from(stage.querySelectorAll(":scope > .ds-toolblock")).forEach((block, idx) => {
      const title = String(block.querySelector(".ds-block-title")?.textContent || "").trim() || ("Group " + (idx + 1));
      const key = title.toLowerCase();
      const iconMap = {
        template: "dashicons-layout",
        insert: "dashicons-plus-alt2",
        edit: "dashicons-edit",
        arrange: "dashicons-screenoptions",
        output: "dashicons-download",
        zones: "dashicons-location-alt",
        view: "dashicons-visibility"
      };
      const iconClass = iconMap[key] || "dashicons-admin-generic";
      const id = "modern_block_" + idx + "_" + title.toLowerCase().replace(/[^a-z0-9]+/g, "_");
      block.setAttribute("data-modern-block-id", id);
      const tab = document.createElement("button");
      tab.type = "button";
      tab.className = "ds-modern-tab";
      tab.setAttribute("data-modern-tab-id", id);
      tab.setAttribute("data-title", title);
      tab.setAttribute("aria-label", title);
      tab.setAttribute("role", "tab");
      tab.innerHTML =
        '<span class="ds-modern-tab-ic dashicons ' + iconClass + '" aria-hidden="true"></span>' +
        '<span class="ds-modern-tab-tx">' + title + "</span>";
      tab.addEventListener("click", () => setActiveBlock(id));
      rail.appendChild(tab);
      tabs.push(id);
    });

    if (tabs.length) setActiveBlock(tabs[0]);
    // Ensure icons are decorated after layout settles (covers dynamic/template re-renders).
    if (typeof window !== "undefined" && typeof window.requestAnimationFrame === "function") {
      window.requestAnimationFrame(() => {
        decorateIconButtons(stage);
        const right = root.querySelector('[data-role="right-tools"]');
        if (right) decorateIconButtons(right);
      });
    } else {
      decorateIconButtons(stage);
      const right = root.querySelector('[data-role="right-tools"]');
      if (right) decorateIconButtons(right);
    }
  }

  function ensureObjectQuickButton() {
    if (!wrapEl) return null;
    if (state.objectQuickDock && state.objectQuickDock.parentNode === wrapEl) return state.objectQuickDock;
    const dock = document.createElement("div");
    dock.className = "ds-object-quick-dock";

    const mkBtn = (label, action) => {
      const b = document.createElement("button");
      b.type = "button";
      b.className = "ds-object-quick-handle";
      b.setAttribute("data-quick-action", action);
      b.setAttribute("aria-label", label);
      b.setAttribute("title", label);
      const iconClass = action === "duplicate"
        ? "dashicons-admin-page"
        : "dashicons-trash";
      b.innerHTML = '<span class="dashicons ' + iconClass + '" aria-hidden="true"></span>';
      return b;
    };

    dock.appendChild(mkBtn("Duplicate", "duplicate"));
    dock.appendChild(mkBtn("Delete", "delete"));

    dock.addEventListener("click", (e) => {
      const btn = e.target && e.target.closest ? e.target.closest("[data-quick-action]") : null;
      if (!btn) return;
      const action = String(btn.getAttribute("data-quick-action") || "");
      if (!action) return;

      if (action === "duplicate") {
        if (!duplicateActiveObject() && typeof clickRealControl === "function") {
          clickRealControl('.ds-toolbar [data-action="duplicate-object"]');
        }
        return;
      }
      if (action === "delete") {
        const ok = deleteActiveObject();
        if (ok) pushHistory();
        else if (typeof clickRealControl === "function") clickRealControl('.ds-toolbar [data-action="delete-object"]');
        return;
      }
    });

    wrapEl.appendChild(dock);
    state.objectQuickDock = dock;
    return dock;
  }

  function updateObjectQuickButton() {
    const dock = ensureObjectQuickButton();
    if (!dock || !wrapEl || !canvas || !canvas.lowerCanvasEl) return;
    const obj = getSingleActive();
    const mode = String((root && root.getAttribute) ? (root.getAttribute("data-ds-mode") || "") : "").toLowerCase();
    const allowQuickDock = isMobilePortraitUi() || mode === "desktop";
    if (!obj || obj.isEditing || !allowQuickDock) {
      dock.classList.remove("is-visible");
      return;
    }

    const b = obj.getBoundingRect(true, true);
    const lower = canvas.lowerCanvasEl;
    const wrapRect = wrapEl.getBoundingClientRect();
    const lowerRect = lower.getBoundingClientRect();
    const scaleX = (canvas.getWidth() > 0) ? (lowerRect.width / canvas.getWidth()) : 1;
    const scaleY = (canvas.getHeight() > 0) ? (lowerRect.height / canvas.getHeight()) : 1;

    let left = (lowerRect.left - wrapRect.left) + ((b.left + b.width) * scaleX) + 8;
    let top = (lowerRect.top - wrapRect.top) + (b.top * scaleY) - 28;

    const minLeft = 6;
    const dockW = dock.offsetWidth || 240;
    const maxLeft = Math.max(minLeft, wrapEl.clientWidth - dockW - 6);
    const minTop = 6;
    const maxTop = Math.max(minTop, wrapEl.clientHeight - 48);
    left = Math.max(minLeft, Math.min(maxLeft, left));
    top = Math.max(minTop, Math.min(maxTop, top));

    dock.style.left = Math.round(left) + "px";
    dock.style.top = Math.round(top) + "px";
    dock.classList.add("is-visible");
  }

  function placePreviewControl() {
    if (!mugMiniGroupEl) return;
    const canShow3D = !!enablePreview3D;
    const rightWrap = root.querySelector('[data-role="right-tools"]');
    const leftAside = root.querySelector(".ds-canvas-side-left");
    const zonesTop = leftAside ? leftAside.querySelector(".ds-modern-zones-top") : null;
    const useLeftHost = isModernUi() && isModernRightPanelLeft() && !isMobileViewport() && !!zonesTop;
    const hostWrap = useLeftHost ? zonesTop : rightWrap;
    if (hostWrap) {
      let previewBlock = root.querySelector(".ds-previewblock");
      function ensurePreviewToggle(block) {
        if (!block) return;
        let title = block.querySelector(".ds-block-title");
        if (!title) {
          title = document.createElement("div");
          title.className = "ds-block-title";
          title.textContent = tUi("preview", "Preview");
          block.insertBefore(title, block.firstChild || null);
        }
        let toggle = block.querySelector(".ds-preview-collapse-btn");
        if (!toggle) {
          toggle = document.createElement("button");
          toggle.type = "button";
          toggle.className = "ds-preview-collapse-btn";
          toggle.setAttribute("aria-label", tUi("collapse_preview", "Collapse preview"));
          toggle.setAttribute("title", tUi("collapse_preview", "Collapse preview"));
          toggle.textContent = "▴";
          title.appendChild(toggle);
          toggle.addEventListener("click", (e) => {
            e.preventDefault();
            e.stopPropagation();
            const next = !block.classList.contains("is-collapsed");
            block.classList.toggle("is-collapsed", next);
            toggle.textContent = next ? "▾" : "▴";
            toggle.setAttribute("aria-label", next ? tUi("expand_preview", "Expand preview") : tUi("collapse_preview", "Collapse preview"));
            toggle.setAttribute("title", next ? tUi("expand_preview", "Expand preview") : tUi("collapse_preview", "Collapse preview"));
            try { window.sessionStorage.setItem("ds_preview_collapsed", next ? "1" : "0"); } catch (err) {}
          });
        }
        let collapsed = false;
        try { collapsed = window.sessionStorage.getItem("ds_preview_collapsed") === "1"; } catch (err) {}
        block.classList.toggle("is-collapsed", collapsed);
        toggle.textContent = collapsed ? "▾" : "▴";
        toggle.setAttribute("aria-label", collapsed ? tUi("expand_preview", "Expand preview") : tUi("collapse_preview", "Collapse preview"));
        toggle.setAttribute("title", collapsed ? tUi("expand_preview", "Expand preview") : tUi("collapse_preview", "Collapse preview"));
      }
      if (!canShow3D) {
        mugMiniGroupEl.style.display = "none";
        if (previewBlock && previewBlock.parentNode) previewBlock.parentNode.removeChild(previewBlock);
        initMobileToolTabs();
        return;
      }
      if (!previewBlock) {
        previewBlock = document.createElement("div");
        previewBlock.className = "ds-toolblock ds-previewblock";
        const title = document.createElement("div");
        title.className = "ds-block-title";
        title.textContent = tUi("preview", "Preview");
        previewBlock.appendChild(title);
      } else if (previewBlock.parentNode !== hostWrap) {
        hostWrap.appendChild(previewBlock);
      }
      ensurePreviewToggle(previewBlock);
      mugMiniGroupEl.classList.add("ds-zone-preview-3d");
      if (mugOpenBtn) mugOpenBtn.textContent = tUi("preview", "Preview");
      previewBlock.appendChild(mugMiniGroupEl);
      mugMiniGroupEl.style.display = "";
      if (hostWrap.firstChild !== previewBlock) {
        hostWrap.insertBefore(previewBlock, hostWrap.firstChild);
      }
      initMobileToolTabs();
      return;
    }
    mugMiniGroupEl.style.display = "none";
  }

  function updateFullscreenOffsets() {
    try {
      const topWrap = root.querySelector('[data-role="top-tools"]');
      const h = topWrap ? Math.max(0, Math.ceil(topWrap.getBoundingClientRect().height || 0)) : 0;
      root.style.setProperty("--ds-top-tools-h", String(h) + "px");
      let adminH = 0;
      const admin = document.getElementById("wpadminbar");
      if (admin) {
        const cs = window.getComputedStyle ? window.getComputedStyle(admin) : null;
        const visible = !cs || (cs.display !== "none" && cs.visibility !== "hidden");
        if (visible) {
          const r = admin.getBoundingClientRect ? admin.getBoundingClientRect() : { height: 0 };
          adminH = Math.max(0, Math.ceil(r.height || 0));
        }
      }
      root.style.setProperty("--ds-adminbar-h", String(adminH) + "px");
    } catch (e) {}
  }

  function isMobileViewport() {
    if (typeof window === "undefined" || !window.matchMedia) return false;
    return window.matchMedia("(max-width: 1023px)").matches ||
      window.matchMedia("(orientation: landscape) and (pointer: coarse)").matches;
  }

  function isLandscapeMobileViewport() {
    return typeof window !== "undefined" &&
      window.matchMedia &&
      (
        window.matchMedia("(max-width: 1023px) and (orientation: landscape)").matches ||
        window.matchMedia("(orientation: landscape) and (pointer: coarse)").matches
      );
  }

  function isClassicDesktopViewport() {
    if (typeof window === "undefined" || !window.matchMedia) return false;
    const coarseLandscape = window.matchMedia("(orientation: landscape) and (pointer: coarse)").matches;
    if (coarseLandscape) return false;
    const wideDesktop = window.matchMedia("(min-width: 1024px)").matches;
    if (isModernUi()) return false;
    return wideDesktop;
  }

  function resolveResponsiveMode() {
    if (isLandscapeMobileViewport()) return "landscape";
    if (isMobileViewport()) return "portrait";
    return "desktop";
  }

  function applyViewportModeFlags() {
    if (!root) return;
    const mobile = isMobileViewport();
    const landscape = isLandscapeMobileViewport();
    const mode = resolveResponsiveMode();
    const fullscreen = root.classList.contains("ds-fullscreen");
    root.setAttribute("data-ds-mode", mode);
    root.classList.toggle("ds-mode-portrait", mode === "portrait");
    root.classList.toggle("ds-mode-landscape", mode === "landscape");
    root.classList.toggle("ds-mode-desktop", mode === "desktop");
    root.setAttribute("data-vp-mobile", mobile ? "1" : "0");
    root.setAttribute("data-vp-orient", landscape ? "landscape" : "portrait");
    root.setAttribute("data-vp-fullscreen", fullscreen ? "1" : "0");
    if (!mobile && !isClassicDesktopViewport()) {
      root.classList.remove("ds-mobile-more-open");
      root.classList.remove("ds-mobile-sheet-open");
      root.removeAttribute("data-mobile-sheet-open");
    }
    syncTextEditabilityByViewport();
  }

  function setFullscreenToggleButtonState(active) {
    const btn = root.querySelector('[data-action="toggle-fullscreen"]');
    if (!btn) return;
    btn.setAttribute("data-title", active ? tUi("exit_fullscreen", "Exit Fullscreen") : tUi("fullscreen", "Fullscreen"));
    btn.setAttribute("aria-label", active ? tUi("exit_fullscreen", "Exit Fullscreen") : tUi("fullscreen", "Fullscreen"));
  }

  function syncFullscreenUiState(active) {
    root.classList.toggle("ds-fullscreen", !!active);
    try {
      if (active) window.sessionStorage.setItem("ds_editor_fullscreen", "1");
      else window.sessionStorage.removeItem("ds_editor_fullscreen");
    } catch (e) {}
    refreshLayoutUi(false);
    setFullscreenToggleButtonState(!!active);
  }

  function refreshLayoutUi(forceFit) {
  try { applyViewportModeFlags(); } catch (e) {}
  try { initModernSidebarLayout(); } catch (e) {}

  try {
    if (!(isMobileViewport() && !isLandscapeMobileViewport())) {
      initMobileToolTabs();
    } else {
      const topWrap = root.querySelector('[data-role="top-tools"]');
      if (topWrap) {
        topWrap.classList.remove("ds-mobile-tabs-mode");
        topWrap.querySelectorAll(".ds-mobile-tool-tabs").forEach((el) => {
          if (el && el.parentNode) {
            el.parentNode.removeChild(el);
          }
        });
      }
    }
  } catch (e) {}

  try { updateFullscreenOffsets(); } catch (e) {}
  try { syncMobileEmptyStateOverlay(); } catch (e) {}
  try { updatePortraitCanvasRowHeight(); } catch (e) {}
  try { fitCanvas(!!forceFit); } catch (e) {}
  try { redrawSystem(); } catch (e) {}

  if (miniPreview && typeof miniPreview.resize === "function") miniPreview.resize();
  if (largePreview && typeof largePreview.resize === "function") largePreview.resize();
}

  function setActiveMobileToolTab(tabId) {
    const topWrap = root.querySelector('[data-role="top-tools"]');
    const rightWrap = root.querySelector('[data-role="right-tools"]');
    if (!topWrap || !rightWrap) return;
    const id = String(tabId || "");
    const blocks = Array.from(rightWrap.querySelectorAll(":scope > .ds-toolblock"));
    blocks.forEach((block) => {
      const bid = String(block.getAttribute("data-mobile-tab-id") || "");
      if (bid === id) block.style.setProperty("display", "flex", "important");
      else block.style.setProperty("display", "none", "important");
    });
    const tabs = topWrap.querySelector(".ds-mobile-tool-tabs");
    if (tabs) {
      Array.from(tabs.querySelectorAll(".ds-mobile-tool-tab")).forEach((btn) => {
        const on = String(btn.getAttribute("data-tab-id") || "") === id;
        btn.classList.toggle("is-active", on);
        btn.setAttribute("aria-selected", on ? "true" : "false");
      });
    }
    state.mobileActiveToolTab = id;
  }

  function initMobileToolTabs() {
    const topWrap = root.querySelector('[data-role="top-tools"]');
    const rightWrap = root.querySelector('[data-role="right-tools"]');
    if (!topWrap || !rightWrap) return;
    const existingTabs = Array.from(root.querySelectorAll(".ds-mobile-tool-tabs"));
    let tabs = topWrap.querySelector(".ds-mobile-tool-tabs") || (existingTabs.length ? existingTabs[0] : null);
    if (tabs && tabs.parentNode !== topWrap) {
      topWrap.appendChild(tabs);
    }
    if (existingTabs.length > 1) {
      existingTabs.forEach((el) => {
        if (el !== tabs && el && el.parentNode) el.parentNode.removeChild(el);
      });
    }
    const isTouchLandscape = typeof window !== "undefined" &&
      window.matchMedia &&
      window.matchMedia("(orientation: landscape) and (pointer: coarse)").matches;
    const isFsLandscapeMobile = root.classList.contains("ds-fullscreen") && (isLandscapeMobileViewport() || isTouchLandscape);
    const mobileLike = isMobileViewport() || isFsLandscapeMobile;

    if (!mobileLike) {
      topWrap.classList.remove("ds-mobile-tabs-mode");
      root.querySelectorAll(".ds-mobile-tool-tabs").forEach((el) => {
        if (el && el.parentNode) el.parentNode.removeChild(el);
      });
      const blocks = Array.from(rightWrap.querySelectorAll(":scope > .ds-toolblock"));
      blocks.forEach((block) => block.style.removeProperty("display"));
      state.mobileActiveToolTab = "";
      return;
    }

    // Mobile contract: top area = group selectors only, commands live below canvas.
    Array.from(topWrap.querySelectorAll(":scope > .ds-toolblock")).forEach((block) => {
      rightWrap.appendChild(block);
    });

    const blocks = Array.from(rightWrap.querySelectorAll(":scope > .ds-toolblock"));
    if (!blocks.length) return;

    if (isFsLandscapeMobile) {
      topWrap.classList.remove("ds-mobile-tabs-mode");
      root.querySelectorAll(".ds-mobile-tool-tabs").forEach((el) => {
        if (el && el.parentNode) el.parentNode.removeChild(el);
      });
      blocks.forEach((block) => block.style.setProperty("display", "flex", "important"));
      state.mobileActiveToolTab = "";
      return;
    }

    topWrap.classList.add("ds-mobile-tabs-mode");
    if (!tabs) {
      tabs = document.createElement("div");
      tabs.className = "ds-mobile-tool-tabs";
      tabs.setAttribute("role", "tablist");
      topWrap.appendChild(tabs);
    }
    if (!tabs.hasAttribute("data-delegated")) {
      const pointerState = { x: 0, y: 0, t: 0, active: false };
      const activateFromEvent = (ev) => {
        const target = ev && ev.target ? ev.target : null;
        if (!target || !target.closest) return;
        const btn = target.closest(".ds-mobile-tool-tab");
        if (!btn || !tabs.contains(btn)) return;
        const tabId = String(btn.getAttribute("data-tab-id") || "");
        if (!tabId) return;
        setActiveMobileToolTab(tabId);
      };
      tabs.addEventListener("pointerdown", (ev) => {
        if (!ev) return;
        pointerState.x = Number(ev.clientX || 0);
        pointerState.y = Number(ev.clientY || 0);
        pointerState.t = Date.now();
        pointerState.active = true;
      }, { passive: true, capture: true });
      tabs.addEventListener("pointercancel", () => {
        pointerState.active = false;
      }, { passive: true, capture: true });
      tabs.addEventListener("pointerup", (ev) => {
        if (!pointerState.active) return;
        pointerState.active = false;
        const dx = Math.abs(Number(ev && ev.clientX || 0) - pointerState.x);
        const dy = Math.abs(Number(ev && ev.clientY || 0) - pointerState.y);
        const dt = Date.now() - pointerState.t;
        // Ignore drag/scroll gestures; activate only true taps.
        if (dx > 10 || dy > 10 || dt > 600) return;
        activateFromEvent(ev);
      }, { passive: true, capture: true });
      tabs.addEventListener("click", activateFromEvent, true);
      tabs.addEventListener("keydown", (ev) => {
        if (!ev) return;
        if (ev.key === "Enter" || ev.key === " ") {
          ev.preventDefault();
          activateFromEvent(ev);
        }
      }, true);
      tabs.setAttribute("data-delegated", "1");
    }

    tabs.innerHTML = "";
    const tabIconByLabel = {
      template: "dashicons-layout",
      insert: "dashicons-plus-alt2",
      edit: "dashicons-edit",
      arrange: "dashicons-screenoptions",
      output: "dashicons-download",
      view: "dashicons-visibility",
      zones: "dashicons-location-alt",
      preview: "dashicons-format-image"
    };
    const tabOrder = {
      template: 10,
      insert: 20,
      edit: 30,
      arrange: 40,
      output: 50,
      view: 60,
      zones: 70,
      preview: 80
    };
    const sortedBlocks = blocks.slice().sort((a, b) => {
      const la = String(a.querySelector(":scope > .ds-block-title")?.textContent || "").trim().toLowerCase();
      const lb = String(b.querySelector(":scope > .ds-block-title")?.textContent || "").trim().toLowerCase();
      const oa = Object.prototype.hasOwnProperty.call(tabOrder, la) ? tabOrder[la] : 999;
      const ob = Object.prototype.hasOwnProperty.call(tabOrder, lb) ? tabOrder[lb] : 999;
      if (oa !== ob) return oa - ob;
      return la.localeCompare(lb);
    });

    sortedBlocks.forEach((block, idx) => {
      const t = block.querySelector(":scope > .ds-block-title");
      const label = t ? String(t.textContent || "").trim() : ("Group " + (idx + 1));
      const key = label.toLowerCase();
      const iconClass = tabIconByLabel[key] || "dashicons-admin-generic";
      const id = block.getAttribute("data-mobile-tab-id") || ("mtool_" + idx);
      block.setAttribute("data-mobile-tab-id", id);

      const btn = document.createElement("button");
      btn.type = "button";
      btn.className = "ds-mobile-tool-tab";
      btn.setAttribute("role", "tab");
      btn.setAttribute("data-tab-id", id);
      btn.setAttribute("aria-label", label);
      btn.style.touchAction = "pan-x";
      btn.innerHTML =
        '<span class="ds-mobile-tool-tab-ic dashicons ' + iconClass + '" aria-hidden="true"></span>' +
        '<span class="ds-mobile-tool-tab-tx">' + label + "</span>";
      tabs.appendChild(btn);
    });

    const allIds = sortedBlocks.map((b) => String(b.getAttribute("data-mobile-tab-id") || "")).filter(Boolean);
    const activeId = allIds.includes(state.mobileActiveToolTab) ? state.mobileActiveToolTab : allIds[0];
    if (activeId) setActiveMobileToolTab(activeId);
    else blocks.forEach((block) => { block.style.display = "flex"; });
  }

  function initCollapsibleToolblocks() {
    const leftWrap = root.querySelector('[data-role="left-tools"]');
    if (!leftWrap) return;
    const blocks = Array.from(leftWrap.querySelectorAll(":scope > .ds-toolblock"));
    blocks.forEach((block) => {
      if (!block || block.classList.contains("ds-collapsible-ready")) return;
      const title = block.querySelector(":scope > .ds-block-title");
      if (!title) return;
      let content = block.querySelector(":scope > .ds-block-content");
      if (!content) {
        content = document.createElement("div");
        content.className = "ds-block-content";
        const moveNodes = [];
        Array.from(block.children).forEach((child) => {
          if (child === title) return;
          moveNodes.push(child);
        });
        moveNodes.forEach((n) => content.appendChild(n));
        block.appendChild(content);
      }
      block.classList.add("ds-collapsible-ready", "is-collapsed");
      title.setAttribute("role", "button");
      title.setAttribute("tabindex", "0");
      title.setAttribute("aria-expanded", "false");
      const toggle = () => {
        const willOpen = block.classList.contains("is-collapsed");
        block.classList.toggle("is-collapsed", !willOpen);
        title.setAttribute("aria-expanded", willOpen ? "true" : "false");
      };
      title.addEventListener("click", toggle);
      title.addEventListener("keydown", (e) => {
        if (!e) return;
        if (e.key === "Enter" || e.key === " ") {
          e.preventDefault();
          toggle();
        }
      });
    });
  }

  function placementOverlaps(side, x, y, w, h, excludeId) {
    if (!zoneState || !zoneState.placements) return false;
    const margin = 0.006;
    const ids = Object.keys(zoneState.placements);
    for (let i = 0; i < ids.length; i++) {
      const pid = ids[i];
      if (excludeId && String(pid) === String(excludeId)) continue;
      const p = zoneState.placements[pid];
      if (!p || String(p.side) !== String(side)) continue;
      const ax1 = Number(p.x || 0), ay1 = Number(p.y || 0), ax2 = ax1 + Number(p.w || 0), ay2 = ay1 + Number(p.h || 0);
      const bx1 = x, by1 = y, bx2 = x + w, by2 = y + h;
      if (ax1 - margin < bx2 && ax2 + margin > bx1 && ay1 - margin < by2 && ay2 + margin > by1) return true;
    }
    return false;
  }

  function findPlacementSpot(side, wRel, hRel) {
    const body = getTshirtBodyBounds(side);
    const maxX = body.x + body.w - wRel;
    const maxY = body.y + body.h - hRel;
    if (maxX < body.x || maxY < body.y) return null;
    const step = 0.012;
    for (let y = body.y; y <= maxY + 0.0001; y += step) {
      for (let x = body.x; x <= maxX + 0.0001; x += step) {
        if (placementOverlaps(side, x, y, wRel, hRel)) continue;
        if (!rectInsideContour(side, x, y, wRel, hRel)) continue;
        return { x: x, y: y };
      }
    }
    return null;
  }

  function addPlacementZone(side) {
    if (!zoneState || !zoneState.enabled) return;
    const s = String(side || "front");
    const promptW = window.prompt(tUi("print_width_prompt", "Print width in mm (max A4)"), "210");
    if (promptW == null) return;
    const promptH = window.prompt(tUi("print_height_prompt", "Print height in mm (max A4)"), "297");
    if (promptH == null) return;
    let mmW = Number(promptW);
    let mmH = Number(promptH);
    if (!(mmW > 0) || !(mmH > 0)) {
      setStatus("Invalid size. Use numeric mm values.");
      return;
    }
    const maxA = Math.max(mmW, mmH);
    const minA = Math.min(mmW, mmH);
    if (maxA > 297 || minA > 210) {
      setStatus("Max print size is A4 (210x297 mm).");
      return;
    }
    const g = getGarmentMmForSide(s);
    const body = getTshirtBodyBounds(s);
    const wRel = Math.max(0.04, Math.min(body.w, body.w * (mmW / Math.max(1, g.w))));
    const hRel = Math.max(0.04, Math.min(body.h, body.h * (mmH / Math.max(1, g.h))));
    const spot = findPlacementSpot(s, wRel, hRel);
    if (!spot) {
      setStatus("No free space for this print area without overlap.");
      return;
    }
    const id = s + "_p_" + (placementCounter++);
    const label = (s === "back" ? "Back" : "Front") + " #" + String(placementCounter - 1);
    if (!zoneState.placements) zoneState.placements = {};
    zoneState.placements[id] = { id: id, side: s, x: spot.x, y: spot.y, w: wRel, h: hRel, width_mm: mmW, height_mm: mmH, label: label };
    zoneState.zones.push({ id: id, label: label });
    zoneState.snapshots[id] = { version: fabric.version, objects: [] };
    zoneState.orientations[id] = (mmW > mmH) ? "landscape" : "portrait";
    zoneState.previewCanvases[id] = createBlankZoneCanvas(id);
    if (zoneState.previewCanvasStamp) zoneState.previewCanvasStamp[id] = 0;
    if (zoneState.previewTransparentCanvases) delete zoneState.previewTransparentCanvases[id];
    if (typeof zoneState.renderZoneButtons === "function") zoneState.renderZoneButtons();
    switchToZone(id);
    setStatus(tUi("added_with_size", "Added %s (%sx%s mm).", [label, mmW, mmH]));
  }

  function pickZoneMockup(zoneId) {
    const mockups = getTemplateMockups();
    if (!mockups.length) return null;
    const side = zoneSide(zoneId);
    for (let i = 0; i < mockups.length; i++) {
      if (String(mockups[i].side || "") === side) return mockups[i];
    }
    for (let j = 0; j < mockups.length; j++) {
      if (String(mockups[j].side || "") === "front") return mockups[j];
    }
    return mockups[0];
  }

  function updateZonePreviewUi() {
    if (!zoneState || !zoneState.previewUi) return;
    const ui = zoneState.previewUi;
    const currentZone = (zoneState.zones || []).find((z) => z.id === zoneState.currentId) || null;
    const chosen = pickZoneMockup(zoneState.currentId);
    if (!chosen || !chosen.image_url) {
      ui.wrap.style.display = "none";
      return;
    }
    ui.wrap.style.display = "";
    const selectedIds = getSelectedZoneIds();
    const areas = Array.isArray(chosen.areas) ? chosen.areas : [];
    const zoneById = {};
    (zoneState.zones || []).forEach((z) => {
      const zid = String((z && z.id) || "");
      if (!zid) return;
      zoneById[zid] = z;
    });
    const renderComposedThumb = (baseImg) => {
      if (!baseImg) {
        ui.img.src = chosen.image_url;
        return;
      }
      const bw = Math.max(1, Number(baseImg.naturalWidth || baseImg.width || 1));
      const bh = Math.max(1, Number(baseImg.naturalHeight || baseImg.height || 1));
      const targetW = 360;
      const tw = targetW;
      const th = Math.max(1, Math.round((targetW * bh) / bw));
      const stage = document.createElement("canvas");
      stage.width = tw;
      stage.height = th;
      const sctx = stage.getContext("2d");
      if (!sctx) {
        ui.img.src = chosen.image_url;
        return;
      }
      sctx.clearRect(0, 0, tw, th);
      sctx.drawImage(baseImg, 0, 0, tw, th);
      areas.forEach((a) => {
        const zid = resolveAreaZoneId(a, zoneById, null, chosen.side) || String((a && a.id) || "");
        if (!zid || !selectedIds.includes(zid)) return;
        const zc = getZonePreviewCanvasForDisplay(zid);
        if (!zc) return;
        const zx = Math.max(0, Math.min(1, Number((a && a.x_pct) || 0)));
        const zy = Math.max(0, Math.min(1, Number((a && a.y_pct) || 0)));
        const zw = Math.max(0.01, Math.min(1, Number((a && a.w_pct) || 0.25)));
        const zh = Math.max(0.01, Math.min(1, Number((a && a.h_pct) || 0.25)));
        const rx = Math.round(zx * tw);
        const ry = Math.round(zy * th);
        const rw = Math.max(1, Math.round(zw * tw));
        const rh = Math.max(1, Math.round(zh * th));
        sctx.drawImage(zc, rx, ry, rw, rh);
      });
      try {
        ui.img.src = stage.toDataURL("image/png");
      } catch (e) {
        ui.img.src = chosen.image_url;
      }
    };
    if (!ui._thumbImageCache) ui._thumbImageCache = {};
    const cacheKey = String(chosen.image_url || "");
    const cached = ui._thumbImageCache[cacheKey];
    if (cached && cached.complete) {
      renderComposedThumb(cached);
    } else {
      const im = new Image();
      im.onload = function() {
        ui._thumbImageCache[cacheKey] = im;
        renderComposedThumb(im);
      };
      im.onerror = function() {
        ui.img.src = chosen.image_url;
      };
      im.src = chosen.image_url;
    }
    ui.img.alt = chosen.name || tUi("zone_preview_alt", "Zone preview");
    ui.meta.textContent = (currentZone ? currentZone.label : tUi("zone_label_single", "Zone")) + " | " + (chosen.name || tUi("mockup_single", "Mockup"));
  }

  function getZonesGroupedByMockup() {
    if (!zoneState || !Array.isArray(zoneState.zones)) return [];
    const zones = zoneState.zones.slice();
    const zoneById = {};
    zones.forEach((z) => { zoneById[String(z.id || "")] = z; });
    const seenAny = {};
    const groups = [];
    const mockups = getTemplateMockups();
    mockups.forEach((m, idx) => {
      const groupZones = [];
      const groupSeen = {};
      const areas = Array.isArray(m.areas) ? m.areas : [];
      areas.forEach((a) => {
        const id = resolveAreaZoneId(a, zoneById, groupSeen, m.side) || String((a && a.id) || "");
        if (!id || !zoneById[id] || groupSeen[id]) return;
        groupSeen[id] = true;
        seenAny[id] = true;
        groupZones.push({
          id: id,
          label: String((a && a.label) || (zoneById[id].label || id)),
          x_pct: Number((a && a.x_pct) || 0),
          y_pct: Number((a && a.y_pct) || 0),
          w_pct: Number((a && a.w_pct) || 0),
          h_pct: Number((a && a.h_pct) || 0),
          width_mm: Number((a && a.width_mm) || 0),
          height_mm: Number((a && a.height_mm) || 0)
        });
      });
      if (groupZones.length) {
        groups.push({
          key: "m_" + idx,
        title: m.name || tUi("mockup_numbered", "Mockup %s", [idx + 1]),
          image_url: m.image_url || "",
          zones: groupZones
        });
      }
    });
    const rest = zones.filter((z) => !seenAny[String(z.id || "")]);
    if (rest.length) {
      groups.push({
        key: "other",
        title: tUi("other_areas", "Other Areas"),
        image_url: "",
        zones: rest
      });
    }
    return groups;
  }

  function buildZonePickerModal() {
    if (!zoneState) return null;
    if (zoneState.zonePickerModal && zoneState.zonePickerModal.wrap) return zoneState.zonePickerModal;
    const wrap = document.createElement("div");
    wrap.className = "ds-zone-picker-modal";
    wrap.setAttribute("data-role", "zone-picker-modal");
    wrap.setAttribute("aria-hidden", "true");
    const dialog = document.createElement("div");
    dialog.className = "ds-zone-picker-dialog";
    dialog.setAttribute("role", "dialog");
    dialog.setAttribute("aria-modal", "true");
    dialog.setAttribute("aria-label", tUi("choose_print_areas", "Choose print areas"));
    dialog.innerHTML = ''
      + '<div class="ds-zone-picker-top">'
      + '  <h3>' + escHtml(tUi("step3_choose_print_areas", "Step 3: Choose Print Areas")) + '</h3>'
      + '  <button type="button" class="ds-woo-btn" data-action="zone-picker-close">' + escHtml(tUi("close", "Close")) + '</button>'
      + '</div>'
      + '<div class="ds-zone-picker-help">' + escHtml(tUi("zone_picker_help", "Select the print areas you want to use for this template. Close returns to Step 2.")) + '</div>'
      + '<div class="ds-zone-picker-note" data-role="zone-picker-note" style="display:none"></div>'
      + '<div class="ds-zone-picker-list" data-role="zone-picker-list"></div>'
      + '<div class="ds-zone-picker-actions">'
      + '  <button type="button" class="ds-woo-btn ds-woo-btn-primary" data-action="zone-picker-apply">' + escHtml(tUi("apply_selection", "Apply Selection")) + '</button>'
      + '</div>';
    wrap.appendChild(dialog);
    root.appendChild(wrap);
    const list = dialog.querySelector('[data-role="zone-picker-list"]');
    zoneState.zonePickerModal = { wrap: wrap, dialog: dialog, list: list };
    wrap.addEventListener("click", (e) => {
      if (e.target === wrap) closeZonePickerModal();
    });
    dialog.addEventListener("click", (e) => {
      const t = e.target && e.target.closest ? e.target.closest("[data-action]") : null;
      if (t) {
        const action = t.getAttribute("data-action");
        if (action === "zone-picker-close") {
          if ((startupFlowEnabled && startupStep === "zones") || requiresInitialZoneSelection()) {
            redirectToStartupTemplateStep();
          } else {
            closeZonePickerModal();
          }
        }
        if (action === "zone-picker-apply") applyZonePickerSelection();
        return;
      }
      const area = e.target && e.target.closest ? e.target.closest(".ds-zone-picker-area[data-zone-id]") : null;
      if (!area || !zoneState || !zoneState.zonePickerModal || !zoneState.zonePickerModal.list) return;
      const zid = String(area.getAttribute("data-zone-id") || "");
      if (!zid) return;
      let cb = null;
      zoneState.zonePickerModal.list.querySelectorAll('input[type="checkbox"][data-zone-id]').forEach((el) => {
        if (cb) return;
        if (String(el.getAttribute("data-zone-id") || "") === zid) cb = el;
      });
      if (!cb) return;
      cb.checked = !cb.checked;
      try { cb.dispatchEvent(new Event("change", { bubbles: true })); } catch (err) {}
    });
    dialog.addEventListener("change", (e) => {
      const t = e.target;
      if (!t || !t.matches || !t.matches('input[type="checkbox"][data-zone-id]')) return;
      setZonePickerNote("");
      const id = String(t.getAttribute("data-zone-id") || "");
      if (!id) return;
      setZonePickerCheckedState(id, !!t.checked);
      const conflict = getZoneConflictMap();
      if (t.checked && id && conflict[id]) {
        let blockedBy = "";
        dialog.querySelectorAll('input[type="checkbox"][data-zone-id]:checked').forEach((other) => {
          const oid = String(other.getAttribute("data-zone-id") || "");
          if (!oid || oid === id) return;
          if (conflict[id] && conflict[id][oid]) blockedBy = oid;
        });
        if (blockedBy) {
          setZonePickerCheckedState(id, false);
          const a = getZoneLabel(id);
          const b = getZoneLabel(blockedBy);
          const msg = tUi("zone_conflict_pair", "%s and %s cannot be used together (conflict rule).", [a, b]);
          setZonePickerNote(msg);
          try { window.alert(msg); } catch (err) {}
        }
      }
      applyZonePickerExclusionState();
      syncZonePickerOverlayFromChecks();
    });
    return zoneState.zonePickerModal;
  }

  function renderZonePickerModal() {
    if (!zoneState || !zoneState.zonePickerModal || !zoneState.zonePickerModal.list) return;
    const list = zoneState.zonePickerModal.list;
    const selected = zoneState.userSelectionReady ? getSelectedZoneIds() : [];
    const groups = getZonesGroupedByMockup();
    if (!groups.length) {
      list.innerHTML = '<div class="ds-zone-picker-empty">' + escHtml(tUi("no_print_areas_found", "No print areas found.")) + '</div>';
      return;
    }
    let html = "";
    groups.forEach((g) => {
      html += '<div class="ds-zone-picker-group">';
      html += '<div class="ds-zone-picker-group-head">';
      html += '<div class="ds-zone-picker-title">' + escAttr(g.title) + '</div>';
      html += '</div>';
      if (g.image_url) {
        html += '<div class="ds-zone-picker-preview">';
        html += '<div class="ds-zone-picker-stage">';
        html += '<img class="ds-zone-picker-preview-img" src="' + escAttr(g.image_url) + '" alt="' + escAttr(g.title) + '" />';
        html += '<div class="ds-zone-picker-overlay" data-group-key="' + escAttr(g.key) + '">';
        g.zones.forEach((z) => {
          const id = String(z.id || "");
          const zx = Math.max(0, Math.min(1, Number(z.x_pct || 0)));
          const zy = Math.max(0, Math.min(1, Number(z.y_pct || 0)));
          const zw = Math.max(0.01, Math.min(1, Number(z.w_pct || 0.25)));
          const zh = Math.max(0.01, Math.min(1, Number(z.h_pct || 0.25)));
          const selectedCls = selected.includes(id) ? "" : " is-off";
          html += '<div class="ds-zone-picker-area' + selectedCls + '" data-zone-id="' + escAttr(id) + '" style="left:' + (zx * 100).toFixed(4) + '%;top:' + (zy * 100).toFixed(4) + '%;width:' + (zw * 100).toFixed(4) + '%;height:' + (zh * 100).toFixed(4) + '%;">';
          html += '<div class="ds-zone-picker-area-fill"></div>';
          html += '<span>' + escAttr(z.label || id) + '</span>';
          html += '</div>';
        });
        html += '</div>';
        html += '</div>';
        html += '</div>';
      }
      html += '<div class="ds-zone-picker-checks">';
      g.zones.forEach((z) => {
        const id = String(z.id || "");
        const checked = selected.includes(id) ? ' checked="checked"' : "";
        html += '<label class="ds-zone-picker-check">';
        html += '<input type="checkbox" data-zone-id="' + escAttr(id) + '"' + checked + ' />';
        html += '<span>' + escAttr(z.label || id) + '</span>';
        html += '</label>';
      });
      html += '</div>';
      html += '</div>';
    });
    list.innerHTML = html;
    refreshZonePickerAreaDesigns();
  }

  function setZonePickerNote(msg) {
    if (!zoneState || !zoneState.zonePickerModal || !zoneState.zonePickerModal.dialog) return;
    const note = zoneState.zonePickerModal.dialog.querySelector('[data-role="zone-picker-note"]');
    if (!note) return;
    const text = String(msg || "").trim();
    if (!text) {
      note.textContent = "";
      note.style.display = "none";
      return;
    }
    note.textContent = text;
    note.style.display = "";
  }

  function getZonePickerCheckedIds() {
    const out = [];
    if (!zoneState || !zoneState.zonePickerModal || !zoneState.zonePickerModal.list) return out;
    zoneState.zonePickerModal.list.querySelectorAll('input[type="checkbox"][data-zone-id]:checked').forEach((el) => {
      const id = String(el.getAttribute("data-zone-id") || "");
      if (!id || out.includes(id)) return;
      out.push(id);
    });
    return out;
  }

  function setZonePickerCheckedState(zoneId, checked) {
    const zid = String(zoneId || "");
    if (!zid || !zoneState || !zoneState.zonePickerModal || !zoneState.zonePickerModal.list) return;
    zoneState.zonePickerModal.list.querySelectorAll('input[type="checkbox"][data-zone-id]').forEach((el) => {
      const id = String(el.getAttribute("data-zone-id") || "");
      if (id !== zid) return;
      el.checked = !!checked;
    });
  }

  function applyZonePickerExclusionState() {
    if (!zoneState || !zoneState.zonePickerModal || !zoneState.zonePickerModal.list) return;
    const list = zoneState.zonePickerModal.list;
    const checked = getZonePickerCheckedIds();
    list.querySelectorAll('input[type="checkbox"][data-zone-id]').forEach((el) => {
      el.disabled = false;
      el.closest(".ds-zone-picker-check")?.classList.remove("is-disabled");
    });
    if (!checked.length) setZonePickerNote("");
  }

  function syncZonePickerOverlayFromChecks() {
    if (!zoneState || !zoneState.zonePickerModal || !zoneState.zonePickerModal.list) return;
    const list = zoneState.zonePickerModal.list;
    const on = {};
    list.querySelectorAll('input[type="checkbox"][data-zone-id]').forEach((el) => {
      const id = String(el.getAttribute("data-zone-id") || "");
      if (!id) return;
      on[id] = !!el.checked;
    });
    list.querySelectorAll('.ds-zone-picker-area[data-zone-id]').forEach((area) => {
      const id = String(area.getAttribute("data-zone-id") || "");
      area.classList.toggle("is-off", !on[id]);
    });
    refreshZonePickerAreaDesigns();
  }

  function getZonePreviewCanvas(zoneId) {
    const id = String(zoneId || "");
    if (!id || !zoneState) return null;
    if (zoneState.previewCanvases && zoneState.previewCanvases[id]) return zoneState.previewCanvases[id];
    if (zoneState.currentId === id && canvas && canvas.lowerCanvasEl) return canvas.lowerCanvasEl;
    return null;
  }

  function buildTransparentPreviewCanvas(srcCanvas) {
    if (!srcCanvas) return null;
    if (!hideWhiteZoneBgInPreview) return srcCanvas;
    const w = Math.max(1, Number(srcCanvas.width || 1));
    const h = Math.max(1, Number(srcCanvas.height || 1));
    const out = document.createElement("canvas");
    out.width = w;
    out.height = h;
    const ctx = out.getContext("2d");
    if (!ctx) return srcCanvas;
    try {
      ctx.clearRect(0, 0, w, h);
      ctx.drawImage(srcCanvas, 0, 0, w, h);
      const img = ctx.getImageData(0, 0, w, h);
      const d = img.data;
      const whiteCut = 246;
      for (let i = 0; i < d.length; i += 4) {
        const a = d[i + 3];
        if (a === 0) continue;
        const r = d[i];
        const g = d[i + 1];
        const b = d[i + 2];
        if (r >= whiteCut && g >= whiteCut && b >= whiteCut) {
          d[i + 3] = 0;
        }
      }
      ctx.putImageData(img, 0, 0);
      return out;
    } catch (e) {
      return srcCanvas;
    }
  }

  function getZonePreviewCanvasForDisplay(zoneId) {
    const id = String(zoneId || "");
    const src = getZonePreviewCanvas(id);
    if (!src) return null;
    if (!zoneState) return src;
    if (!zoneState.previewTransparentCanvases) zoneState.previewTransparentCanvases = {};
    if (!zoneState.previewCanvasStamp) zoneState.previewCanvasStamp = {};
    const stamp = Number(zoneState.previewCanvasStamp[id] || 0);
    const cached = zoneState.previewTransparentCanvases[id];
    if (cached && Number(cached.__dsStamp || 0) === stamp && cached.width === src.width && cached.height === src.height) {
      return cached;
    }
    const out = buildTransparentPreviewCanvas(src);
    if (out && out !== src) {
      out.__dsStamp = stamp;
      zoneState.previewTransparentCanvases[id] = out;
      return out;
    }
    return src;
  }

  function refreshZonePickerAreaDesigns() {
    if (!zoneState || !zoneState.zonePickerModal || !zoneState.zonePickerModal.list) return;
    const list = zoneState.zonePickerModal.list;
    list.querySelectorAll('.ds-zone-picker-area[data-zone-id]').forEach((el) => {
      const id = String(el.getAttribute("data-zone-id") || "");
      const fill = el.querySelector(".ds-zone-picker-area-fill");
      if (!fill) return;
      const cnv = getZonePreviewCanvasForDisplay(id);
      let url = "";
      if (cnv && typeof cnv.toDataURL === "function") {
        try { url = cnv.toDataURL("image/png"); } catch (e) { url = ""; }
      }
      fill.style.backgroundImage = url ? ('url("' + url + '")') : "none";
      fill.style.opacity = el.classList.contains("is-off") ? "0.3" : "0.95";
    });
  }

  function openZonePickerModal() {
    if (!zoneState || !zoneState.enabled) {
      return;
    }
    const modal = buildZonePickerModal();
    if (!modal || !modal.wrap) {
      return;
    }
    const note = modal.dialog ? modal.dialog.querySelector('[data-role="zone-picker-note"]') : null;
    if (note) {
      note.textContent = "";
      note.style.display = "none";
    }
    renderZonePickerModal();
    applyZonePickerExclusionState();
    syncZonePickerOverlayFromChecks();
    refreshZonePickerAreaDesigns();
    modal.wrap.classList.add("is-open");
    modal.wrap.setAttribute("aria-hidden", "false");
  }

  function requiresInitialZoneSelection() {
    const areaCount = (() => {
      try { return Object.keys(getMockupAreaMap()).length; } catch (e) { return 0; }
    })();
    const zoneCount = (zoneState && Array.isArray(zoneState.zones)) ? zoneState.zones.length : 0;
    const hasMultipleSelectableAreas = Math.max(zoneCount, areaCount) > 1;
    return !!(
      zoneState &&
      zoneState.enabled &&
      hasMultipleSelectableAreas &&
      !zoneState.userSelectionReady
    );
  }

  function isStartupZoneStepReady() {
    if (!zoneState || !zoneState.enabled) return true;
    if (!Array.isArray(zoneState.zones) || zoneState.zones.length <= 1) return true;
    return !requiresInitialZoneSelection() && getSelectedZoneIds().length > 0;
  }

  function isStartupInteractionLocked() {
    return false;
  }

  function ensureStartupFlowPanel() {
    return null;
  }

  function syncStartupFlowPanel() {
    return;
  }

  function setStartupFlowGate(active) {
    if (!active) root.classList.remove("ds-startup-gated");
    return;
  }

  function triggerStartupFlowRequiredStatus() {
    return;
  }

  function isZoneSelectionInteractionLocked() {
    return requiresInitialZoneSelection();
  }

  function triggerZoneSelectionRequiredStatus() {
    setZoneSelectionGate(true);
    openZonePickerModal();
    setStatus("Choose print areas to start designing.");
  }

  function isAllowedDuringZoneLock(actionKey) {
    const k = String(actionKey || "");
    if (!k) return false;
    return (
      k === "template-apply" ||
      k === "open-zones" ||
      k === "zone-picker-open" ||
      k === "hard-refresh" ||
      k === "my-designs" ||
      k === "load-design" ||
      k === "save" ||
      k === "final-save" ||
      k === "export" ||
      k === "apply"
    );
  }

  function guardZoneSelectionLock(actionKey) {
    if (isStartupInteractionLocked() && !isAllowedDuringZoneLock(actionKey)) {
      triggerStartupFlowRequiredStatus();
      return true;
    }
    if (!isZoneSelectionInteractionLocked()) return false;
    if (isAllowedDuringZoneLock(actionKey)) return false;
    triggerZoneSelectionRequiredStatus();
    return true;
  }

  function ensureZoneSelectionOverlay() {
    const host = root.querySelector(".ds-center-stage") || root.querySelector(".ds-canvas-shell");
    if (!host) return null;
    let overlay = host.querySelector('[data-role="zone-required-overlay"]');
    if (overlay) return overlay;
    overlay = document.createElement("div");
    overlay.className = "ds-zone-required-overlay";
    overlay.setAttribute("data-role", "zone-required-overlay");
    overlay.innerHTML =
      '<div class="ds-zone-required-card">' +
      '<h4>Choose Print Areas</h4>' +
      '<p>Select one or more zones to activate the canvas.</p>' +
      '<button type="button" data-role="zone-required-open">Select Zones</button>' +
      '</div>';
    overlay.addEventListener("click", (e) => {
      const btn = e.target && e.target.closest ? e.target.closest('[data-role="zone-required-open"]') : null;
      if (!btn) return;
      openZonePickerModal();
    });
    host.appendChild(overlay);
    return overlay;
  }

  function setZoneSelectionGate(active) {
    const on = !!active;
    root.classList.toggle("ds-zone-selection-required", on);
    root.classList.toggle("ds-no-active-canvas", on);
    if (wrapEl) {
      wrapEl.style.display = on ? "none" : "block";
      wrapEl.style.visibility = on ? "hidden" : "visible";
    }
    const overlay = ensureZoneSelectionOverlay();
    if (overlay) {
      overlay.classList.toggle("is-visible", on);
      overlay.setAttribute("aria-hidden", on ? "false" : "true");
    }
    if (canvas && canvas.upperCanvasEl) {
      canvas.upperCanvasEl.style.pointerEvents = on ? "none" : "";
    }
  }

  function closeZonePickerModal() {
    if (requiresInitialZoneSelection()) {
      setStatus("Select at least one print area to continue.");
      return;
    }
    if (!zoneState || !zoneState.zonePickerModal || !zoneState.zonePickerModal.wrap) return;
    zoneState.zonePickerModal.wrap.classList.remove("is-open");
    zoneState.zonePickerModal.wrap.setAttribute("aria-hidden", "true");
  }

  function applyZoneSelection(selectedIds) {
    if (!zoneState || !zoneState.enabled) return;
    const all = zoneState.zones.map((z) => String(z.id || "")).filter(Boolean);
    const conflict = getZoneConflictMap();
    const unique = [];
    let hasConflict = false;
    (Array.isArray(selectedIds) ? selectedIds : []).forEach((id) => {
      if (hasConflict) return;
      const zid = String(id || "");
      if (!zid || !all.includes(zid) || unique.includes(zid)) return;
      const blockedBy = unique.find((sid) => conflict[sid] && conflict[sid][zid]);
      if (blockedBy) {
        const msg = getZoneLabel(zid) + " and " + getZoneLabel(blockedBy) + " cannot be used together (conflict rule).";
        try { window.alert(msg); } catch (e) {}
        hasConflict = true;
        return;
      }
      unique.push(zid);
    });
    if (hasConflict) return;
    if (!unique.length) {
      setStatus("Select at least one print area.");
      return;
    }
    const prev = getSelectedZoneIds();
    zoneState.selectedIds = unique.slice();
    zoneState.userSelectionReady = true;
    setZoneSelectionGate(false);
    prev.forEach((id) => {
      if (!zoneState.selectedIds.includes(id) && zoneHasContent(id)) {
        resetZoneData(id);
      }
    });
    if (typeof zoneState.renderZoneButtons === "function") zoneState.renderZoneButtons();
    const nextActiveZone = zoneState.selectedIds.includes(zoneState.currentId)
      ? String(zoneState.currentId || "")
      : String(zoneState.selectedIds[0] || "");
    if (nextActiveZone) switchToZone(nextActiveZone);
    else updateZoneToolbarUi();
    renderActiveZoneSelector();
    syncStartupFlowPanel();
    setStatus(tUi("print_areas_updated_selected", "Print areas updated (%s selected).", [zoneState.selectedIds.length]));
    if (startupFlowEnabled && startupStep === "zones" && !startupZoneStepRedirecting) {
      startupZoneStepRedirecting = true;
      saveStartupZoneReadyForTemplate(selectedTemplateId, zoneState.selectedIds);
      window.setTimeout(() => {
        try {
          const u = new URL(window.location.href);
          u.searchParams.set("ds_template", String(selectedTemplateId));
          u.searchParams.set("ds_pick", "1");
          u.searchParams.set("ds_start", "1");
          u.searchParams.set("ds_zsel", zoneState.selectedIds.join(","));
          u.searchParams.delete("ds_step");
          u.searchParams.delete("ds_open_zones");
          u.searchParams.set("_dsr", String(Date.now()));
          window.location.href = u.toString();
        } catch (e) {
          startupZoneStepRedirecting = false;
        }
      }, 120);
    }
  }

  function normalizeLoadedZoneSelectionIds(selectedIds, preferredActiveZoneId) {
    if (!zoneState || !zoneState.enabled) return [];
    const all = zoneState.zones.map((z) => String(z.id || "")).filter(Boolean);
    const incoming = Array.isArray(selectedIds) ? selectedIds.map((z) => String(z || "")).filter(Boolean) : [];
    const preferred = String(preferredActiveZoneId || "").trim();
    if (preferred && all.includes(preferred)) {
      incoming.unshift(preferred);
    }
    const conflict = getZoneConflictMap();
    const unique = [];
    incoming.forEach((zid) => {
      if (!all.includes(zid) || unique.includes(zid)) return;
      const blockedBy = unique.find((sid) => conflict[sid] && conflict[sid][zid]);
      if (blockedBy) return;
      unique.push(zid);
    });
    if (!unique.length) {
      const fallback = (preferred && all.includes(preferred))
        ? preferred
        : ((zoneState.currentId && all.includes(String(zoneState.currentId))) ? String(zoneState.currentId) : (all[0] || ""));
      if (fallback) unique.push(fallback);
    }
    return unique;
  }

  function applyLoadedZoneSelection(selectedIds, preferredActiveZoneId) {
    if (!zoneState || !zoneState.enabled) return;
    const unique = normalizeLoadedZoneSelectionIds(selectedIds, preferredActiveZoneId);
    zoneState.selectedIds = unique.slice();
    zoneState.userSelectionReady = true;
    setZoneSelectionGate(false);
    if (typeof zoneState.renderZoneButtons === "function") zoneState.renderZoneButtons();
    const nextActiveZone = zoneState.selectedIds.includes(zoneState.currentId)
      ? String(zoneState.currentId || "")
      : String(zoneState.selectedIds[0] || "");
    if (nextActiveZone) switchToZone(nextActiveZone);
    else updateZoneToolbarUi();
    renderActiveZoneSelector();
    syncStartupFlowPanel();
    if (zoneState.zonePickerModal && zoneState.zonePickerModal.wrap && zoneState.zonePickerModal.wrap.classList.contains("is-open")) {
      renderZonePickerModal();
      applyZonePickerExclusionState();
      syncZonePickerOverlayFromChecks();
    }
  }

  function applyZonePickerSelection() {
    if (!zoneState || !zoneState.zonePickerModal || !zoneState.zonePickerModal.list) return;
    const selected = [];
    zoneState.zonePickerModal.list.querySelectorAll('input[type="checkbox"][data-zone-id]:checked').forEach((el) => {
      selected.push(String(el.getAttribute("data-zone-id") || ""));
    });
    if (!selected.length) {
      try { window.alert(tUi("choose_at_least_one_print_area", "Choose at least one print area.")); } catch (e) {}
      return;
    }
    applyZoneSelection(selected);
    closeZonePickerModal();
  }

  function buildZoneToolbar() {
    if (!zoneState || !zoneState.enabled) return;
    const toolbar = root.querySelector(".ds-toolbar");
    if (!toolbar) return;
    const block = document.createElement("div");
    block.className = "ds-toolblock ds-zoneblock";
    const title = document.createElement("div");
    title.className = "ds-block-title";
    title.textContent = tUi("zones", "Zones");
    const group = document.createElement("div");
    group.className = "ds-group ds-zone-buttons";
    const previewWrap = document.createElement("div");
    previewWrap.className = "ds-zone-preview";
    previewWrap.setAttribute("title", tUi("choose_print_areas", "Choose print areas"));
    const previewImg = document.createElement("img");
    previewImg.className = "ds-zone-preview-img";
    previewImg.alt = tUi("zone_preview_alt", "Zone preview");
    previewImg.loading = "lazy";
    const previewMeta = document.createElement("div");
    previewMeta.className = "ds-zone-preview-meta";
    previewMeta.textContent = tUi("zone_label_single", "Zone");
    previewWrap.appendChild(previewImg);
    previewWrap.appendChild(previewMeta);

    const zoneIcons = {
      main_front: "MF",
      main_back: "MB",
      top_left: "TL",
      top_right: "TR",
      top_center: "TC",
      front_full: "F",
      back_full: "B",
      top: "T",
      chest_left: "CL",
      chest_right: "CR",
      sleeve_left: "SL",
      sleeve_right: "SR",
      side_left: "L",
      side_right: "R"
    };
    function renderButtons() {
      group.innerHTML = "";
      const selectedIds = getSelectedZoneIds();
      zoneState.zones.forEach((z) => {
        if (!selectedIds.includes(String(z.id || ""))) return;
        const b = document.createElement("button");
        b.type = "button";
        b.setAttribute("data-action", "switch-zone");
        b.setAttribute("data-zone-id", z.id);
        const ic = isPlacementZoneId(z.id)
          ? (zoneSide(z.id) === "back" ? "B+" : "F+")
          : (zoneIcons[String(z.id || "")] || z.label.slice(0, 1).toUpperCase());
        b.setAttribute("title", z.label);
        b.innerHTML = '<span class="ds-zone-ic" aria-hidden="true">' + escAttr(ic) + '</span><span class="ds-zone-tx">' + escAttr(z.label) + '</span>';
        group.appendChild(b);
      });
      decorateIconButtons(group);
      updateZonePreviewUi();
      renderActiveZoneSelector();
    }
    renderButtons();
    block.appendChild(title);
    block.appendChild(previewWrap);
    block.appendChild(group);
    const templateBlock = toolbar.querySelector(".ds-template-block");
    if (templateBlock && templateBlock.parentNode === toolbar) {
      if (templateBlock.nextSibling) toolbar.insertBefore(block, templateBlock.nextSibling);
      else toolbar.appendChild(block);
    } else {
      toolbar.insertBefore(block, toolbar.firstChild);
    }
    zoneState.buttonsWrap = group;
    zoneState.renderZoneButtons = renderButtons;
    zoneState.previewUi = { wrap: previewWrap, img: previewImg, meta: previewMeta };
    previewWrap.addEventListener("click", () => openZonePickerModal());
    ensureActiveZoneSelector();
    renderActiveZoneSelector();
    updateZonePreviewUi();
    updateZoneToolbarUi();
  }

  function ensureActiveZoneSelector() {
    if (!zoneState || !zoneState.enabled) return null;
    if (zoneState.activeZoneSelectWrap && zoneState.activeZoneSelectWrap.parentNode) {
      return zoneState.activeZoneSelectWrap;
    }
    const host = isMobileViewport()
      ? (root.querySelector(".ds-mobile-topbar-center")
        || root.querySelector(".ds-mobile-topbar")
        || root.querySelector(".ds-toolbar"))
      : (root.querySelector('[data-role="desktop-top-center"]')
        || root.querySelector(".ds-topbar-center")
        || root.querySelector(".ds-topbar-meta")
        || root.querySelector(".ds-topbar")
        || root.querySelector(".ds-toolbar"));
    if (!host) return null;
    const wrap = document.createElement("label");
    wrap.className = "ds-active-zone-select";
    wrap.innerHTML =
      '<button type="button" class="ds-active-zone-icon-btn" data-mobile-action="open-zones" title="' + escAttr(tUi("open_zone_tools", "Open Zone Tools")) + '" aria-label="' + escAttr(tUi("open_zone_tools", "Open Zone Tools")) + '">' +
        '<img class="ds-active-zone-icon" data-role="active-zone-mockup-icon" alt="' + escAttr(tUi("canvas_preview", "Canvas preview")) + '" loading="lazy" />' +
      "</button>" +
      '<span class="ds-active-zone-help">' + escHtml(tUi("active_canvas", "Active Canvas")) + '</span>' +
      '<span class="ds-active-zone-row"><select data-role="active-zone-select"></select></span>';
    const sel = wrap.querySelector('[data-role="active-zone-select"]');
    if (sel) {
      sel.addEventListener("change", () => {
        const zid = String(sel.value || "");
        if (!zid) return;
        switchToZone(zid);
      });
    }
    host.appendChild(wrap);
    zoneState.activeZoneSelectWrap = wrap;
    return wrap;
  }

  function renderActiveZoneSelector() {
    if (!zoneState || !zoneState.enabled) return;
    const wrap = ensureActiveZoneSelector();
    if (!wrap) return;
    const sel = wrap.querySelector('[data-role="active-zone-select"]');
    const iconBtn = wrap.querySelector(".ds-active-zone-icon-btn");
    const icon = wrap.querySelector('[data-role="active-zone-mockup-icon"]');
    const help = wrap.querySelector(".ds-active-zone-help");
    const row = wrap.querySelector(".ds-active-zone-row");
    const is3DTopPreview = !!enablePreview3D;
    if (help) help.style.display = is3DTopPreview ? "none" : "";
    if (row) row.style.display = is3DTopPreview ? "none" : "";
    if (is3DTopPreview) {
      wrap.style.gridTemplateColumns = "52px";
      wrap.style.columnGap = "0";
      if (sel) sel.disabled = true;
    } else {
      wrap.style.gridTemplateColumns = "";
      wrap.style.columnGap = "";
    }
    if (iconBtn) {
      iconBtn.setAttribute("data-mobile-action", is3DTopPreview ? "mug-open" : "open-zones");
      iconBtn.setAttribute("title", is3DTopPreview ? tUi("open_3d_preview", "Open 3D Preview") : tUi("open_zone_tools", "Open Zone Tools"));
      iconBtn.setAttribute("aria-label", is3DTopPreview ? tUi("open_3d_preview", "Open 3D Preview") : tUi("open_zone_tools", "Open Zone Tools"));
      if (iconBtn.classList) iconBtn.classList.toggle("ds-is-3d-preview", is3DTopPreview);
    }
    if (!sel) return;
    const all = Array.isArray(zoneState.zones) ? zoneState.zones : [];
    const needsInitial = requiresInitialZoneSelection();
    const selectedIds = getSelectedZoneIds();
    const enabledIds = selectedIds.length ? selectedIds : all.map((z) => String(z.id || "")).filter(Boolean);
    if (needsInitial) {
      sel.innerHTML = '<option value="">' + escHtml(tUi("choose_zones", "Choose zones...")) + '</option>';
      sel.disabled = true;
      if (icon) {
        icon.removeAttribute("src");
        icon.style.display = "none";
      }
      if (iconBtn) iconBtn.style.display = "none";
      wrap.style.display = "";
      return;
    }
    const options = [];
    enabledIds.forEach((id) => {
      const z = all.find((row) => String(row.id || "") === id);
      if (!z) return;
      options.push('<option value="' + escAttr(id) + '">' + escAttr(String(z.label || id)) + "</option>");
    });
    sel.innerHTML = options.length ? options.join("") : ('<option value="">' + escHtml(tUi("no_zone", "No zone")) + '</option>');
    const current = String(zoneState.currentId || "");
    if (current && enabledIds.includes(current)) sel.value = current;
    else if (enabledIds[0]) sel.value = enabledIds[0];
    sel.disabled = enabledIds.length <= 1;
    if (icon) {
      if (is3DTopPreview) {
        icon.src = topbarPreview3DIconSrc;
        icon.style.display = "block";
        icon.setAttribute("alt", tUi("preview_3d", "3D Preview"));
        if (iconBtn) iconBtn.style.display = "inline-flex";
        wrap.style.display = enabledIds.length ? "" : "none";
        try {
          if (isClassicDesktopViewport()) {
            ensureDesktopClassicUtilityPanel();
          }
        } catch (e) {}
        return;
      }
      const previewZoneId = String(sel.value || current || enabledIds[0] || "");
      const mock = pickZoneMockup(previewZoneId);
      const iconSrc = String((mock && mock.image_url) || "");
      if (iconSrc) {
        icon.src = iconSrc;
        icon.style.display = "block";
        icon.setAttribute("alt", String((mock && mock.name) || tUi("canvas_preview", "Canvas preview")));
        if (iconBtn) iconBtn.style.display = "inline-flex";
      } else {
        icon.removeAttribute("src");
        icon.style.display = "none";
        if (iconBtn) iconBtn.style.display = "none";
      }
    }
    wrap.style.display = enabledIds.length ? "" : "none";
    try {
      if (isClassicDesktopViewport()) {
        ensureDesktopClassicUtilityPanel();
      }
    } catch (e) {}
  }

  function setZoneStatusPrefix() {
    if (!zoneState || !zoneState.enabled) return "";
    const currentZone = zoneState.zones.find((z) => z.id === zoneState.currentId);
    return currentZone ? ("[" + currentZone.label + "] ") : "";
  }

  function captureCurrentZone() {
    if (!zoneState || !zoneState.enabled || !zoneState.currentId) return;
    zoneState.snapshots[zoneState.currentId] = designJSON();
    copyMainCanvasToZone(zoneState.currentId);
  }

  function isTopZoneId(zoneId) {
    const id = String(zoneId || "");
    return id === "top_left" || id === "top_right" || id === "top_center";
  }

  function isChestZoneId(zoneId) {
    return false;
  }

  function zoneHasContent(zoneId) {
    if (!zoneState || !zoneState.enabled) return false;
    const zid = String(zoneId || "");
    if (!zid) return false;
    if (zid === String(zoneState.currentId || "")) {
      if (getDesignObjects().length > 0) return true;
    }
    const snap = zoneState.snapshots[zid];
    if (snap && Array.isArray(snap.objects) && snap.objects.length > 0) return true;
    const draft = loadLocalDraft();
    if (draft && draft.zones && typeof draft.zones === "object" && draft.zones[zid]) {
      const row = draft.zones[zid];
      if (row && row.snapshot && Array.isArray(row.snapshot.objects) && row.snapshot.objects.length > 0) return true;
      if (row && typeof row.pngData === "string" && row.pngData.indexOf("data:image/png;base64,") === 0) return true;
    }
    return false;
  }

  function resetZoneData(zoneId) {
    if (!zoneState || !zoneState.enabled) return;
    const id = String(zoneId || "");
    zoneState.snapshots[id] = { version: fabric.version, objects: [] };
    zoneState.previewCanvases[id] = createBlankZoneCanvas(id);
  }

  function enforceTopChestExclusion(nextId) {
    if (!zoneState || !zoneState.enabled) return "";
    const next = String(nextId || "");
    if (isTopZoneId(next)) {
      try { window.alert(tUi("top_options_exclusive", "Top options: you can use only one of topLeft, topRight, topCenter.")); } catch (e) {}
      let changed = false;
      ["top_left", "top_right", "top_center"].forEach((id) => {
        if (id === next) return;
        if (zoneHasContent(id)) {
          resetZoneData(id);
          changed = true;
        }
      });
      return changed ? "Other top zones were cleared (only one top zone is allowed)." : "Only one top zone is allowed.";
    }
    return "";
  }

  function switchToZone(zoneId, onReady) {
    const finish = () => {
      if (typeof onReady === "function") {
        try { onReady(); } catch (e) {}
      }
    };
    if (!zoneState || !zoneState.enabled) return;
    if (requiresInitialZoneSelection()) {
      setZoneSelectionGate(true);
      openZonePickerModal();
      finish();
      return;
    }
    const nextId = String(zoneId || "");
    if (!nextId || !zoneState.zones.some((z) => z.id === nextId)) {
      finish();
      return;
    }
    if (!zoneState.internalSwitch && zoneState.userSelectionReady && Array.isArray(zoneState.selectedIds) && zoneState.selectedIds.length) {
      const selected = zoneState.selectedIds.map((id) => String(id || ""));
      if (!selected.includes(nextId)) {
        finish();
        return;
      }
    }
    if (!zoneState.internalSwitch && !restoring && zoneState.currentId) {
      captureCurrentZone();
      // Auto-persist outgoing zone locally so Final Save has all worked zones.
      saveZoneToLocalDraftById(zoneState.currentId, false);
    }
    const exclusionMsg = enforceTopChestExclusion(nextId);
    zoneState.currentId = nextId;
    renderActiveZoneSelector();
    applyCanvasPixelSizeForZone(nextId);
    fitCanvas(true);
    updateZoneToolbarUi();
    updateOrientationToolbarUi();
    const snap = zoneState.snapshots[nextId];
    if (snap && snap.objects && Array.isArray(snap.objects)) {
      loadDesignJSON(snap, () => {
        resetHistory();
        const mm = getZoneCanvasMm(nextId);
        setStatus(setZoneStatusPrefix() + tUi("zone_ready_with_size", "Zone ready (%sx%s mm).", [mm.w, mm.h]) + (exclusionMsg ? " " + exclusionMsg : ""));
        finish();
      }, nextId);
    } else {
      loadDesignJSON({ version: fabric.version, objects: [] }, () => {
        resetHistory();
        const mm = getZoneCanvasMm(nextId);
        setStatus(setZoneStatusPrefix() + tUi("zone_ready_with_size", "Zone ready (%sx%s mm).", [mm.w, mm.h]) + (exclusionMsg ? " " + exclusionMsg : ""));
        finish();
      }, nextId);
    }
  }

  function collectUsedZonesForSave() {
    if (!zoneState || !zoneState.enabled) {
      if (getDesignObjects().length > 0) {
        return [zoneState && zoneState.zones[0] ? zoneState.zones[0].id : "front_full"];
      }
      return [];
    }
    const selected = getSelectedZoneIds();
    const used = selected.length ? selected.slice() : [];
    if (!used.length) {
      zoneState.zones.forEach((z) => {
        const snap = zoneState.snapshots[z.id];
        if (snap && Array.isArray(snap.objects) && snap.objects.length > 0) used.push(z.id);
      });
    }
    const tops = ["top_left", "top_right", "top_center"].filter((id) => used.includes(id));
    if (tops.length > 1) {
      const keep = tops.includes(zoneState.currentId) ? zoneState.currentId : tops[0];
      return used.filter((id) => !isTopZoneId(id) || id === keep);
    }
    if (!used.length && zoneState.currentId) used.push(zoneState.currentId);
    return used;
  }

  function buildPersistedLayersPayload() {
    if (!zoneState || !zoneState.enabled) return designJSON();
    captureCurrentZone();
    const zonesPayload = {};
    const zoneOrientations = {};
    zoneState.zones.forEach((z) => {
      const snap = zoneState.snapshots[z.id];
      zonesPayload[z.id] = (snap && Array.isArray(snap.objects))
        ? snap
        : { version: fabric.version, objects: [] };
      zoneOrientations[z.id] = getCurrentZoneOrientation(z.id);
    });
    return {
      type: "zoned",
      active_zone: zoneState.currentId,
      zones: zonesPayload,
      orientations: zoneOrientations,
      placements: (previewType === "tshirt") ? {} : (zoneState.placements || {})
    };
  }

  function saveZonesSequential(designPostId, layersPayload, usedZoneIds, allZoneIds, zonePngMap) {
    const designId = parseInt(designPostId || "0", 10);
    if (!designId || !layersPayload || layersPayload.type !== "zoned") return Promise.resolve();
    const zones = (layersPayload.zones && typeof layersPayload.zones === "object") ? layersPayload.zones : {};
    const all = Array.isArray(allZoneIds) ? allZoneIds.map((z) => String(z || "")).filter(Boolean) : [];
    const filtered = Array.isArray(usedZoneIds) ? usedZoneIds.map((z) => String(z || "")).filter(Boolean) : [];
    const zoneIds = all.length ? all : Object.keys(zones);
    if (!zoneIds.length) return Promise.resolve();
    const orientations = (layersPayload.orientations && typeof layersPayload.orientations === "object")
      ? layersPayload.orientations
      : {};
    const placements = (layersPayload.placements && typeof layersPayload.placements === "object")
      ? layersPayload.placements
      : {};
    const activeZone = String(layersPayload.active_zone || "");
    let saved = 0;
    let savedPng = 0;
    const zonePngUrls = {};
    let activeCount = 0;
    let chain = Promise.resolve();
    let reqIdx = 0;
    const renderSnapshotToPng = (snapshot, zoneId) => new Promise((resolve) => {
      try {
        const mm = getZoneCanvasMm(zoneId);
        const w = Math.max(8, mmToPx(Number(mm.w || cfg.width_mm || 200), previewDpi));
        const h = Math.max(8, mmToPx(Number(mm.h || cfg.height_mm || 100), previewDpi));
        const tmpEl = document.createElement("canvas");
        tmpEl.width = w;
        tmpEl.height = h;
        const tmp = new fabric.StaticCanvas(tmpEl, { backgroundColor: "#fff" });
        const safe = (snapshot && Array.isArray(snapshot.objects))
          ? snapshot
          : { version: fabric.version, objects: [] };
        tmp.loadFromJSON(safe, () => {
          try {
            tmp.renderAll();
            resolve(tmp.toDataURL({ format: "png", multiplier: 1 }));
          } catch (e) {
            resolve("");
          }
          try { tmp.dispose(); } catch (e2) {}
        });
      } catch (e) {
        resolve("");
      }
    });
    zoneIds.forEach((zid) => {
      const isActive = filtered.includes(zid);
      if (isActive) activeCount += 1;
      const clearFirstFlag = reqIdx === 0;
      chain = chain.then(() => {
        const snapshot = zones[zid] || { version: fabric.version, objects: [] };
        let pngPromise = Promise.resolve("");
        if (isActive && zonePngMap && typeof zonePngMap[zid] === "string" && zonePngMap[zid].indexOf("data:image/png;base64,") === 0) {
          pngPromise = Promise.resolve(zonePngMap[zid]);
        } else if (isActive) {
          pngPromise = renderSnapshotToPng(snapshot, zid).then((png) => {
            if (png) return png;
            const zoneCanvas = getZonePreviewCanvas(zid);
            if (zoneCanvas && typeof zoneCanvas.toDataURL === "function") {
              try { return zoneCanvas.toDataURL("image/png"); } catch (e) {}
            }
            return "";
          });
        }
        return pngPromise.then((zonePngData) => fetchJSON(
          cfg.rest.base + "/design/" + encodeURIComponent(String(designId)) + "/zone-layer",
          {
            method: "POST",
            headers: { "Content-Type": "application/json", "X-WP-Nonce": cfg.rest.nonce },
            body: JSON.stringify({
              zoneId: zid,
              snapshot: snapshot,
              orientation: String(orientations[zid] || "portrait"),
              activeZone: activeZone,
              zoneActive: isActive,
              placements: placements,
              allZones: zoneIds,
              zonesUsed: filtered,
              designUuid: (window.DS_LAST_SAVED && typeof window.DS_LAST_SAVED === "object") ? String(window.DS_LAST_SAVED.id || "") : "",
              pngData: zonePngData || "",
              clearFirst: clearFirstFlag
            })
          }
        ));
      }).then((res) => {
        saved += 1;
        if (res && typeof res.zone_png === "string" && res.zone_png) {
          savedPng += 1;
          zonePngUrls[zid] = res.zone_png;
        }
      });
      reqIdx += 1;
    });
    return chain.then(() => ({
      saved: saved,
      savedPng: savedPng,
      zonePngUrls: zonePngUrls,
      total: zoneIds.length,
      active: activeCount,
      inactive: Math.max(0, zoneIds.length - activeCount),
      activeZones: filtered.slice()
    }));
  }

  function loadPersistedLayersPayload(payload, done) {
    if (!zoneState || !zoneState.enabled) {
      loadDesignJSON(payload, done);
      return;
    }
    if (payload && Array.isArray(payload.objects)) {
      zoneState.placements = {};
      zoneState.zones = zoneState.zones.filter((z) => !isPlacementZoneId(z.id));
      if (typeof zoneState.renderZoneButtons === "function") zoneState.renderZoneButtons();
      zoneState.zones.forEach((z) => { zoneState.snapshots[z.id] = { version: fabric.version, objects: [] }; });
      zoneState.snapshots[zoneState.currentId] = payload;
      zoneState.internalSwitch = true;
      switchToZone(zoneState.currentId, () => {
        zoneState.internalSwitch = false;
        if (typeof done === "function") done();
      });
      return;
    }
    if (!payload || payload.type !== "zoned" || !payload.zones || typeof payload.zones !== "object") {
      zoneState.placements = {};
      zoneState.zones = zoneState.zones.filter((z) => !isPlacementZoneId(z.id));
      if (typeof zoneState.renderZoneButtons === "function") zoneState.renderZoneButtons();
      zoneState.internalSwitch = true;
      zoneState.zones.forEach((z) => { zoneState.snapshots[z.id] = { version: fabric.version, objects: [] }; });
      switchToZone(zoneState.currentId, () => {
        zoneState.internalSwitch = false;
        if (typeof done === "function") done();
      });
      return;
    }
    if (previewType !== "tshirt" && payload.placements && typeof payload.placements === "object") {
      // rebuild dynamic placement zones
      const dynIds = Object.keys(payload.placements);
      let maxNum = 0;
      zoneState.placements = {};
      dynIds.forEach((id) => {
        const p = payload.placements[id];
        if (!p) return;
        const m = String(id).match(/_(\d+)$/);
        if (m && Number(m[1]) > maxNum) maxNum = Number(m[1]);
        zoneState.placements[id] = {
          id: String(id),
          side: String(p.side || zoneSide(id)),
          x: Number(p.x || 0.3),
          y: Number(p.y || 0.3),
          w: Number(p.w || 0.2),
          h: Number(p.h || 0.2),
          width_mm: Number(p.width_mm || 210),
          height_mm: Number(p.height_mm || 297),
          label: String(p.label || id)
        };
      });
      zoneState.zones = zoneState.zones.filter((z) => !isPlacementZoneId(z.id));
      Object.keys(zoneState.placements).forEach((id) => {
        if (!zoneState.zones.some((z) => z.id === id)) {
          zoneState.zones.push({ id: id, label: zoneState.placements[id].label || id });
        }
      });
      if (maxNum >= placementCounter) placementCounter = maxNum + 1;
      if (typeof zoneState.renderZoneButtons === "function") zoneState.renderZoneButtons();
    }
    const normalizeZoneKey = (v) => String(v || "").trim();
    const normalizeZoneKeyLegacy = (v) => String(v || "").toLowerCase().replace(/[^a-z0-9_-]/g, "");
    const findLegacyUniqueMatch = (sourceMap, legacyKey) => {
      if (!sourceMap || typeof sourceMap !== "object" || !legacyKey) return null;
      const hits = Object.keys(sourceMap).filter((k) => normalizeZoneKeyLegacy(k) === legacyKey);
      return hits.length === 1 ? sourceMap[hits[0]] : null;
    };
    const findZonePayload = (zoneId) => {
      const directKey = normalizeZoneKey(zoneId);
      if (directKey && payload.zones[directKey]) return payload.zones[directKey];
      const legacyKey = normalizeZoneKeyLegacy(zoneId);
      if (legacyKey && payload.zones[legacyKey]) return payload.zones[legacyKey];
      const byLegacyUnique = findLegacyUniqueMatch(payload.zones, legacyKey);
      if (byLegacyUnique) return byLegacyUnique;
      return null;
    };
    const findOrientation = (zoneId) => {
      if (!payload.orientations || typeof payload.orientations !== "object") return null;
      const directKey = normalizeZoneKey(zoneId);
      if (directKey && payload.orientations[directKey]) return payload.orientations[directKey];
      const legacyKey = normalizeZoneKeyLegacy(zoneId);
      if (legacyKey && payload.orientations[legacyKey]) return payload.orientations[legacyKey];
      const byLegacyUnique = findLegacyUniqueMatch(payload.orientations, legacyKey);
      if (byLegacyUnique) return byLegacyUnique;
      return null;
    };
    zoneState.zones.forEach((z) => {
      const raw = findZonePayload(z.id);
      if (raw && Array.isArray(raw.objects)) zoneState.snapshots[z.id] = raw;
      else zoneState.snapshots[z.id] = { version: fabric.version, objects: [] };
      const o = findOrientation(z.id) || zoneState.orientations[z.id];
      zoneState.orientations[z.id] = normalizeOrientation(o);
    });
    zoneState.internalSwitch = true;
    const target = (payload.active_zone && zoneState.zones.some((z) => z.id === payload.active_zone))
      ? String(payload.active_zone)
      : zoneState.currentId;
    switchToZone(target, () => {
      zoneState.internalSwitch = false;
      if (typeof done === "function") done();
    });
  }

  function fitCanvas(resetZoom) {
    if (!viewportEl || !wrapEl) return;
    const availableW = Math.floor(viewportEl.clientWidth || 0);
    const availableH = Math.floor(viewportEl.clientHeight || 0);
    if (availableW <= 0 || availableH <= 0) return;
    const sx = availableW / Math.max(1, widthPx);
    const sy = availableH / Math.max(1, heightPx);
    // Always full-fit canvas, with a safety gap.
    const isMobileVp = typeof window !== "undefined" && window.matchMedia
      ? window.matchMedia("(max-width: 1023px)").matches
      : false;
    if (isMobileVp) {
      const isPortraitMobileVp = typeof window !== "undefined" && window.matchMedia
        ? window.matchMedia("(max-width: 1023px) and (orientation: portrait)").matches
        : false;
      const mobileFitFactor = isPortraitMobileVp ? 0.95 : 0.99;
      state.fitScale = Math.max(0.01, Math.min(sx, sy) * mobileFitFactor);
      if (root && root.setAttribute) {
        root.setAttribute("data-canvas-major-axis", "full-fit");
      }
    } else {
      // Desktop/tablet fallback: keep full-canvas fit.
      const baseFit = Math.min(1, sx, sy);
      const isDesktopClassic = isClassicDesktopViewport();
      const paneExpanded = !!(root && root.getAttribute && root.getAttribute("data-dc-pane-expanded") === "1");
      const desktopClassicFitFactor = (isDesktopClassic && paneExpanded) ? 0.96 : 1;
      state.fitScale = Math.max(0.01, baseFit * desktopClassicFitFactor);
      if (root && root.removeAttribute) {
        root.removeAttribute("data-canvas-major-axis");
      }
    }
    if (resetZoom) state.zoom = 1;
    updatePortraitCanvasRowHeight();
    applyCanvasScale();
    if (zoomInput) zoomInput.value = String(Math.round(state.zoom * 100));
    if (zoomLabel) zoomLabel.textContent = Math.round(state.zoom * 100) + "%";
  }

  function updatePortraitCanvasRowHeight() {
    if (!root || typeof window === "undefined" || !window.matchMedia) return;
    const isPortraitMobile = window.matchMedia("(max-width: 1023px) and (orientation: portrait)").matches;
    if (!isPortraitMobile) {
      root.style.removeProperty("--ds-portrait-canvas-row");
      return;
    }
    const vh = Math.max(1, window.innerHeight || 0);
    const vw = Math.max(1, window.innerWidth || 0);
    const headerH = (() => {
      const el = root.querySelector(".ds-fullscreen-brand");
      if (!el || !root.classList.contains("ds-fullscreen")) return 0;
      const cs = window.getComputedStyle(el);
      if (cs && cs.display === "none") return 0;
      const r = el.getBoundingClientRect();
      return Math.max(0, Math.ceil(r.height || 0));
    })();
    const pad = 24;
    const availW = Math.max(120, vw - pad);
    const fitRatio = widthPx > 0 ? (heightPx / widthPx) : 0.5;
    const natural = Math.ceil(availW * fitRatio) + 18;
    const minH = Math.floor(vh * 0.28);
    const maxH = Math.floor(vh * 0.74) - headerH;
    const row = Math.max(minH, Math.min(Math.max(minH, maxH), natural));
    root.style.setProperty("--ds-portrait-canvas-row", row + "px");
  }

  function applyCanvasScale() {
    const cssScale = state.fitScale * state.zoom;
    const displayW = Math.max(1, Math.round(widthPx * cssScale));
    const displayH = Math.max(1, Math.round(heightPx * cssScale));

    canvas.setViewportTransform([1, 0, 0, 1, 0, 0]);
    canvas.setDimensions({ width: displayW, height: displayH }, { cssOnly: true });
    if (canvas.wrapperEl) {
      canvas.wrapperEl.style.width = displayW + "px";
      canvas.wrapperEl.style.height = displayH + "px";
    }
    if (canvas.lowerCanvasEl) {
      canvas.lowerCanvasEl.style.width = displayW + "px";
      canvas.lowerCanvasEl.style.height = displayH + "px";
    }
    if (canvas.upperCanvasEl) {
      canvas.upperCanvasEl.style.width = displayW + "px";
      canvas.upperCanvasEl.style.height = displayH + "px";
    }
    // Keep viewport anchors in mobile portrait full-height mode.
    // Otherwise, legacy reset clears top/bottom anchoring and re-introduces 100dvh clipping.
    const keepAnchoredViewport = isMobileViewport() && window.matchMedia && window.matchMedia("(orientation: portrait)").matches;
    if (!keepAnchoredViewport) {
      viewportEl.style.height = "";
      wrapEl.style.height = "";
    }
    canvas.requestRenderAll();
    if (typeof canvas.calcOffset === "function") canvas.calcOffset();
    syncActiveControlHandleSize();
  }

  function setZoomPercent(percent) {
    const p = Math.max(25, Math.min(300, Number(percent) || 100));
    state.zoom = p / 100;
    applyCanvasScale();
    if (zoomInput) zoomInput.value = String(Math.round(state.zoom * 100));
    if (zoomLabel) zoomLabel.textContent = Math.round(state.zoom * 100) + "%";
    setStatus(tUi("zoom_percent", "Zoom: %s%%", [Math.round(state.zoom * 100)]));
  }

  function setActual100() {
    // 1:1 preview relative to template preview pixels
    const z = 1 / Math.max(0.01, state.fitScale);
    state.zoom = Math.max(0.25, Math.min(6, z));
    applyCanvasScale();
    if (zoomInput) zoomInput.value = String(Math.round(state.zoom * 100));
    if (zoomLabel) zoomLabel.textContent = Math.round(state.zoom * 100) + "%";
    setStatus("Zoom: 100% actual.");
  }

  function redrawSystem() {
    canvas.getObjects().filter((o) => o.dsGrid || o.dsSafe).forEach((o) => canvas.remove(o));

    if (state.gridEnabled) {
      for (let x = 0; x <= widthPx; x += gridStep) {
        const l = new fabric.Line([x, 0, x, heightPx], {
          stroke: x === Math.round(widthPx / 2) ? "rgba(60,120,255,.35)" : "rgba(40,50,70,.1)",
          strokeWidth: x === Math.round(widthPx / 2) ? 2 : 1,
          selectable: false, evented: false, excludeFromExport: true
        });
        l.dsSystem = true; l.dsGrid = true;
        canvas.add(l); l.moveTo(0);
      }
      for (let y = 0; y <= heightPx; y += gridStep) {
        const l2 = new fabric.Line([0, y, widthPx, y], {
          stroke: y === Math.round(heightPx / 2) ? "rgba(60,120,255,.35)" : "rgba(40,50,70,.1)",
          strokeWidth: y === Math.round(heightPx / 2) ? 2 : 1,
          selectable: false, evented: false, excludeFromExport: true
        });
        l2.dsSystem = true; l2.dsGrid = true;
        canvas.add(l2); l2.moveTo(0);
      }
    }

    const bleedPx = mmToPx(Number(cfg.bleed_mm) || 0, previewDpi);
    if (bleedPx > 0) {
      const safe = new fabric.Rect({
        left: bleedPx, top: bleedPx,
        width: widthPx - bleedPx * 2, height: heightPx - bleedPx * 2,
        fill: "rgba(0,0,0,0)", stroke: "rgba(220,42,42,.34)", strokeDashArray: [8, 6],
        selectable: false, evented: false, excludeFromExport: true
      });
      safe.dsSystem = true; safe.dsSafe = true;
      canvas.add(safe); safe.moveTo(0);
    }
    canvas.requestRenderAll();
  }

  function withSystemGuidesHidden(fn) {
    if (!canvas || typeof fn !== "function") return fn();
    const sys = canvas.getObjects().filter((o) => !!(o && o.dsSystem));
    if (!sys.length) return fn();
    const prev = sys.map((o) => ({ o: o, v: o.visible !== false }));
    sys.forEach((o) => { o.visible = false; });
    canvas.requestRenderAll();
    const restore = () => {
      prev.forEach((row) => {
        if (!row || !row.o) return;
        row.o.visible = !!row.v;
      });
      canvas.requestRenderAll();
    };
    try {
      const out = fn();
      if (out && typeof out.then === "function") {
        return out.finally(restore);
      }
      restore();
      return out;
    } catch (e) {
      restore();
      throw e;
    }
  }

  function snapObject(o) {
    if (!state.snapEnabled || !o || isSystemObject(o)) return;
    o.left = Math.round((o.left || 0) / gridStep) * gridStep;
    o.top = Math.round((o.top || 0) / gridStep) * gridStep;
    o.setCoords();
  }

  function getLayerListTargets() {
    const targets = [];
    if (layerListEl) targets.push(layerListEl);
    root.querySelectorAll('[data-role="arrange-layer-list"]').forEach((el) => {
      if (!el) return;
      if (targets.indexOf(el) >= 0) return;
      targets.push(el);
    });
    return targets;
  }

  function applyTopLayerOrder(nowTop) {
    if (!Array.isArray(nowTop) || !nowTop.length) return;
    const bottom = nowTop.slice().reverse();
    const sysCount = canvas.getObjects().filter((x) => isSystemObject(x)).length;
    bottom.forEach((x, i) => x.moveTo(sysCount + i));
    canvas.requestRenderAll();
  }

  function moveLayerByUid(uid, direction) {
    const key = String(uid || "").trim();
    const dir = String(direction || "").toLowerCase();
    if (!key || (dir !== "up" && dir !== "down")) return false;
    const nowTop = getDesignObjects().slice().reverse();
    const from = nowTop.findIndex((x) => String(x && x.dsUid || "") === key);
    if (from < 0) return false;
    const to = dir === "up" ? Math.max(0, from - 1) : Math.min(nowTop.length - 1, from + 1);
    if (to === from) return false;
    const moved = nowTop.splice(from, 1)[0];
    nowTop.splice(to, 0, moved);
    applyTopLayerOrder(nowTop);
    if (moved) canvas.setActiveObject(moved);
    pushHistory();
    updateLayerList();
    updateProps();
    return true;
  }

  function layerIconMeta(obj) {
    const type = String((obj && obj.type) || "").toLowerCase();
    if (isTextObject(obj) && !isStickerObject(obj)) {
      return { badge: "T", kind: "Text", bg: normColor(obj && obj.fill), fg: null };
    }
    if (type === "group") return { badge: "G", kind: "Group", bg: "#e8eef8", fg: "#243447" };
    if (type === "activeSelection") return { badge: "M", kind: "Multi", bg: "#e8eef8", fg: "#243447" };
    if (isStickerObject(obj)) {
      const sourceType = String((obj && obj.dsSourceType) || "").toLowerCase();
      if (sourceType === "emoji") return { badge: "😀", kind: "Emoji", bg: "#fff5db", fg: "#7a5100" };
      if (sourceType === "icon") return { badge: "★", kind: "Icon", bg: "#eef4ff", fg: "#1f3a63" };
      return { badge: "ST", kind: "Sticker", bg: "#eef4ff", fg: "#1f3a63" };
    }
    if (type === "image") return { badge: "IMG", kind: "Image", bg: "#eef4ff", fg: "#1f3a63" };
    const shapeKindMap = {
      rect: "Rectangle",
      circle: "Circle",
      triangle: "Triangle",
      polygon: "Polygon",
      path: "Path",
      line: "Line",
      ellipse: "Ellipse"
    };
    const kind = shapeKindMap[type] || (type ? (type.charAt(0).toUpperCase() + type.slice(1)) : "Layer");
    return { badge: "◆", kind: kind, bg: "#f4f6fb", fg: "#334a66" };
  }

  function layerTitle(obj, fallbackIndex) {
    if (!obj) return "Layer " + String(fallbackIndex + 1);
    if (isTextObject(obj) && !isStickerObject(obj)) {
      const raw = String(obj.text || "").trim();
      return raw ? raw.slice(0, 34) : "Text";
    }
    if (isStickerObject(obj)) {
      const s = String(obj.dsSourceType || "").toLowerCase();
      if (s === "emoji") return "Emoji";
      if (s === "icon") return "Icon";
      return "Sticker";
    }
    if (obj.type === "image") return "Image";
    if (obj.type === "group") return "Group";
    if (obj.type === "activeSelection") return "Multi Selection";
    const t = String(obj.type || "").trim();
    if (!t) return "Layer " + String(fallbackIndex + 1);
    return t.charAt(0).toUpperCase() + t.slice(1);
  }

  function clearLayerDragIndicators() {
    root.querySelectorAll(".ds-layer-item.is-dragover").forEach((el) => el.classList.remove("is-dragover"));
  }

  function createLayerListItem(obj, active, index) {
    ensureUid(obj);
    const meta = layerIconMeta(obj);
    const li = document.createElement("li");
    li.className = "ds-layer-item" + (active === obj ? " is-active" : "");
    li.draggable = true;
    li.dataset.uid = obj.dsUid;

    li.addEventListener("dragstart", (e) => {
      state.draggedUid = obj.dsUid;
      if (e.dataTransfer) {
        e.dataTransfer.effectAllowed = "move";
        try { e.dataTransfer.setData("text/plain", String(obj.dsUid || "")); } catch (e2) {}
      }
    });
    li.addEventListener("dragend", () => {
      state.draggedUid = null;
      clearLayerDragIndicators();
    });
    li.addEventListener("dragover", (e) => {
      e.preventDefault();
      li.classList.add("is-dragover");
    });
    li.addEventListener("dragleave", () => li.classList.remove("is-dragover"));
    li.addEventListener("drop", (e) => {
      e.preventDefault();
      li.classList.remove("is-dragover");
      const fromUid = String(state.draggedUid || "").trim();
      state.draggedUid = null;
      clearLayerDragIndicators();
      const toUid = String(obj.dsUid || "").trim();
      if (!fromUid || !toUid || fromUid === toUid) return;
      const nowTop = getDesignObjects().slice().reverse();
      const from = nowTop.findIndex((x) => String(x && x.dsUid || "") === fromUid);
      const to = nowTop.findIndex((x) => String(x && x.dsUid || "") === toUid);
      if (from < 0 || to < 0) return;
      const moved = nowTop.splice(from, 1)[0];
      nowTop.splice(to, 0, moved);
      applyTopLayerOrder(nowTop);
      if (moved) canvas.setActiveObject(moved);
      pushHistory();
      updateLayerList();
      updateProps();
    });

    const select = document.createElement("button");
    select.type = "button";
    select.className = "ds-layer-select";
    select.setAttribute("title", tUi("select_layer", "Select layer"));

    const thumb = document.createElement("span");
    thumb.className = "ds-layer-thumb";
    thumb.textContent = String(meta.badge || "•");
    thumb.style.background = String(meta.bg || "#eef3fb");
    if (meta.fg) thumb.style.color = String(meta.fg);

    const textWrap = document.createElement("span");
    textWrap.className = "ds-layer-meta";

    const titleEl = document.createElement("span");
    titleEl.className = "ds-layer-title";
    titleEl.textContent = layerTitle(obj, index);

    const kindEl = document.createElement("span");
    kindEl.className = "ds-layer-subtitle";
    kindEl.textContent = String(meta.kind || tUi("layer_single", "Layer"));

    textWrap.appendChild(titleEl);
    textWrap.appendChild(kindEl);
    select.appendChild(thumb);
    select.appendChild(textWrap);
    select.addEventListener("click", () => {
      canvas.setActiveObject(obj);
      canvas.requestRenderAll();
      updateLayerList();
      updateProps();
    });

    const actions = document.createElement("div");
    actions.className = "ds-layer-actions";

    const upBtn = document.createElement("button");
    upBtn.type = "button";
    upBtn.className = "ds-layer-move ds-layer-move-up";
    upBtn.setAttribute("title", tUi("move_layer_up", "Move layer up"));
    upBtn.setAttribute("aria-label", tUi("move_layer_up", "Move layer up"));
    upBtn.textContent = "↑";
    upBtn.addEventListener("click", (e) => {
      e.preventDefault();
      e.stopPropagation();
      moveLayerByUid(obj.dsUid, "up");
    });

    const downBtn = document.createElement("button");
    downBtn.type = "button";
    downBtn.className = "ds-layer-move ds-layer-move-down";
    downBtn.setAttribute("title", tUi("move_layer_down", "Move layer down"));
    downBtn.setAttribute("aria-label", tUi("move_layer_down", "Move layer down"));
    downBtn.textContent = "↓";
    downBtn.addEventListener("click", (e) => {
      e.preventDefault();
      e.stopPropagation();
      moveLayerByUid(obj.dsUid, "down");
    });

    actions.appendChild(upBtn);
    actions.appendChild(downBtn);

    li.appendChild(select);
    li.appendChild(actions);
    return li;
  }

  function renderLayerListInto(listEl, top, active) {
    if (!listEl) return;
    listEl.innerHTML = "";
    if (!top.length) {
      listEl.innerHTML = '<li class="ds-layer-empty">' + escHtml(tUi("no_layers_yet", "No layers yet.")) + '</li>';
      return;
    }
    top.forEach((obj, index) => {
      listEl.appendChild(createLayerListItem(obj, active, index));
    });
  }

  function updateLayerList() {
    const targets = getLayerListTargets();
    if (!targets.length) return;
    const top = getDesignObjects().slice().reverse();
    const active = canvas.getActiveObject();
    targets.forEach((listEl) => renderLayerListInto(listEl, top, active));
  }

  function normColor(v) {
    if (!v || v === "transparent") return "#000000";
    const raw = String(v || "").trim();
    if (!raw) return "#000000";
    if (raw[0] === "#") {
      if (raw.length === 4) return "#" + raw[1] + raw[1] + raw[2] + raw[2] + raw[3] + raw[3];
      if (raw.length >= 7) return raw.slice(0, 7);
    }
    const m = raw.match(/^rgba?\((\d+)\s*,\s*(\d+)\s*,\s*(\d+)/i);
    const toHex = (n) => Math.max(0, Math.min(255, Number(n) || 0)).toString(16).padStart(2, "0");
    if (m) return "#" + toHex(m[1]) + toHex(m[2]) + toHex(m[3]);
    try {
      const c = document.createElement("canvas");
      c.width = 1;
      c.height = 1;
      const ctx = c.getContext("2d");
      if (!ctx) return "#000000";
      ctx.clearRect(0, 0, 1, 1);
      ctx.fillStyle = "#000000";
      ctx.fillStyle = raw;
      const resolved = String(ctx.fillStyle || "");
      if (resolved[0] === "#") {
        if (resolved.length === 4) return "#" + resolved[1] + resolved[1] + resolved[2] + resolved[2] + resolved[3] + resolved[3];
        if (resolved.length >= 7) return resolved.slice(0, 7);
      }
      const mm = resolved.match(/^rgba?\((\d+)\s*,\s*(\d+)\s*,\s*(\d+)/i);
      if (mm) return "#" + toHex(mm[1]) + toHex(mm[2]) + toHex(mm[3]);
    } catch (e) {}
    return "#000000";
  }

  function updateProps() {
    if (!propsEl) return;
    const obj = getSingleActive();
    state.syncProps = true;
    propsEl.querySelectorAll("[data-prop]").forEach((el) => {
      const p = el.getAttribute("data-prop");
      if (p === "strokeSide") {
        el.disabled = !obj || !isTextObject(obj);
      } else if (p === "fontFamily") {
        el.disabled = !obj || !isTextObject(obj);
      } else if (p === "cornerRadius") {
        el.disabled = !obj || obj.type !== "image";
      } else {
        el.disabled = !obj;
      }
      if (!obj) { el.value = el.type === "color" ? "#000000" : ""; return; }
      if (p === "text") el.value = obj.text || "";
      else if (p === "fontFamily") {
        const ff = String(obj.fontFamily || "");
        if (ff && el.tagName && el.tagName.toLowerCase() === "select") {
          let has = false;
          Array.from(el.options || []).forEach((opt) => {
            if (String(opt.value || "") === ff) has = true;
          });
          if (!has) {
            const opt = document.createElement("option");
            opt.value = ff;
            opt.textContent = ff;
            el.appendChild(opt);
          }
        }
        el.value = ff;
        try { el.style.fontFamily = ff || "inherit"; } catch (e) {}
      }
      else if (p === "fontSize") el.value = obj.fontSize || "";
      else if (p === "left") el.value = Math.round(obj.left || 0);
      else if (p === "top") el.value = Math.round(obj.top || 0);
      else if (p === "width") el.value = Math.round(obj.getScaledWidth());
      else if (p === "height") el.value = Math.round(obj.getScaledHeight());
      else if (p === "angle") el.value = Math.round(obj.angle || 0);
      else if (p === "opacity") el.value = Math.round((obj.opacity == null ? 1 : obj.opacity) * 100);
      else if (p === "fill") el.value = normColor(obj.fill);
      else if (p === "stroke") el.value = normColor(obj.stroke);
      else if (p === "strokeWidth") el.value = obj.strokeWidth || 0;
      else if (p === "strokeSide") el.value = (obj.paintFirst === "stroke") ? "outside" : "inside";
      else if (p === "cornerRadius") el.value = Math.max(0, Number(obj.dsCornerRadius || 0));
    });
    state.syncProps = false;
    updateObjectQuickButton();
    syncContextToolbar();
    if (typeof root.__dsSyncMobileObjectMode === "function") {
      root.__dsSyncMobileObjectMode();
    }
  }

  function getContextSelectionType(obj) {
    if (!obj || isSystemObject(obj)) return "none";
    if (obj.type === "activeSelection") return "multi";
    if (obj.type === "group") return "group";
    if (isStickerObject(obj) || obj.type === "image") return "image";
    if (isTextObject(obj)) return "text";
    if (isShapeObject(obj)) return "shape";
    return "none";
  }

  // Mobile object-mode taxonomy:
  // text | image | shape | svg | group | null
  function getSelectedObjectType(obj) {
    if (!obj || isSystemObject(obj)) return null;
    if (obj.type === "activeSelection") return "multi";
    if (obj.type === "group") return "group";
    if (isStickerObject(obj)) return "image";
    if (obj.type === "image") return "image";
    if (isTextObject(obj)) return "text";
    if (obj.dsSourceType === "svg") return "svg";
    if (obj.type === "path" || obj.type === "path-group" || obj.type === "polyline" || obj.type === "line") return "svg";
    if (isShapeObject(obj)) return "shape";
    return "shape";
  }
  root.__dsDetectSelectedType = () => {
    try {
      return getSelectedObjectType(canvas.getActiveObject()) || null;
    } catch (e) {
      return null;
    }
  };

  function getContextActiveTarget() {
    const a = canvas.getActiveObject();
    if (!a || isSystemObject(a)) return null;
    return a;
  }

  function getLastSelectedObjectRef() {
    const o = state.lastSelectedObject;
    if (!o || isSystemObject(o)) return null;
    if (canvas.getObjects().indexOf(o) === -1) return null;
    if (o.type === "activeSelection") return null;
    return o;
  }

  function rememberLastSelectedObjectRef(obj) {
    if (!obj || isSystemObject(obj) || obj.type === "activeSelection") return;
    state.lastSelectedObject = obj;
  }

  function captureObjectUiValues(obj) {
    if (!obj || isSystemObject(obj) || obj.type === "activeSelection") return;
    state.lastObjectUiValues = {
      type: getSelectedObjectType(obj) || "",
      fontFamily: String(obj.fontFamily || ""),
      textAlign: String(obj.textAlign || "left"),
      fontSize: String(obj.fontSize || ""),
      fill: normColor(obj.fill),
      stroke: normColor(obj.stroke),
      strokeWidth: String(obj.strokeWidth || 0),
      strokeSide: String((obj.paintFirst === "stroke") ? "outside" : "inside"),
      opacity: String(Math.round((obj.opacity == null ? 1 : obj.opacity) * 100)),
      angle: String(Math.round(obj.angle || 0)),
      cornerRadius: String(Math.max(0, Number(obj.dsCornerRadius || 0)))
    };
  }

  function getPreferredMobileObjectRef() {
    const active = canvas.getActiveObject();
    if (active && !isSystemObject(active) && active.type !== "activeSelection") return active;
    const last = getLastSelectedObjectRef();
    if (last) return last;
    const rec = Array.isArray(state.recentSelection) ? state.recentSelection : [];
    for (let i = 0; i < rec.length; i += 1) {
      const o = rec[i];
      if (!o || isSystemObject(o) || o.type === "activeSelection") continue;
      if (canvas.getObjects().indexOf(o) === -1) continue;
      return o;
    }
    return null;
  }

  function setMobileNavLabelsDirect(mode) {
    const nav = root.querySelector('[data-role="mobile-bottom-nav"]');
    if (!nav) return;
    const labels = (mode === "object")
      ? {
          insert: tUi("edit", "Edit"),
          canvas: tUi("style", "Style"),
          library: tUi("arrange", "Arrange"),
          preview: tUi("unselect", "Unselect")
        }
      : {
          insert: tUi("insert", "Insert"),
          canvas: tUi("canvas", "Canvas"),
          library: tUi("library", "Library"),
          preview: tUi("preview", "Preview")
        };
    Object.keys(labels).forEach((key) => {
      const btn = nav.querySelector('[data-mobile-action="' + key + '"]');
      if (!btn) return;
      const txt = labels[key];
      btn.textContent = txt;
      btn.setAttribute("data-label", txt);
      btn.setAttribute("aria-label", txt);
    });
    const previewBtn = nav.querySelector('[data-mobile-action="preview"]');
    if (previewBtn) {
      if (mode === "object") {
        previewBtn.style.display = "";
        previewBtn.classList.remove("is-active");
        previewBtn.setAttribute("data-mobile-object-action", "unselect-object");
      } else {
        previewBtn.style.display = "";
        previewBtn.removeAttribute("data-mobile-object-action");
      }
    }
    const insertBtn = nav.querySelector('[data-mobile-action="insert"]');
    if (insertBtn) {
      if (mode === "object") {
        const meta = getObjectInsertTabMeta();
        if (meta && meta.label) {
          insertBtn.textContent = meta.label;
          insertBtn.setAttribute("data-label", meta.label);
          insertBtn.setAttribute("aria-label", meta.label);
        }
        insertBtn.setAttribute("data-mobile-object-action", String((meta && meta.action) || "open-edit-sheet"));
      } else {
        insertBtn.removeAttribute("data-mobile-object-action");
      }
    }
    root.setAttribute("data-mobile-mode", mode === "object" ? "object" : "canvas");
  }

  function syncMobileModeDirectFromSelection() {
    const nav = root.querySelector('[data-role="mobile-bottom-nav"]');
    if (!nav) return;
    const activeEl = (typeof document !== "undefined") ? document.activeElement : null;
    const isEditingField = !!(activeEl && activeEl.closest && activeEl.closest(".ds-mobile-sheet") &&
      (activeEl.tagName === "INPUT" || activeEl.tagName === "SELECT" || activeEl.tagName === "TEXTAREA"));
    if (isEditingField) return;
    const a = canvas.getActiveObject();
    const hasSelection = !!(a && !isSystemObject(a));
    const nextMode = hasSelection ? "object" : "canvas";
    const prevMode = String(root.getAttribute("data-mobile-watchdog-mode") || "");
    setMobileNavLabelsDirect(nextMode);
    if (prevMode === nextMode) return;
    root.setAttribute("data-mobile-watchdog-mode", nextMode);
    if (hasSelection) {
      if (typeof root.__dsApplyObjectSheets === "function") root.__dsApplyObjectSheets();
      return;
    }
    if (typeof root.__dsRestoreCanvasSheets === "function") root.__dsRestoreCanvasSheets();
  }

  function startMobileModeWatchdog() {
    if (root.__dsMobileModeWatchdogStarted) return;
    root.__dsMobileModeWatchdogStarted = true;
    const tick = () => {
      try { syncMobileModeDirectFromSelection(); } catch (e) {}
    };
    tick();
    root.__dsMobileModeWatchdogTimer = window.setInterval(tick, 900);
    window.addEventListener("resize", tick, { passive: true });
    window.addEventListener("orientationchange", tick, { passive: true });
  }

  function setContextControlsVisibility(names, visible) {
    if (!contextToolbarEl || !Array.isArray(names)) return;
    names.forEach((name) => {
      contextToolbarEl.querySelectorAll('[data-ctx-control="' + name + '"]').forEach((el) => {
        el.style.display = visible ? "" : "none";
      });
    });
  }

  function canMaskActiveSelection(obj) {
    if (!obj || obj.type !== "activeSelection" || typeof obj.getObjects !== "function") return false;
    const picked = obj.getObjects().filter((o) => !isSystemObject(o));
    if (picked.length !== 2) return false;
    const images = picked.filter((o) => o.type === "image").length;
    return images === 1;
  }

  function syncContextFontOptions() {
    if (!contextFontFamilyEl || !propsEl) return;
    const src = propsEl.querySelector('[data-prop="fontFamily"]');
    if (!src) return;
    const srcOptions = Array.from(src.options || []);
    const hasOptions = contextFontFamilyEl.options && contextFontFamilyEl.options.length > 0;
    if (hasOptions && contextFontFamilyEl.getAttribute("data-fonts-ready") === "1") return;
    contextFontFamilyEl.innerHTML = "";
    srcOptions.forEach((opt) => {
      const clone = document.createElement("option");
      clone.value = String(opt.value || "");
      clone.textContent = String(opt.textContent || opt.value || "");
      contextFontFamilyEl.appendChild(clone);
    });
    contextFontFamilyEl.setAttribute("data-fonts-ready", "1");
  }

  function syncContextToolbar() {
    if (!contextToolbarEl) return;
    const hasOpenMobileSheet = !!(root && root.classList && root.classList.contains("ds-mobile-sheet-open"));
    if (hasOpenMobileSheet) {
      contextToolbarEl.classList.remove("is-visible");
      contextToolbarEl.setAttribute("aria-hidden", "true");
      contextToolbarEl.setAttribute("data-ctx-type", "none");
      contextToolbarEl.classList.remove("is-floating");
      contextToolbarEl.classList.remove("is-fixed-bottom-safe");
      contextToolbarEl.classList.remove("is-floating-above-object", "is-floating-bottom-safe", "is-floating-top-safe");
      contextToolbarEl.style.left = "";
      contextToolbarEl.style.top = "";
      contextToolbarEl.style.right = "";
      contextToolbarEl.style.bottom = "";
      return;
    }
    const obj = getContextActiveTarget();
    const type = getContextSelectionType(obj);
    const visible = type !== "none";
    // Hard-sync mobile mode directly from selection state (independent from bridge timing).
    syncMobileModeDirectFromSelection();
    const hasMobileNav = !!root.querySelector('[data-role="mobile-bottom-nav"]');
    if (hasMobileNav && typeof root.__dsSetNavModeLabels === "function") {
      root.__dsSetNavModeLabels(visible ? "object" : "canvas");
    }
    contextToolbarEl.classList.toggle("is-visible", visible);
    contextToolbarEl.setAttribute("aria-hidden", visible ? "false" : "true");
    contextToolbarEl.setAttribute("data-ctx-type", type);
    if (!visible || !obj) {
      contextToolbarEl.classList.remove("is-floating");
      contextToolbarEl.classList.remove("is-fixed-bottom-safe");
      contextToolbarEl.classList.remove("is-floating-above-object", "is-floating-bottom-safe", "is-floating-top-safe");
      contextToolbarEl.style.left = "";
      contextToolbarEl.style.top = "";
      contextToolbarEl.style.right = "";
      contextToolbarEl.style.bottom = "";
      return;
    }

    const commonSingle = [
      "rotate", "layer-up", "layer-down", "layer-front", "layer-back",
      "align-left", "align-center", "align-right", "align-top", "align-middle", "align-bottom",
      "duplicate", "delete"
    ];
    const commonSpecial = ["group", "ungroup", "mask"];
    setContextControlsVisibility(commonSingle.concat(commonSpecial), false);

    if (type === "multi") {
      setContextControlsVisibility(["group"], true);
      setContextControlsVisibility(["mask"], canMaskActiveSelection(obj));
      if (contextRotateEl) contextRotateEl.disabled = true;
    } else if (type === "group") {
      setContextControlsVisibility(commonSingle.concat(["ungroup"]), true);
      if (contextRotateEl) contextRotateEl.disabled = false;
    } else {
      setContextControlsVisibility(commonSingle, true);
      if (contextRotateEl) contextRotateEl.disabled = false;
    }

    // Object type mapping:
    // text -> font/fill/align quick actions
    // image -> crop/AI/opacity quick actions
    // shape -> fill/stroke/stroke width quick actions
    if (type === "text") {
      syncContextFontOptions();
      if (contextFontFamilyEl) {
        const ff = String(obj.fontFamily || "");
        if (ff && !Array.from(contextFontFamilyEl.options || []).some((o) => String(o.value || "") === ff)) {
          const opt = document.createElement("option");
          opt.value = ff;
          opt.textContent = ff;
          contextFontFamilyEl.appendChild(opt);
        }
        contextFontFamilyEl.value = ff;
      }
      if (contextFontSizeEl) contextFontSizeEl.value = String(obj.fontSize || "");
      contextFillEls.forEach((el) => { el.value = normColor(obj.fill); });
    } else if (type === "image") {
      if (contextOpacityEl) contextOpacityEl.value = String(Math.round((obj.opacity == null ? 1 : obj.opacity) * 100));
    } else if (type === "shape") {
      contextFillEls.forEach((el) => { el.value = normColor(obj.fill); });
      if (contextStrokeEl) contextStrokeEl.value = normColor(obj.stroke);
      if (contextStrokeWidthEl) contextStrokeWidthEl.value = String(obj.strokeWidth || 0);
    }
    if (contextRotateEl) contextRotateEl.value = String(Math.round(obj.angle || 0));
    updateContextToolbarPosition();
  }

  function updateContextToolbarPosition() {
    if (!contextToolbarEl || !canvas || !canvas.lowerCanvasEl) return;
    if (!contextToolbarEl.classList.contains("is-visible")) return;
    const isPortraitMobile = window.matchMedia("(max-width: 1023px)").matches &&
      (window.matchMedia("(orientation: portrait)").matches || (window.innerHeight >= window.innerWidth));
    if (isPortraitMobile) {
      contextToolbarEl.classList.remove("is-floating-above-object", "is-floating-bottom-safe", "is-floating-top-safe");
      contextToolbarEl.classList.add("is-fixed-bottom-safe");
      contextToolbarEl.style.left = "";
      contextToolbarEl.style.top = "";
      contextToolbarEl.style.right = "";
      contextToolbarEl.style.bottom = "";
      return;
    }
    if (!isPortraitMobile) {
      contextToolbarEl.classList.remove("is-floating");
      contextToolbarEl.classList.remove("is-fixed-bottom-safe");
      contextToolbarEl.classList.remove("is-floating-above-object", "is-floating-bottom-safe", "is-floating-top-safe");
      contextToolbarEl.style.left = "";
      contextToolbarEl.style.top = "";
      contextToolbarEl.style.right = "";
      contextToolbarEl.style.bottom = "";
      return;
    }
    const obj = getSingleActive();
    if (!obj || obj.type === "activeSelection" || isSystemObject(obj)) return;

    const lower = canvas.lowerCanvasEl;
    const lowerRect = lower.getBoundingClientRect();
    const objRect = obj.getBoundingRect(true, true);
    const scaleX = canvas.getWidth() > 0 ? (lowerRect.width / canvas.getWidth()) : 1;
    const scaleY = canvas.getHeight() > 0 ? (lowerRect.height / canvas.getHeight()) : 1;

    const anchorX = lowerRect.left + ((objRect.left + (objRect.width / 2)) * scaleX);
    const anchorTop = lowerRect.top + (objRect.top * scaleY);
    const anchorBottom = lowerRect.top + ((objRect.top + objRect.height) * scaleY);

    const topBar = root.querySelector('[data-role="mobile-topbar"]');
    const nav = root.querySelector('[data-role="mobile-bottom-nav"]');
    const cta = root.querySelector('[data-role="mobile-apply-cta"]');
    const topLimit = (topBar ? topBar.getBoundingClientRect().bottom : 0) + 12;
    const navRect = nav ? nav.getBoundingClientRect() : null;
    const ctaRect = cta ? cta.getBoundingClientRect() : null;
    const navTop = navRect ? navRect.top : window.innerHeight;
    const ctaTop = ctaRect ? ctaRect.top : window.innerHeight;
    const controlsTop = Math.min(navTop, ctaTop);
    const minGapToObject = 12;
    const minGapFromControls = 90;
    const safeBottom = controlsTop - minGapFromControls;

    contextToolbarEl.classList.add("is-floating");
    contextToolbarEl.classList.remove("is-floating-above-object", "is-floating-bottom-safe", "is-floating-top-safe");
    const toolRect = contextToolbarEl.getBoundingClientRect();
    const margin = 8;
    const maxLeft = Math.max(margin, window.innerWidth - toolRect.width - margin);

    let left = anchorX - (toolRect.width / 2);
    left = Math.max(margin, Math.min(maxLeft, left));

    const preferredTop = anchorTop - toolRect.height - minGapToObject;
    const objectTooCloseTop = preferredTop < topLimit;
    const objectTooLow = (anchorBottom + minGapToObject + toolRect.height) > safeBottom;
    const aboveFits = preferredTop >= topLimit && (preferredTop + toolRect.height) <= safeBottom;
    const bottomSafeTop = Math.max(topLimit, safeBottom - toolRect.height);
    let top = preferredTop;

    if (aboveFits) {
      contextToolbarEl.classList.add("is-floating-above-object");
    } else if (objectTooLow) {
      top = bottomSafeTop;
      contextToolbarEl.classList.add("is-floating-bottom-safe");
    } else if (objectTooCloseTop) {
      top = topLimit;
      contextToolbarEl.classList.add("is-floating-top-safe");
    } else {
      top = Math.max(topLimit, Math.min(preferredTop, bottomSafeTop));
      contextToolbarEl.classList.add("is-floating-above-object");
    }

    top = Math.max(topLimit, Math.min(top, safeBottom - toolRect.height));

    contextToolbarEl.style.left = Math.round(left) + "px";
    contextToolbarEl.style.top = Math.round(top) + "px";
    contextToolbarEl.style.right = "auto";
    contextToolbarEl.style.bottom = "auto";
  }

  function handleMobileDoubleTap(target) {
    if (!target || isSystemObject(target) || !isMobileViewport()) return;
    canvas.setActiveObject(target);
    syncContextToolbar();
    if (typeof root.__dsCloseMobileSheets === "function") {
      root.__dsCloseMobileSheets();
    }
    if (isStickerObject(target)) {
      if (contextOpacityEl && typeof contextOpacityEl.focus === "function") contextOpacityEl.focus();
      contextToolbarEl && contextToolbarEl.classList.add("is-edit-boost");
      window.setTimeout(() => { try { contextToolbarEl && contextToolbarEl.classList.remove("is-edit-boost"); } catch (e) {} }, 180);
      return;
    }
    if (isTextObject(target)) {
      focusTextObjectForEditing(target);
      contextToolbarEl && contextToolbarEl.classList.add("is-edit-boost");
      window.setTimeout(() => { try { contextToolbarEl && contextToolbarEl.classList.remove("is-edit-boost"); } catch (e) {} }, 180);
      return;
    }
    if (target.type === "image") {
      if (contextOpacityEl && typeof contextOpacityEl.focus === "function") contextOpacityEl.focus();
      contextToolbarEl && contextToolbarEl.classList.add("is-edit-boost");
      window.setTimeout(() => { try { contextToolbarEl && contextToolbarEl.classList.remove("is-edit-boost"); } catch (e) {} }, 180);
      return;
    }
    if (isShapeObject(target)) {
      const fillEl = contextFillEls && contextFillEls.length ? contextFillEls[0] : null;
      if (fillEl && typeof fillEl.focus === "function") fillEl.focus();
      contextToolbarEl && contextToolbarEl.classList.add("is-edit-boost");
      window.setTimeout(() => { try { contextToolbarEl && contextToolbarEl.classList.remove("is-edit-boost"); } catch (e) {} }, 180);
    }
  }

  function focusTextObjectForEditing(target) {
    if (!target || !isTextObject(target) || isStickerObject(target)) return false;
    const isMobileTextUi = isMobileViewport();
    const syncMobileTextEditorViewport = () => {
      try {
        const modal = root.querySelector('[data-role="mobile-text-edit-modal"]');
        if (!modal || !modal.classList.contains("is-open")) return;
        const vv = (typeof window !== "undefined" && window.visualViewport) ? window.visualViewport : null;
        if (!vv) {
          modal.style.removeProperty("top");
          modal.style.removeProperty("left");
          modal.style.removeProperty("width");
          modal.style.removeProperty("height");
          return;
        }
        modal.style.setProperty("top", Math.max(0, Math.round(vv.offsetTop || 0)) + "px", "important");
        modal.style.setProperty("left", Math.max(0, Math.round(vv.offsetLeft || 0)) + "px", "important");
        modal.style.setProperty("width", Math.max(1, Math.round(vv.width || window.innerWidth || 1)) + "px", "important");
        modal.style.setProperty("height", Math.max(1, Math.round(vv.height || window.innerHeight || 1)) + "px", "important");
      } catch (e) {}
    };
    const ensureMobileTextEditor = () => {
      let modal = root.querySelector('[data-role="mobile-text-edit-modal"]');
      if (modal) return modal;
      modal = document.createElement("div");
      modal.className = "ds-mobile-text-edit-modal";
      modal.setAttribute("data-role", "mobile-text-edit-modal");
      modal.setAttribute("aria-hidden", "true");
      modal.innerHTML =
        '<div class="ds-mobile-text-edit-card" role="dialog" aria-modal="true" aria-label="' + escAttr(tUi("edit_text", "Edit Text")) + '">' +
          '<div class="ds-mobile-text-edit-head">' +
            '<strong>' + escHtml(tUi("edit_text", "Edit Text")) + '</strong>' +
            '<button type="button" data-role="mte-close">' + escHtml(tUi("close", "Close")) + '</button>' +
          '</div>' +
          '<textarea data-role="mte-input" rows="4" spellcheck="false"></textarea>' +
          '<div class="ds-mobile-text-edit-actions">' +
            '<button type="button" data-role="mte-apply">' + escHtml(tUi("apply", "Apply")) + '</button>' +
            '<button type="button" data-role="mte-done">' + escHtml(tUi("done", "Done")) + '</button>' +
          '</div>' +
        '</div>';
      modal.addEventListener("click", (ev) => {
        const isBackdrop = ev.target === modal;
        const btn = ev.target && ev.target.closest ? ev.target.closest("button[data-role]") : null;
        const close = () => {
          modal.classList.remove("is-open");
          modal.setAttribute("aria-hidden", "true");
          modal.style.removeProperty("top");
          modal.style.removeProperty("left");
          modal.style.removeProperty("width");
          modal.style.removeProperty("height");
          const active = getSingleActive();
          if (active && isTextObject(active) && !isStickerObject(active)) {
            try { active.set("editable", false); } catch (e) {}
          }
          try { canvas.requestRenderAll(); } catch (e2) {}
        };
        const apply = () => {
          const input = modal.querySelector('[data-role="mte-input"]');
          const txt = input ? String(input.value || "") : "";
          const active = getSingleActive() || state.lastSelectedText;
          if (!active || !isTextObject(active) || isStickerObject(active)) return;
          try {
            active.set("text", txt);
            if (typeof active.setCoords === "function") active.setCoords();
            canvas.requestRenderAll();
            updateProps();
          } catch (e) {}
        };
        if (isBackdrop || (btn && btn.getAttribute("data-role") === "mte-close")) {
          close();
          return;
        }
        if (!btn) return;
        const role = String(btn.getAttribute("data-role") || "");
        if (role === "mte-apply") {
          apply();
          return;
        }
        if (role === "mte-done") {
          apply();
          close();
          pushHistory();
        }
      });
      root.appendChild(modal);
      if (!root.__dsMteViewportBound) {
        root.__dsMteViewportBound = true;
        const onViewportTick = () => {
          try { syncMobileTextEditorViewport(); } catch (e) {}
        };
        if (typeof window !== "undefined" && window.visualViewport && window.visualViewport.addEventListener) {
          window.visualViewport.addEventListener("resize", onViewportTick, { passive: true });
          window.visualViewport.addEventListener("scroll", onViewportTick, { passive: true });
        }
        window.addEventListener("orientationchange", onViewportTick, { passive: true });
        window.addEventListener("resize", onViewportTick, { passive: true });
      }
      return modal;
    };
    const openMobileTextEditor = (obj) => {
      const modal = ensureMobileTextEditor();
      if (!modal) return false;
      try {
        if (canvas.getActiveObject() !== obj) canvas.setActiveObject(obj);
      } catch (e) {}
      state.lastSelectedText = obj;
      try { obj.set("editable", false); } catch (e2) {}
      const input = modal.querySelector('[data-role="mte-input"]');
      if (input) input.value = String(obj.text || "");
      modal.classList.add("is-open");
      modal.setAttribute("aria-hidden", "false");
      try { syncMobileTextEditorViewport(); } catch (e4) {}
      window.setTimeout(() => {
        try {
          if (!input) return;
          input.focus();
          input.setSelectionRange(input.value.length, input.value.length);
          try { syncMobileTextEditorViewport(); } catch (e5) {}
        } catch (e3) {}
      }, 10);
      return true;
    };
    if (isMobileTextUi) {
      return openMobileTextEditor(target);
    }
    try {
      if (canvas.getActiveObject() !== target) canvas.setActiveObject(target);
      try {
        if (canvas && canvas.upperCanvasEl && typeof canvas.upperCanvasEl.focus === "function") {
          canvas.upperCanvasEl.focus();
        }
      } catch (e) {}
      state.lastSelectedText = target;
      target.dsForceMobileEdit = true;
      target.set("editable", true);
      if (typeof target.enterEditing === "function") target.enterEditing();
      try { if (typeof target.selectAll === "function") target.selectAll(); } catch (e) {}
      const focusHidden = () => {
        try {
          if (target.hiddenTextarea && typeof target.hiddenTextarea.focus === "function") {
            target.hiddenTextarea.setAttribute("inputmode", "text");
            target.hiddenTextarea.focus();
            if (typeof target.hiddenTextarea.click === "function") {
              target.hiddenTextarea.click();
            }
            return true;
          }
        } catch (e) {}
        return false;
      };
      // Immediate + delayed focus improves virtual keyboard reliability on mobile browsers.
      focusHidden();
      window.setTimeout(focusHidden, 0);
      window.setTimeout(() => {
        try {
          if (!target.isEditing && typeof target.enterEditing === "function") target.enterEditing();
          focusHidden();
        } catch (e) {}
      }, 20);
      window.setTimeout(focusHidden, 60);
      canvas.requestRenderAll();
      return true;
    } catch (e) {
      return false;
    }
  }

  function applyImageCornerRadius(img, radiusValue) {
    if (!img || img.type !== "image") return;
    const n = Number(radiusValue);
    const radius = Number.isFinite(n) ? Math.max(0, n) : 0;
    img.dsCornerRadius = radius;
    if (radius <= 0) {
      img.set("clipPath", null);
      return;
    }
    const w = Math.max(1, Number(img.width) || 1);
    const h = Math.max(1, Number(img.height) || 1);
    const maxR = Math.min(w, h) / 2;
    const rr = Math.min(maxR, radius);
    const cp = new fabric.Rect({
      originX: "center",
      originY: "center",
      left: 0,
      top: 0,
      width: w,
      height: h,
      rx: rr,
      ry: rr
    });
    cp.excludeFromExport = true;
    img.set("clipPath", cp);
  }

  root.__dsEnterTextEdit = () => {
    const active = getSingleActive();
    if (active && isTextObject(active) && !isStickerObject(active)) return focusTextObjectForEditing(active);
    const fallback = state.lastSelectedText;
    if (fallback && canvas.getObjects().indexOf(fallback) !== -1 && isTextObject(fallback) && !isStickerObject(fallback)) {
      return focusTextObjectForEditing(fallback);
    }
    return false;
  };

  function normalizeColorInput(value) {
    const raw = String(value == null ? "" : value).trim();
    if (!raw) return "";
    if (/^#[0-9a-f]{3}$/i.test(raw) || /^#[0-9a-f]{6}$/i.test(raw) || /^#[0-9a-f]{8}$/i.test(raw)) return raw;
    if (/^[0-9a-f]{3}$/i.test(raw) || /^[0-9a-f]{6}$/i.test(raw) || /^[0-9a-f]{8}$/i.test(raw)) return "#" + raw;
    if (/^(rgb|rgba|hsl|hsla)\(/i.test(raw)) return raw;
    return "";
  }

  function applyColorDeep(obj, prop, color) {
    if (!obj || !prop || !color) return;
    if (typeof obj.set === "function" && typeof obj[prop] !== "undefined") {
      obj.set(prop, color);
    }
    if (typeof obj.getObjects === "function") {
      (obj.getObjects() || []).forEach((child) => applyColorDeep(child, prop, color));
    }
  }

  function applyProp(name, value, options) {
    const opts = options && typeof options === "object" ? options : {};
    const skipHistory = !!opts.skipHistory;
    const skipUiSync = !!opts.skipUiSync;
    const obj = getSingleActive();
    if (!obj) {
      const active = canvas ? canvas.getActiveObject() : null;
      const isMulti = !!(active && !isSystemObject(active) && active.type === "activeSelection");
      const nMulti = Number(value);
      if (name !== "angle" || !isMulti || Number.isNaN(nMulti)) return;
      active.set("angle", nMulti);
      active.setCoords();
      canvas.requestRenderAll();
      if (!skipUiSync) updateProps();
      if (!skipHistory) pushHistory();
      return;
    }
    rememberLastSelectedObjectRef(obj);
    const n = Number(value);
    if (name === "text" && typeof obj.text !== "undefined") obj.set("text", String(value));
    if (name === "textAlign" && isTextObject(obj)) {
      const align = String(value || "").toLowerCase();
      obj.set("textAlign", (align === "center" || align === "right") ? align : "left");
    }
    if (name === "fontFamily" && isTextObject(obj)) {
      const ff = String(value || "Arial");
      applyTextFontNow(obj, ff);
      ensureWebFontLoaded(ff).then(() => {
        try {
          applyTextFontNow(obj, ff);
          window.setTimeout(() => applyTextFontNow(obj, ff), 90);
          window.setTimeout(() => applyTextFontNow(obj, ff), 260);
        } catch (e) {}
      });
    }
    if (name === "fontSize" && !Number.isNaN(n) && n > 0 && typeof obj.fontSize !== "undefined") obj.set("fontSize", n);
    if (name === "left" && !Number.isNaN(n)) obj.set("left", n);
    if (name === "top" && !Number.isNaN(n)) obj.set("top", n);
    if (name === "angle" && !Number.isNaN(n)) obj.set("angle", n);
    if (name === "opacity" && !Number.isNaN(n)) obj.set("opacity", Math.max(0, Math.min(1, n / 100)));
    if (name === "fill") {
      const c = normalizeColorInput(value);
      if (c || String(value || "").trim() === "") {
        const next = c || "#000000";
        obj.set("fill", next);
        if (isStickerObject(obj) || obj.type === "group" || obj.type === "path-group") {
          applyColorDeep(obj, "fill", next);
        }
      }
    }
    if (name === "stroke") {
      const c = normalizeColorInput(value);
      if (c || String(value || "").trim() === "") {
        const next = c || "#000000";
        obj.set("stroke", next);
        if (isStickerObject(obj) || obj.type === "group" || obj.type === "path-group") {
          applyColorDeep(obj, "stroke", next);
        }
      }
    }
    if (name === "strokeWidth" && !Number.isNaN(n)) obj.set("strokeWidth", Math.max(0, n));
    if (name === "strokeSide" && isTextObject(obj)) obj.set("paintFirst", value === "outside" ? "stroke" : "fill");
    if (name === "cornerRadius" && obj.type === "image") applyImageCornerRadius(obj, value);
    if (name === "width" && !Number.isNaN(n) && n > 0) { const w = obj.getScaledWidth(); if (w > 0) obj.scaleX = (obj.scaleX || 1) * (n / w); }
    if (name === "height" && !Number.isNaN(n) && n > 0) { const h = obj.getScaledHeight(); if (h > 0) obj.scaleY = (obj.scaleY || 1) * (n / h); }
    obj.setCoords();
    canvas.requestRenderAll();
    if (!skipUiSync) updateProps();
    if (!skipHistory) pushHistory();
  }

  function duplicateActiveObject() {
    const active = canvas ? canvas.getActiveObject() : null;
    if (!active || isSystemObject(active)) return false;
    active.clone((cloneObj) => {
      if (!cloneObj) return;
      const baseLeft = Number(active.left) || 0;
      const baseTop = Number(active.top) || 0;
      cloneObj.set({ left: baseLeft + 20, top: baseTop + 20 });
      tuneObjectForTouch(cloneObj);
      ensureUid(cloneObj);
      canvas.add(cloneObj).setActiveObject(cloneObj);
      canvas.requestRenderAll();
      updateLayerList();
      updateProps();
      pushHistory();
    }, serialProps);
    return true;
  }

  function deleteActiveObject() {
    const active = canvas ? canvas.getActiveObject() : null;
    if (!active || isSystemObject(active)) return false;
    if (active.type === "activeSelection" && typeof active.getObjects === "function") {
      const toRemove = active.getObjects().slice().filter((o) => !isSystemObject(o));
      canvas.discardActiveObject();
      toRemove.forEach((o) => canvas.remove(o));
      canvas.requestRenderAll();
      updateLayerList();
      updateProps();
      return true;
    }
    return withActive((o) => canvas.remove(o));
  }

  function withActive(fn) {
    const o = getSingleActive();
    if (!o) { setStatus("Select one object first."); return false; }
    fn(o); canvas.requestRenderAll(); updateProps(); updateLayerList(); return true;
  }

  function replaceActiveImageFromData(active, nextData, successStatus, done) {
    if (!nextData || nextData.indexOf("data:image/") !== 0) {
      if (done) done(false);
      setStatus("AI service returned invalid image.");
      return;
    }
    const original = {
      left: active.left || 0,
      top: active.top || 0,
      angle: active.angle || 0,
      scaleX: active.scaleX == null ? 1 : active.scaleX,
      scaleY: active.scaleY == null ? 1 : active.scaleY,
      opacity: active.opacity == null ? 1 : active.opacity,
      flipX: !!active.flipX,
      flipY: !!active.flipY,
      originX: active.originX || "left",
      originY: active.originY || "top"
    };
    fabric.Image.fromURL(nextData, (nextImg) => {
      if (!nextImg) {
        if (done) done(false);
        setStatus("AI result could not be loaded.");
        return;
      }
      nextImg.set(original);
      tuneObjectForTouch(nextImg);
      ensureUid(nextImg);
      canvas.remove(active);
      canvas.add(nextImg);
      canvas.setActiveObject(nextImg);
      canvas.requestRenderAll();
      pushHistory();
      if (done) done(true);
      if (successStatus) setStatus(successStatus);
    });
  }

  function removeBackgroundForActiveImage() {
    if (!aiRemoveBgEnabled) {
      setStatus("AI Background Remove is currently unavailable.");
      return;
    }
    const active = getSingleActive();
    if (!active) { setStatus("Select one image first."); return; }
    if (active.type !== "image") { setStatus("AI Background Remove works only on images."); return; }
    if (!cfg || !cfg.rest || !cfg.rest.base) { setStatus("REST endpoint is not configured."); return; }
    if (state.aiBgRemoving) return;
    state.aiBgRemoving = true;
    let imageData = "";
    try { imageData = active.toDataURL({ format: "png", multiplier: 1 }); } catch (e) {}
    if (!imageData || imageData.indexOf("data:image/") !== 0) {
      state.aiBgRemoving = false;
      setStatus("Could not prepare image for background remove.");
      return;
    }
    setStatus("Removing background...");
    fetchJSON(cfg.rest.base + "/ai/remove-bg", {
      method: "POST",
      headers: { "Content-Type": "application/json", "X-WP-Nonce": cfg.rest.nonce },
      body: JSON.stringify({ imageData: imageData })
    }).then((res) => {
      replaceActiveImageFromData(active, res && res.imageData ? String(res.imageData) : "", "Background removed.", () => {
        state.aiBgRemoving = false;
      });
    }).catch((err) => {
      state.aiBgRemoving = false;
      setStatus(err && err.message ? err.message : "AI background remove failed.");
    });
  }

  function applyImageCropFractions(img, leftP, topP, rightP, bottomP) {
    if (!img) return false;
    const l = Math.max(0, Math.min(0.98, Number(leftP) || 0));
    const t = Math.max(0, Math.min(0.98, Number(topP) || 0));
    const r = Math.max(0, Math.min(0.98, Number(rightP) || 0));
    const b = Math.max(0, Math.min(0.98, Number(bottomP) || 0));
    if ((l + r) >= 0.99 || (t + b) >= 0.99) return false;

    const srcEl = (typeof img.getElement === "function") ? img.getElement() : null;
    const srcW = Number((srcEl && (srcEl.naturalWidth || srcEl.width)) || img.width || 0);
    const srcH = Number((srcEl && (srcEl.naturalHeight || srcEl.height)) || img.height || 0);
    const baseCropX = Number(img.cropX) || 0;
    const baseCropY = Number(img.cropY) || 0;
    const baseW = Number(img.width) || srcW;
    const baseH = Number(img.height) || srcH;
    if (srcW <= 0 || srcH <= 0 || baseW <= 0 || baseH <= 0) return false;

    const newCropX = baseCropX + (baseW * l);
    const newCropY = baseCropY + (baseH * t);
    const newW = baseW * (1 - l - r);
    const newH = baseH * (1 - t - b);
    if (newW < 1 || newH < 1) return false;

    const cropX = Math.max(0, Math.min(srcW - 1, newCropX));
    const cropY = Math.max(0, Math.min(srcH - 1, newCropY));
    const nextW = Math.max(1, Math.min(srcW - cropX, newW));
    const nextH = Math.max(1, Math.min(srcH - cropY, newH));
    const keepScaleX = (img.scaleX == null) ? 1 : img.scaleX;
    const keepScaleY = (img.scaleY == null) ? 1 : img.scaleY;

    img.set({
      cropX: cropX,
      cropY: cropY,
      width: nextW,
      height: nextH,
      scaleX: keepScaleX,
      scaleY: keepScaleY,
      objectCaching: false
    });
    img.dirty = true;
    img.setCoords();
    return true;
  }

  function cancelCropSession(msg) {
    const s = state.cropSession;
    if (!s) return;
    if (s.frame && canvas.getObjects().indexOf(s.frame) !== -1) canvas.remove(s.frame);
    if (s.image) {
      s.image.set({ selectable: true, evented: true });
      s.image.setCoords();
    }
    state.cropSession = null;
    root.classList.remove("ds-crop-mode");
    const cropApply = root.querySelector('[data-role="mobile-crop-apply"]');
    if (cropApply) cropApply.setAttribute("aria-hidden", "true");
    canvas.requestRenderAll();
    if (msg) setStatus(msg);
  }

  function ensureMobileCropApplyButton() {
    let btn = root.querySelector('[data-role="mobile-crop-apply"]');
    if (btn) return btn;
    btn = document.createElement("button");
    btn.type = "button";
    btn.className = "ds-mobile-crop-apply";
    btn.setAttribute("data-role", "mobile-crop-apply");
    btn.setAttribute("aria-hidden", "true");
    btn.textContent = tUi("apply_crop", "Apply Crop");
    btn.addEventListener("click", (ev) => {
      if (ev) {
        try { ev.preventDefault(); } catch (e1) {}
        try { ev.stopPropagation(); } catch (e2) {}
      }
      if (state && state.cropSession) {
        applyCropSession();
        return;
      }
      const target = root.querySelector('.ds-toolbar [data-action="crop-image"]');
      if (target && typeof target.click === "function") {
        target.click();
      } else {
        setStatus("Crop mode is not active.");
      }
    });
    root.appendChild(btn);
    return btn;
  }

  function clampCropFrameToImage(s) {
    if (!s || !s.frame || !s.image) return;
    const b = s.image.getBoundingRect(true, true);
    const f = s.frame;
    f.scaleX = Math.max(0.02, f.scaleX || 1);
    f.scaleY = Math.max(0.02, f.scaleY || 1);
    let fw = Math.max(1, f.width * f.scaleX);
    let fh = Math.max(1, f.height * f.scaleY);
    if (fw > b.width) f.scaleX = b.width / Math.max(1, f.width);
    if (fh > b.height) f.scaleY = b.height / Math.max(1, f.height);
    fw = Math.max(1, f.width * f.scaleX);
    fh = Math.max(1, f.height * f.scaleY);
    const halfW = fw / 2;
    const halfH = fh / 2;
    f.left = Math.max(b.left + halfW, Math.min(b.left + b.width - halfW, f.left || 0));
    f.top = Math.max(b.top + halfH, Math.min(b.top + b.height - halfH, f.top || 0));
    f.setCoords();
  }

  function startCropSession(img) {
    cancelCropSession();
    const b = img.getBoundingRect(true, true);
    const frame = new fabric.Rect({
      left: b.left + (b.width / 2),
      top: b.top + (b.height / 2),
      originX: "center",
      originY: "center",
      width: Math.max(4, b.width),
      height: Math.max(4, b.height),
      fill: "rgba(10,127,115,0.08)",
      stroke: "#0a7f73",
      strokeWidth: 2,
      strokeDashArray: [8, 6],
      borderColor: "#0a7f73",
      cornerColor: "#0a7f73",
      transparentCorners: false,
      cornerSize: isTouchDevice ? 16 : 12,
      touchCornerSize: isTouchDevice ? 44 : 26,
      padding: 0,
      hasRotatingPoint: false,
      lockRotation: true
    });
    frame.dsSystem = true;
    frame.dsCropFrame = true;
    img.set({ selectable: false, evented: false });
    canvas.add(frame);
    canvas.setActiveObject(frame);
    state.cropSession = { image: img, frame: frame };
    frame.on("moving", () => clampCropFrameToImage(state.cropSession));
    frame.on("scaling", () => clampCropFrameToImage(state.cropSession));
    clampCropFrameToImage(state.cropSession);
    if (isMobilePortraitUi()) {
      root.classList.add("ds-crop-mode");
      const cropApply = ensureMobileCropApplyButton();
      if (cropApply) cropApply.setAttribute("aria-hidden", "false");
      if (typeof root.__dsCloseMobileSheets === "function") root.__dsCloseMobileSheets();
    }
    canvas.requestRenderAll();
    setStatus("Crop mode: adjust frame, then Apply Crop.");
  }

  function applyCropSession() {
    const s = state.cropSession;
    if (!s || !s.image || !s.frame) return;
    const b = s.image.getBoundingRect(true, true);
    const f = s.frame.getBoundingRect(true, true);
    const ix = Math.max(b.left, f.left);
    const iy = Math.max(b.top, f.top);
    const ir = Math.min(b.left + b.width, f.left + f.width);
    const ib = Math.min(b.top + b.height, f.top + f.height);
    const iw = ir - ix;
    const ih = ib - iy;
    if (iw < 2 || ih < 2) {
      cancelCropSession("Crop area is too small.");
      return;
    }
    const corners = [
      new fabric.Point(ix, iy),
      new fabric.Point(ir, iy),
      new fabric.Point(ir, ib),
      new fabric.Point(ix, ib)
    ];
    const inv = fabric.util.invertTransform(s.image.calcTransformMatrix());
    const local = corners.map((pt) => fabric.util.transformPoint(pt, inv));
    const imgW = Math.max(1, Number(s.image.width) || 0);
    const imgH = Math.max(1, Number(s.image.height) || 0);
    const halfW = imgW / 2;
    const halfH = imgH / 2;
    let minX = Infinity;
    let minY = Infinity;
    let maxX = -Infinity;
    let maxY = -Infinity;
    local.forEach((pt) => {
      const x = Math.max(-halfW, Math.min(halfW, Number(pt.x) || 0));
      const y = Math.max(-halfH, Math.min(halfH, Number(pt.y) || 0));
      if (x < minX) minX = x;
      if (y < minY) minY = y;
      if (x > maxX) maxX = x;
      if (y > maxY) maxY = y;
    });
    const keepW = maxX - minX;
    const keepH = maxY - minY;
    if (keepW < 1 || keepH < 1) {
      cancelCropSession("Crop area is too small.");
      return;
    }
    const leftP = Math.max(0, Math.min(0.98, (minX + halfW) / imgW));
    const topP = Math.max(0, Math.min(0.98, (minY + halfH) / imgH));
    const rightP = Math.max(0, Math.min(0.98, (halfW - maxX) / imgW));
    const bottomP = Math.max(0, Math.min(0.98, (halfH - maxY) / imgH));
    const ok = applyImageCropFractions(s.image, leftP, topP, rightP, bottomP);
    cancelCropSession(ok ? "" : "Crop failed.");
    if (!ok) return;
    canvas.setActiveObject(s.image);
    canvas.requestRenderAll();
    updateProps();
    pushHistory();
    setStatus("Image cropped.");
  }

  function cropSelectedImage() {
    if (state.cropSession) {
      applyCropSession();
      return;
    }
    const img = getSingleActive();
    if (!img || img.type !== "image") {
      setStatus("Select one image object first.");
      return;
    }
    startCropSession(img);
  }

  function rememberSelection() {
    const active = canvas.getActiveObject();
    if (!active) return;
    if (active.type === "activeSelection") {
      state.recentSelection = active.getObjects().filter((o) => !isSystemObject(o)).slice(-4);
    } else if (!isSystemObject(active)) {
      state.recentSelection = [active].concat(state.recentSelection.filter((o) => o !== active)).slice(0, 4);
    }
  }

  function resolveMaskPair() {
    const selected = canvas.getActiveObjects().filter((o) => !isSystemObject(o));
    let imageObj = selected.find((o) => o.type === "image");
    let shapeObj = selected.find((o) => o.type !== "image");
    if (imageObj && shapeObj) return { imageObj, shapeObj };

    const remembered = state.recentSelection.filter((o) => o && canvas.getObjects().indexOf(o) !== -1);
    imageObj = remembered.find((o) => o.type === "image");
    shapeObj = remembered.find((o) => o.type !== "image");
    if (imageObj && shapeObj) return { imageObj, shapeObj };

    const active = canvas.getActiveObject();
    if (active && !isSystemObject(active)) {
      if (active.type === "image") {
        const shapeFallback = remembered.find((o) => o !== active && o.type !== "image");
        if (shapeFallback) return { imageObj: active, shapeObj: shapeFallback };
      } else {
        const imgFallback = remembered.find((o) => o.type === "image");
        if (imgFallback) return { imageObj: imgFallback, shapeObj: active };
      }
    }
    return null;
  }

  function forceOpaqueClip(clip) {
    if (!clip) return;
    if (typeof clip.forEachObject === "function") {
      clip.forEachObject((child) => {
        child.set({ fill: "#000000", stroke: null, strokeWidth: 0, opacity: 1 });
      });
    } else {
      clip.set({ fill: "#000000", stroke: null, strokeWidth: 0, opacity: 1 });
    }
    clip.set({ selectable: false, evented: false });
  }

  function buildRelativeClipPath(imageObj, shapeObj, done) {
    if (!imageObj || !shapeObj) { done(null); return; }
    shapeObj.clone((clip) => {
      if (!clip) { done(null); return; }
      forceOpaqueClip(clip);

      // Convert shape transform from canvas space into image-local space,
      // so mask follows image movement/rotation/scale.
      const mImg = imageObj.calcTransformMatrix();
      const mShp = shapeObj.calcTransformMatrix();
      const invImg = fabric.util.invertTransform(mImg);
      const rel = fabric.util.multiplyTransformMatrices(invImg, mShp);
      const d = fabric.util.qrDecompose(rel);

      clip.set({
        originX: "center",
        originY: "center",
        left: d.translateX,
        top: d.translateY,
        angle: d.angle,
        scaleX: d.scaleX,
        scaleY: d.scaleY,
        skewX: d.skewX || 0,
        skewY: d.skewY || 0,
        absolutePositioned: false,
        selectable: false,
        evented: false
      });
      done(clip);
    });
  }

  function applyMask(mode) {
    const pair = resolveMaskPair();
    if (!pair) {
      setStatus("Mask: select image and shape (Shift+click both).");
      return;
    }
    const img = pair.imageObj;
    const shp = pair.shapeObj;

    buildRelativeClipPath(img, shp, (clip) => {
      if (!clip) { setStatus("Mask failed."); return; }

      if (mode === "bake") {
        img.clone((imgClone) => {
          if (!imgClone) { setStatus("Mask bake failed."); return; }
          imgClone.set({
            left: img.left, top: img.top,
            scaleX: img.scaleX, scaleY: img.scaleY,
            angle: img.angle, flipX: img.flipX, flipY: img.flipY,
            opacity: img.opacity, clipPath: clip,
            objectCaching: false
          });

          const offEl = document.createElement("canvas");
          offEl.width = widthPx;
          offEl.height = heightPx;
          const off = new fabric.StaticCanvas(offEl, { backgroundColor: "rgba(0,0,0,0)" });
          off.add(imgClone);
          off.renderAll();

          const b = shp.getBoundingRect(true, true);
          const left = Math.max(0, Math.floor(b.left));
          const top = Math.max(0, Math.floor(b.top));
          const w = Math.max(1, Math.ceil(b.width));
          const h = Math.max(1, Math.ceil(b.height));
          const data = off.toDataURL({ format: "png", left: left, top: top, width: w, height: h });
          off.dispose();

          fabric.Image.fromURL(data, (baked) => {
            if (!baked) { setStatus("Mask bake failed."); return; }
            baked.set({ left: left, top: top, selectable: true, evented: true, objectCaching: false });
            ensureUid(baked);
            canvas.remove(img);
            canvas.remove(shp);
            canvas.add(baked);
            canvas.setActiveObject(baked);
            canvas.renderAll();
            pushHistory();
            setStatus("Mask baked.");
          });
        }, serialProps);
      } else {
        img.set({ clipPath: clip, objectCaching: false });
        img.dirty = true;
        img.setCoords();
        canvas.remove(shp);
        canvas.setActiveObject(img);
        canvas.renderAll();
        pushHistory();
        setStatus("Mask applied.");
      }
    }, serialProps);
  }

  function bindContextToolbarActions() {
    if (!contextToolbarEl) return;
    const triggerAction = (actionName) => {
      const target = root.querySelector('.ds-toolbar [data-action="' + actionName + '"]');
      if (target && typeof target.click === "function") target.click();
    };
    const triggerAlign = (alignName) => {
      const target = root.querySelector('.ds-toolbar [data-align="' + alignName + '"]');
      if (target && typeof target.click === "function") target.click();
    };
    const applyTextAlign = (align) => {
      const obj = getSingleActive();
      if (!obj || !isTextObject(obj)) return;
      obj.set("textAlign", String(align || "left"));
      obj.setCoords();
      canvas.requestRenderAll();
      updateProps();
      pushHistory();
    };
    contextToolbarEl.addEventListener("click", (e) => {
      if (guardZoneSelectionLock("object-props")) return;
      const btn = e.target && e.target.closest ? e.target.closest("[data-ctx-control]") : null;
      if (!btn) return;
      const control = String(btn.getAttribute("data-ctx-control") || "");
      if (!control) return;
      if (control === "duplicate") { e.preventDefault(); triggerAction("duplicate-object"); return; }
      if (control === "delete") { e.preventDefault(); triggerAction("delete-object"); return; }
      if (control === "crop-image") { e.preventDefault(); triggerAction("crop-image"); return; }
      if (control === "ai-remove-bg") { e.preventDefault(); triggerAction("ai-remove-bg"); return; }
      if (control === "mask") { e.preventDefault(); triggerAction("mask"); return; }
      if (control === "text-align-left") { e.preventDefault(); applyTextAlign("left"); return; }
      if (control === "text-align-center") { e.preventDefault(); applyTextAlign("center"); return; }
      if (control === "text-align-right") { e.preventDefault(); applyTextAlign("right"); }
      if (control === "layer-up") { e.preventDefault(); triggerAction("layer-up"); return; }
      if (control === "layer-down") { e.preventDefault(); triggerAction("layer-down"); return; }
      if (control === "layer-front") { e.preventDefault(); triggerAction("layer-front"); return; }
      if (control === "layer-back") { e.preventDefault(); triggerAction("layer-back"); return; }
      if (control === "align-left") { e.preventDefault(); triggerAlign("left"); return; }
      if (control === "align-center") { e.preventDefault(); triggerAlign("center"); return; }
      if (control === "align-right") { e.preventDefault(); triggerAlign("right"); return; }
      if (control === "align-top") { e.preventDefault(); triggerAlign("top"); return; }
      if (control === "align-middle") { e.preventDefault(); triggerAlign("middle"); return; }
      if (control === "align-bottom") { e.preventDefault(); triggerAlign("bottom"); return; }
      if (control === "group") { e.preventDefault(); triggerAction("group"); return; }
      if (control === "ungroup") { e.preventDefault(); triggerAction("ungroup"); return; }
    });
    contextToolbarEl.addEventListener("change", (e) => {
      if (guardZoneSelectionLock("object-props")) return;
      const el = e.target;
      if (!el || !el.getAttribute) return;
      const control = String(el.getAttribute("data-ctx-control") || "");
      if (!control) return;
      if (control === "font-family") { applyProp("fontFamily", el.value); return; }
      if (control === "font-size") { applyProp("fontSize", el.value); return; }
      if (control === "fill") { applyProp("fill", el.value); return; }
      if (control === "stroke") { applyProp("stroke", el.value); return; }
      if (control === "stroke-width") { applyProp("strokeWidth", el.value); return; }
      if (control === "opacity") { applyProp("opacity", el.value); return; }
      if (control === "rotate") { applyProp("angle", el.value); }
    });
  }

  function bindBackdropClose(modalEl, closeFn) {
    if (!modalEl || typeof closeFn !== "function") return;
    modalEl.addEventListener("click", (e) => {
      if (e.target === modalEl) closeFn();
    });
  }

  function bindActions() {
    const on = (sel, fn) => {
      const el = root.querySelector(sel);
      if (!el) return;
      el.addEventListener("click", (e) => {
        const actionKey = String(el.getAttribute("data-action") || "");
        if (actionKey && guardZoneSelectionLock(actionKey)) {
          e.preventDefault();
          return;
        }
        fn(e);
      });
    };
    const navigateToTemplate = (nextId) => {
      const id = parseInt(nextId || "0", 10);
      if (!id || id === Number(cfg.id || 0)) return;
      try {
        if (root.classList.contains("ds-fullscreen")) {
          window.sessionStorage.setItem("ds_editor_fullscreen", "1");
        }
      } catch (e) {}
      persistTemplateOverride(id);
      markStartupAutoOpenZonesForTemplate(id);
      window.location.href = buildEditorUrlForTemplate(id);
    };
    const zoneButtonsWrap = root.querySelector(".ds-zone-buttons");
    if (zoneButtonsWrap) {
      zoneButtonsWrap.addEventListener("click", (e) => {
        const t = e.target && e.target.closest ? e.target.closest("[data-zone-id]") : null;
        if (!t || !t.getAttribute) return;
        const zoneId = t.getAttribute("data-zone-id");
        if (!zoneId) return;
        cancelCropSession();
        switchToZone(zoneId);
      });
    }

    on('[data-action="template-apply"]', () => {
      if (!templateSelectInput) return;
      nudgePreviewGuard(900);
      navigateToTemplate(templateSelectInput.value || "0");
    });
    if (templateSelectInput) {
      const guardTemplateInteraction = () => nudgePreviewGuard(900);
      templateSelectInput.addEventListener("pointerdown", guardTemplateInteraction, true);
      templateSelectInput.addEventListener("mousedown", guardTemplateInteraction, true);
      templateSelectInput.addEventListener("touchstart", guardTemplateInteraction, { passive: true });
      templateSelectInput.addEventListener("click", guardTemplateInteraction, true);
      templateSelectInput.addEventListener("change", () => {
        nudgePreviewGuard(900);
        navigateToTemplate(templateSelectInput.value || "0");
      });
    }

    on('[data-action="add-text"]', () => {
      const prev = canvas.getActiveObject();
      const baseLeft = Number(prev && prev.left) || 90;
      const baseTop = Number(prev && prev.top) || 90;
      const t = new fabric.IText("Text", { left: baseLeft + 20, top: baseTop + 20, fontSize: 28, fill: "#111" });
      tuneObjectForTouch(t);
      ensureUid(t);
      canvas.add(t).setActiveObject(t);
      canvas.requestRenderAll();
      pushHistory();
      if (!isMobileViewport() && typeof t.enterEditing === "function") {
        t.enterEditing();
        if (t.hiddenTextarea && typeof t.hiddenTextarea.focus === "function") t.hiddenTextarea.focus();
      }
    });
    const imgInput = root.querySelector('[data-action="upload-image"]');
    if (imgInput) {
      imgInput.addEventListener("click", (e) => {
        if (!guardZoneSelectionLock("upload-image")) return;
        e.preventDefault();
        e.stopPropagation();
      }, true);
      imgInput.addEventListener("change", (e) => {
      if (guardZoneSelectionLock("upload-image")) { e.target.value = ""; return; }
      const f = e.target.files && e.target.files[0]; if (!f) return;
      const fr = new FileReader();
      fr.onload = function (ev) {
        fabric.Image.fromURL(ev.target.result, (img) => {
          const s = Math.min(widthPx / img.width, heightPx / img.height, 1);
          const prev = canvas.getActiveObject();
          const baseLeft = Number(prev && prev.left) || 120;
          const baseTop = Number(prev && prev.top) || 120;
          img.set({ left: baseLeft + 20, top: baseTop + 20 });
          img.scale(s * 0.65);
          tuneObjectForTouch(img);
          ensureUid(img);
          canvas.add(img).setActiveObject(img);
          canvas.requestRenderAll();
          pushHistory();
        });
      };
      fr.readAsDataURL(f); e.target.value = "";
      });
    }
    const svgInput = root.querySelector('[data-action="upload-svg"]');
    if (svgInput) {
      svgInput.addEventListener("click", (e) => {
        if (!guardZoneSelectionLock("upload-svg")) return;
        e.preventDefault();
        e.stopPropagation();
      }, true);
      svgInput.addEventListener("change", (e) => {
      if (guardZoneSelectionLock("upload-svg")) { e.target.value = ""; return; }
      const f = e.target.files && e.target.files[0]; if (!f) return;
      const fr = new FileReader();
      fr.onload = function (ev) {
        fabric.loadSVGFromString(ev.target.result, (objects, options) => {
          const obj = objects.length > 1 ? fabric.util.groupSVGElements(objects, options) : objects[0];
          if (!obj) return;
          obj.dsSourceType = "svg";
          obj.set({ left: 90, top: 90, fill: obj.fill || "rgba(0,0,0,.1)", stroke: obj.stroke || "#243447" });
          tuneObjectForTouch(obj);
          ensureUid(obj);
          canvas.add(obj).setActiveObject(obj);
          canvas.requestRenderAll();
          pushHistory();
        });
      };
      fr.readAsText(f); e.target.value = "";
      });
    }
    root.querySelectorAll("[data-shape]").forEach((b) => b.addEventListener("click", () => {
      const key = "shape-" + String(b.getAttribute("data-shape") || "");
      if (guardZoneSelectionLock(key)) return;
      const t = b.getAttribute("data-shape");
      let o = null;
      if (t === "rect") o = new fabric.Rect({ left: 80, top: 80, width: 220, height: 140, fill: "rgba(0,0,0,.08)", stroke: "#243447" });
      else if (t === "circle") o = new fabric.Circle({ left: 80, top: 80, radius: 90, fill: "rgba(0,0,0,.08)", stroke: "#243447" });
      else if (t === "triangle") o = new fabric.Triangle({ left: 80, top: 80, width: 180, height: 150, fill: "rgba(0,0,0,.08)", stroke: "#243447" });
      else if (t === "star") {
        const spikes = 5, outerR = 110, innerR = 55, cx = 120, cy = 120;
        const pts = [];
        let rot = Math.PI / 2 * 3;
        const step = Math.PI / spikes;
        for (let i = 0; i < spikes; i++) {
          pts.push({ x: cx + Math.cos(rot) * outerR, y: cy + Math.sin(rot) * outerR });
          rot += step;
          pts.push({ x: cx + Math.cos(rot) * innerR, y: cy + Math.sin(rot) * innerR });
          rot += step;
        }
        o = new fabric.Polygon(pts, { left: 70, top: 70, fill: "rgba(0,0,0,.08)", stroke: "#243447", objectCaching: false });
      } else if (t === "heart") {
        const path = "M 272 464 C 146 368 64 304 64 208 C 64 136 120 80 192 80 C 240 80 280 104 304 144 C 328 104 368 80 416 80 C 488 80 544 136 544 208 C 544 304 462 368 336 464 L 304 488 Z";
        o = new fabric.Path(path, { left: 100, top: 60, fill: "rgba(0,0,0,.08)", stroke: "#243447", scaleX: 0.4, scaleY: 0.4 });
      } else if (t === "polygon") {
        const sides = Math.max(3, parseInt(prompt("Polygon sides (>=3):", "6") || "6", 10));
        const radius = 100, cx2 = 120, cy2 = 120;
        const pts2 = [];
        for (let j = 0; j < sides; j++) {
          const angle = (j / sides) * Math.PI * 2 - Math.PI / 2;
          pts2.push({ x: cx2 + Math.cos(angle) * radius, y: cy2 + Math.sin(angle) * radius });
        }
        o = new fabric.Polygon(pts2, { left: 70, top: 70, fill: "rgba(0,0,0,.08)", stroke: "#243447", objectCaching: false });
      } else {
        o = new fabric.Rect({ left: 80, top: 80, width: 180, height: 120, fill: "rgba(0,0,0,.08)", stroke: "#243447" });
      }
      tuneObjectForTouch(o); ensureUid(o); canvas.add(o).setActiveObject(o);
    }));
    on('[data-action="mask"]', () => applyMask("clip"));
    on('[data-action="mask-bake"]', () => applyMask("bake"));

    on('[data-action="duplicate-object"]', () => { duplicateActiveObject(); });
    on('[data-action="crop-image"]', () => cropSelectedImage());
    on('[data-action="crop-apply"]', () => {
      if (state.cropSession && state.cropSession.image && state.cropSession.frame) {
        applyCropSession();
        return;
      }
      const img = getSingleActive();
      if (img && img.type === "image") {
        startCropSession(img);
        setStatus("Crop mode started. Adjust frame, then Apply Crop.");
        return;
      }
      setStatus("Start Crop on an image first.");
    });
    on('[data-action="ai-remove-bg"]', () => removeBackgroundForActiveImage());
    on('[data-action="crop-cancel"]', () => cancelCropSession("Crop canceled."));
    on('[data-action="delete-object"]', () => { if (deleteActiveObject()) pushHistory(); });
    on('[data-action="object-props"]', () => {
      const active = getSingleActive();
      if (active && isTextObject(active) && !isStickerObject(active) && isMobileViewport()) {
        if (focusTextObjectForEditing(active)) {
          setStatus("Text edit mode.");
          return;
        }
      }
      if (isMobileViewport() && state.lastSelectedText && !isStickerObject(state.lastSelectedText) && canvas.getObjects().indexOf(state.lastSelectedText) !== -1) {
        if (focusTextObjectForEditing(state.lastSelectedText)) {
          setStatus("Text edit mode.");
          return;
        }
      }
      openPropsModal();
    });
    on('[data-action="close-object-props"]', () => closePropsModal());
    on('[data-action="layer-front"]', () => { if (withActive((o) => canvas.bringToFront(o))) pushHistory(); });
    on('[data-action="layer-back"]', () => { if (withActive((o) => canvas.sendToBack(o))) pushHistory(); });
    on('[data-action="layer-up"]', () => { if (withActive((o) => canvas.bringForward(o))) pushHistory(); });
    on('[data-action="layer-down"]', () => { if (withActive((o) => canvas.sendBackwards(o))) pushHistory(); });
    on('[data-action="group"]', () => { const a = canvas.getActiveObject(); if (a && a.type === "activeSelection") { const g = a.toGroup(); ensureUid(g); canvas.setActiveObject(g); pushHistory(); } });
    on('[data-action="ungroup"]', () => { const a = canvas.getActiveObject(); if (a && a.type === "group") { a.toActiveSelection(); pushHistory(); } });
    root.querySelectorAll("[data-align]").forEach((b) => b.addEventListener("click", () => {
      const mode = b.getAttribute("data-align");
      if (guardZoneSelectionLock("align-" + String(mode || ""))) return;
      withActive((o) => {
        const r = o.getBoundingRect(true, true);
        if (mode === "left") o.left -= r.left;
        if (mode === "center") o.left += widthPx / 2 - (r.left + r.width / 2);
        if (mode === "right") o.left += widthPx - (r.left + r.width);
        if (mode === "top") o.top -= r.top;
        if (mode === "middle") o.top += heightPx / 2 - (r.top + r.height / 2);
        if (mode === "bottom") o.top += heightPx - (r.top + r.height);
        o.setCoords();
      });
      pushHistory();
    }));

    on('[data-action="fit-canvas"]', () => {
      fitCanvas(true);
      setStatus("Fit by long side.");
    });
    on('[data-action="zoom-reset"]', () => setActual100());
    on('[data-action="toggle-fullscreen"]', () => {
      const isActive = () => {
        if (document.fullscreenElement) return document.fullscreenElement === root;
        return root.classList.contains("ds-fullscreen");
      };
      const syncState = () => {
        const active = isActive();
        syncFullscreenUiState(active);
      };
      const exitFs = () => {
        if (document.fullscreenElement === root && document.exitFullscreen) {
          document.exitFullscreen().catch(() => {
            root.classList.remove("ds-fullscreen");
            syncState();
          });
          return;
        }
        root.classList.remove("ds-fullscreen");
        syncState();
      };
      const enterFs = () => {
        if (document.fullscreenEnabled && root.requestFullscreen) {
          root.requestFullscreen().then(syncState).catch(() => {
            root.classList.add("ds-fullscreen");
            syncState();
          });
          return;
        }
        root.classList.add("ds-fullscreen");
        syncState();
      };
      if (isActive()) exitFs(); else enterFs();
    });
    if (zoomInput) {
      zoomInput.addEventListener("input", () => setZoomPercent(parseInt(zoomInput.value || "100", 10)));
      zoomInput.addEventListener("change", () => setZoomPercent(parseInt(zoomInput.value || "100", 10)));
    }

    on('[data-action="undo"]', () => {
      if (history.undo.length <= 1) return;
      const cur = history.undo.pop(); history.redo.push(cur);
      loadFromSnapshot(history.undo[history.undo.length - 1]);
      setStatus("Undo");
    });
    on('[data-action="redo"]', () => {
      if (!history.redo.length) return;
      const snap = history.redo.pop(); history.undo.push(snap);
      loadFromSnapshot(snap);
      setStatus("Redo");
    });

    if (gridToggle) gridToggle.addEventListener("change", () => { state.gridEnabled = !!gridToggle.checked; redrawSystem(); syncCanvasToggleButtons(); });
    if (snapToggle) snapToggle.addEventListener("change", () => { state.snapEnabled = !!snapToggle.checked; syncCanvasToggleButtons(); });
    const bleedToggle = root.querySelector('[data-opt="bleed-marks"]');
    if (bleedToggle) bleedToggle.addEventListener("change", () => { syncCanvasToggleButtons(); });

    on('[data-action="load-design"]', () => {
      const id = designIdInput ? parseInt(designIdInput.value || "0", 10) : 0;
      loadDesignById(id);
    });

    on('[data-action="export"]', () => {
      if (!requireAuthForOutput("Export")) return;
      const exportDpi = Number(cfg.dpi) || 300;
      const mul = exportDpi / previewDpi;
      const png = withSystemGuidesHidden(() => canvas.toDataURL({ format: "png", multiplier: mul }));
      const a = document.createElement("a");
      a.href = png;
      a.download = (cfg.title || "design") + ".png";
      document.body.appendChild(a); a.click(); a.remove();
    });

    on('[data-action="my-designs"]', () => openMyDesignsModal());
    on('[data-action="close-my-designs"]', () => closeMyDesignsModal());
    on('[data-action="apply-product"]', () => openApplyProductModal());
    on('[data-action="close-apply-product"]', () => closeApplyProductModal());
    on('[data-action="open-textures"]', () => openAssetLibraryModal("textures"));
    on('[data-action="open-frames"]', () => openAssetLibraryModal("frames"));
    on('[data-action="open-shapes"]', () => openAssetLibraryModal("shapes"));
    on('[data-action="open-emoticons"]', () => openAssetLibraryModal("emoticons"));
    on('[data-action="open-icons"]', () => openAssetLibraryModal("icons"));
    on('[data-action="close-asset-library"]', () => closeAssetLibraryModal());
    on('[data-action="mug-open"]', () => {
      if (enablePreview3D) {
        openPreviewModal();
        return;
      }
      openProductPreview2DModal();
    });
    on('[data-action="mug-close"]', () => closePreviewModal());
    on('[data-action="mug-reset"]', () => {
      const large = ensureLargePreview();
      if (large && typeof large.reset === "function") large.reset();
    });

    document.addEventListener("fullscreenchange", () => {
      const active = document.fullscreenElement === root;
      syncFullscreenUiState(active);
    });

  }

  
function bindMobileQuickShellActions() {
  const top = root.querySelector('[data-role="mobile-topbar"]');
  const desktopTopActions = root.querySelector(".ds-topbar-actions");
  const nav = root.querySelector('[data-role="mobile-bottom-nav"]');
  const cta = root.querySelector('[data-role="mobile-apply-cta"]');
  const sheetsWrap = root.querySelector('[data-role="mobile-sheets"]');
  const sheetBackdrop = root.querySelector('[data-role="mobile-sheet-backdrop"]');
  const mobileTemplateSelect = root.querySelector('[data-role="mobile-template-select"]');
  const mobileDesignIdInput = root.querySelector('[data-role="mobile-design-id"]');
  const mobileExitLink = root.querySelector('.ds-mobile-topbar-back[data-mobile-action="exit-flow"]');
  let classicDesktopSheetHost = null;
  let classicDesktopSheetOriginParent = null;
  let classicDesktopSheetOriginNext = null;
  let classicDesktopBackdropOriginParent = null;
  let classicDesktopBackdropOriginNext = null;
  if (!top && !nav && !cta && !sheetsWrap) return;
  // Mark bridge ready early to prevent legacy inline fallback from hijacking sheet events.
  root.__dsMobileBridgeReady = true;

  // Hard reset stale mobile shell state to avoid blocked UI from legacy handlers.
  root.classList.remove("ds-mobile-sheet-open");
  root.removeAttribute("data-mobile-sheet-open");
  if (sheetsWrap) {
    sheetsWrap.setAttribute("aria-hidden", "true");
    sheetsWrap.style.transform = "";
    sheetsWrap.style.pointerEvents = "";
  }
  if (sheetBackdrop) {
    sheetBackdrop.setAttribute("aria-hidden", "true");
    sheetBackdrop.style.opacity = "";
    sheetBackdrop.style.pointerEvents = "";
  }
  if (cta) {
    cta.setAttribute("aria-hidden", "false");
    cta.style.opacity = "";
    cta.style.pointerEvents = "";
    cta.style.transform = "";
  }

  const isMobileViewport = () =>
    window.matchMedia("(max-width: 1023px)").matches ||
    window.matchMedia("(orientation: landscape) and (pointer: coarse)").matches;
  const MOBILE_LANDSCAPE_ENABLED = true;
  const isMobilePortrait = () => {
    const narrow = isMobileViewport();
    const portraitMq = window.matchMedia("(orientation: portrait)").matches;
    const portraitBySize = window.innerHeight >= window.innerWidth;
    return narrow && (portraitMq || portraitBySize);
  };
  const isMobileLandscape = () => !!(MOBILE_LANDSCAPE_ENABLED && isMobileViewport() && !isMobilePortrait());
  const canUseMobileSheetUi = () => !!(isMobileViewport() || isClassicDesktopViewport());
  let mobileExitConfirmModal = null;
  let mobileRefreshConfirmModal = null;
  const runHardRefreshNow = () => {
    try {
      const u = new URL(window.location.href);
      u.searchParams.set("_dsr", String(Date.now()));
      window.location.href = u.toString();
      return true;
    } catch (e) {
      window.location.reload();
      return true;
    }
  };
  const getExitFallbackUrl = () => {
    const hrefAttr = mobileExitLink ? String(mobileExitLink.getAttribute("data-exit-fallback") || "").trim() : "";
    if (hrefAttr) return hrefAttr;
    try { return String(new URL("/", window.location.origin).toString()); } catch (e) {}
    return "/";
  };
  const resolveExitTargetUrl = () => {
    const fallback = getExitFallbackUrl();
    const ref = String(document.referrer || "").trim();
    if (ref) {
      try {
        const refUrl = new URL(ref, window.location.origin);
        const curUrl = new URL(window.location.href);
        const sameOrigin = refUrl.origin === curUrl.origin;
        const samePage = (refUrl.pathname + refUrl.search) === (curUrl.pathname + curUrl.search);
        if (sameOrigin && !samePage) return refUrl.toString();
      } catch (e) {}
    }
    return fallback;
  };
  const navigateExitTarget = () => {
    window.location.href = resolveExitTargetUrl();
  };
  const closeMobileExitConfirmModal = () => {
    if (!mobileExitConfirmModal) return;
    mobileExitConfirmModal.classList.remove("is-open");
    mobileExitConfirmModal.setAttribute("aria-hidden", "true");
  };
  const applyExitConfirmActionStyles = (host) => {
    if (!host || !host.querySelector) return;
    const cs = (typeof window !== "undefined" && window.getComputedStyle) ? window.getComputedStyle(root) : null;
    const accent = normalizeHexColor((cs && cs.getPropertyValue("--ds-accent")) || "", "#f89f25");
    const secondary = normalizeHexColor((cs && cs.getPropertyValue("--ds-accent-secondary")) || "", "#2f80ed");
    const btnSave = host.querySelector('button[data-action="exit-save"]');
    const btnApply = host.querySelector('button[data-action="exit-apply"]');
    const btnLeave = host.querySelector('button[data-action="exit-leave"]');
    const btnCancel = host.querySelector('button[data-action="exit-cancel"]');

    applyButtonThemeInline(
      btnSave,
      secondary,
      secondary,
      "#ffffff",
      "0 8px 18px " + secondary + "55",
      "linear-gradient(180deg, " + secondary + " 0%, " + secondary + " 100%)"
    );
    applyButtonThemeInline(
      btnApply,
      accent,
      accent,
      "#ffffff",
      "0 8px 18px " + accent + "55",
      "linear-gradient(180deg, " + accent + " 0%, " + accent + " 100%)"
    );
    applyButtonThemeInline(
      btnLeave,
      "#1a2638",
      "#0f1828",
      "#e9f1ff",
      "0 8px 18px rgba(12, 23, 40, 0.24)",
      "linear-gradient(180deg, #263349 0%, #121d30 100%)"
    );
    applyButtonThemeInline(
      btnCancel,
      "#ffffff",
      "#c5d1df",
      "#20354e",
      "0 4px 12px rgba(12, 26, 44, 0.08)",
      "none"
    );
  };
  const openMobileExitConfirmModal = () => {
    if (!mobileExitConfirmModal) {
      mobileExitConfirmModal = document.createElement("div");
      mobileExitConfirmModal.className = "ds-exit-confirm-modal";
      mobileExitConfirmModal.setAttribute("data-role", "exit-confirm-modal");
      mobileExitConfirmModal.setAttribute("aria-hidden", "true");
      mobileExitConfirmModal.innerHTML =
        '<div class="ds-exit-confirm-dialog" role="dialog" aria-modal="true" aria-label="' + escAttr(tUi("exit_designer", "Exit Designer")) + '">' +
          '<div class="ds-exit-confirm-head"><h3>' + escHtml(tUi("exit_designer_confirm", "Exit Designer?")) + '</h3></div>' +
          '<p class="ds-exit-confirm-help">' + escHtml(tUi("exit_designer_help", "Before leaving, choose what you want to do with this design.")) + '</p>' +
          '<div class="ds-exit-confirm-actions">' +
            '<button type="button" data-action="exit-save">' + escHtml(tUi("save_design", "Save Design")) + '</button>' +
            '<button type="button" data-action="exit-apply">' + escHtml(tUi("apply_to_product", "Apply to Product")) + '</button>' +
            '<button type="button" data-action="exit-leave">' + escHtml(tUi("exit_without_saving", "Exit Without Saving")) + '</button>' +
            '<button type="button" data-action="exit-cancel">' + escHtml(tUi("cancel", "Cancel")) + '</button>' +
          '</div>' +
        '</div>';
      mobileExitConfirmModal.addEventListener("click", (e) => {
        if (e.target === mobileExitConfirmModal) {
          closeMobileExitConfirmModal();
          return;
        }
        const btn = e.target && e.target.closest ? e.target.closest("button[data-action]") : null;
        if (!btn) return;
        const modalAction = String(btn.getAttribute("data-action") || "");
        if (modalAction === "exit-cancel") {
          closeMobileExitConfirmModal();
          return;
        }
        if (modalAction === "exit-leave") {
          closeMobileExitConfirmModal();
          navigateExitTarget();
          return;
        }
        if (modalAction === "exit-apply") {
          closeMobileExitConfirmModal();
          resolveMobileAction("apply");
          return;
        }
        if (modalAction === "exit-save") {
          closeMobileExitConfirmModal();
          const onSaved = () => {
            try { window.removeEventListener("ds:designSaved", onSaved); } catch (e2) {}
            navigateExitTarget();
          };
          try { window.addEventListener("ds:designSaved", onSaved); } catch (e3) {}
          resolveMobileAction("final-save");
          window.setTimeout(() => {
            try { window.removeEventListener("ds:designSaved", onSaved); } catch (e4) {}
          }, 30000);
        }
      });
      applyExitConfirmActionStyles(mobileExitConfirmModal);
      root.appendChild(mobileExitConfirmModal);
    }
    applyExitConfirmActionStyles(mobileExitConfirmModal);
    window.setTimeout(() => { try { applyExitConfirmActionStyles(mobileExitConfirmModal); } catch (e5) {} }, 0);
    mobileExitConfirmModal.classList.add("is-open");
    mobileExitConfirmModal.setAttribute("aria-hidden", "false");
  };
  const closeMobileRefreshConfirmModal = () => {
    if (!mobileRefreshConfirmModal) return;
    mobileRefreshConfirmModal.classList.remove("is-open");
    mobileRefreshConfirmModal.setAttribute("aria-hidden", "true");
  };
  const openMobileRefreshConfirmModal = () => {
    if (!mobileRefreshConfirmModal) {
      mobileRefreshConfirmModal = document.createElement("div");
      mobileRefreshConfirmModal.className = "ds-refresh-confirm-modal";
      mobileRefreshConfirmModal.setAttribute("data-role", "refresh-confirm-modal");
      mobileRefreshConfirmModal.setAttribute("aria-hidden", "true");
      mobileRefreshConfirmModal.innerHTML =
        '<div class="ds-refresh-confirm-dialog" role="dialog" aria-modal="true" aria-label="' + escAttr(tUi("refresh_designer", "Refresh Designer")) + '">' +
          '<div class="ds-refresh-confirm-head"><h3>' + escHtml(tUi("refresh_designer_confirm", "Refresh Designer?")) + '</h3></div>' +
          '<p class="ds-refresh-confirm-help">' + escHtml(tUi("unsaved_changes_will_be_lost", "Unsaved changes will be lost.")) + '</p>' +
          '<div class="ds-refresh-confirm-actions">' +
            '<button type="button" data-action="refresh-ok">' + escHtml(tUi("ok", "OK")) + '</button>' +
            '<button type="button" data-action="refresh-save">' + escHtml(tUi("save", "Save")) + '</button>' +
            '<button type="button" data-action="refresh-cancel">' + escHtml(tUi("cancel", "Cancel")) + '</button>' +
          '</div>' +
        '</div>';
      mobileRefreshConfirmModal.addEventListener("click", (e) => {
        if (e.target === mobileRefreshConfirmModal) {
          closeMobileRefreshConfirmModal();
          return;
        }
        const btn = e.target && e.target.closest ? e.target.closest("button[data-action]") : null;
        if (!btn) return;
        const modalAction = String(btn.getAttribute("data-action") || "");
        if (modalAction === "refresh-cancel") {
          closeMobileRefreshConfirmModal();
          return;
        }
        if (modalAction === "refresh-ok") {
          closeMobileRefreshConfirmModal();
          runHardRefreshNow();
          return;
        }
        if (modalAction === "refresh-save") {
          closeMobileRefreshConfirmModal();
          const onSaved = () => {
            try { window.removeEventListener("ds:designSaved", onSaved); } catch (e2) {}
            runHardRefreshNow();
          };
          try { window.addEventListener("ds:designSaved", onSaved); } catch (e3) {}
          resolveMobileAction("final-save");
          window.setTimeout(() => {
            try { window.removeEventListener("ds:designSaved", onSaved); } catch (e4) {}
          }, 30000);
        }
      });
      root.appendChild(mobileRefreshConfirmModal);
    }
    mobileRefreshConfirmModal.classList.add("is-open");
    mobileRefreshConfirmModal.setAttribute("aria-hidden", "false");
  };
  let pendingSheetSide = "right";
  const setPendingSheetSide = (side) => {
    pendingSheetSide = String(side || "").toLowerCase() === "left" ? "left" : "right";
    if (isMobileLandscape() || isClassicDesktopViewport()) {
      root.setAttribute("data-mobile-sheet-side", pendingSheetSide);
    } else {
      root.removeAttribute("data-mobile-sheet-side");
    }
  };

  const getToolbarAction = (selector) => {
    const sel = String(selector || "");
    if (!sel) return null;
    let el = root.querySelector(sel);
    if (el) return el;
    if (sel.indexOf(".ds-toolbar ") === 0) {
      el = root.querySelector(sel.replace(".ds-toolbar ", ""));
      if (el) return el;
    }
    return null;
  };

  const clickRealControl = (selector) => {
    const el = getToolbarAction(selector);
    if (!el) return false;
    el.click();
    return true;
  };

  const triggerFileInput = (selector) => {
    const input = root.querySelector(selector);
    if (!input) return false;
    input.click();
    return true;
  };

  const nudgeZoom = (delta) => {
    const zoomInput = getToolbarAction('.ds-toolbar [data-input="zoom"]');
    if (!zoomInput) return false;
    const step = Number(zoomInput.step || 5) || 5;
    const min = Number(zoomInput.min || 25) || 25;
    const max = Number(zoomInput.max || 300) || 300;
    const cur = Number(zoomInput.value || 100) || 100;
    const next = Math.max(min, Math.min(max, cur + (step * delta)));
    zoomInput.value = String(next);
    zoomInput.dispatchEvent(new Event("input", { bubbles: true }));
    zoomInput.dispatchEvent(new Event("change", { bubbles: true }));
    return true;
  };

  const syncMobileZoomUi = () => {
    const mobileZoomSlider = root.querySelector('[data-role="mobile-zoom-slider"]');
    const mobileZoomLabel = root.querySelector('[data-role="mobile-zoom-label"]');
    const baseZoomInput = getToolbarAction('.ds-toolbar [data-input="zoom"]');
    const cur = Number(baseZoomInput && baseZoomInput.value || 100) || 100;
    if (mobileZoomSlider) mobileZoomSlider.value = String(cur);
    if (mobileZoomLabel) mobileZoomLabel.textContent = String(Math.round(cur)) + "%";
  };

  const syncCanvasToggleButtons = () => {
    const map = [
      { action: "toggle-grid", opt: "grid" },
      { action: "toggle-snap", opt: "snap" },
      { action: "toggle-bleed-marks", opt: "bleed-marks" }
    ];
    map.forEach((item) => {
      const cb = getToolbarAction('.ds-toolbar input[data-opt="' + item.opt + '"]');
      const on = !!(cb && cb.checked);
      root.querySelectorAll('[data-mobile-sheet-action="' + item.action + '"]').forEach((btn) => {
        btn.classList.toggle("is-active", on);
        btn.setAttribute("aria-pressed", on ? "true" : "false");
      });
    });
  };

  const setZoomFromMobileSlider = (value) => {
    const baseZoomInput = getToolbarAction('.ds-toolbar [data-input="zoom"]');
    const next = Math.max(25, Math.min(300, Number(value) || 100));
    if (!baseZoomInput) {
      setZoomPercent(next);
      syncMobileZoomUi();
      return true;
    }
    baseZoomInput.value = String(Math.round(next));
    baseZoomInput.dispatchEvent(new Event("input", { bubbles: true }));
    baseZoomInput.dispatchEvent(new Event("change", { bubbles: true }));
    syncMobileZoomUi();
    return true;
  };

  const insertEmojiText = (emoji) => {
    const glyph = String(emoji || "").trim();
    if (!glyph) return false;
    try {
      const s = 256;
      const off = document.createElement("canvas");
      off.width = s;
      off.height = s;
      const ctx = off.getContext("2d");
      if (!ctx) throw new Error("emoji-canvas-context-missing");
      ctx.clearRect(0, 0, s, s);
      ctx.textAlign = "center";
      ctx.textBaseline = "middle";
      ctx.font = "220px \"Apple Color Emoji\",\"Segoe UI Emoji\",\"Noto Color Emoji\",sans-serif";
      ctx.fillText(glyph, s / 2, (s / 2) + 6);
      const dataUrl = off.toDataURL("image/png");
      fabric.Image.fromURL(dataUrl, (img) => {
        if (!img) return;
        img.dsSourceType = "emoji";
        img.dsSticker = true;
        img.set({
          left: Math.max(40, Math.round(widthPx * 0.26)),
          top: Math.max(40, Math.round(heightPx * 0.26))
        });
        const maxW = Math.max(80, Math.round(widthPx * 0.26));
        const maxH = Math.max(80, Math.round(heightPx * 0.26));
        const scale = Math.min(maxW / Math.max(1, img.width || 1), maxH / Math.max(1, img.height || 1), 1);
        img.scale(scale);
        tuneObjectForTouch(img);
        ensureUid(img);
        canvas.add(img).setActiveObject(img);
        canvas.requestRenderAll();
        pushHistory();
      });
    } catch (err) {
      // Fallback for environments where emoji rasterization is unavailable.
      const t = new fabric.IText(glyph, {
        left: Math.max(40, Math.round(widthPx * 0.26)),
        top: Math.max(40, Math.round(heightPx * 0.26)),
        fontSize: 64,
        fill: "#111",
        editable: false
      });
      t.dsSourceType = "emoji";
      t.dsSticker = true;
      tuneObjectForTouch(t);
      ensureUid(t);
      canvas.add(t).setActiveObject(t);
      canvas.requestRenderAll();
      pushHistory();
    }
    return true;
  };

  function cssColorToHex(cssColor) {
    const raw = String(cssColor || "").trim();
    if (!raw) return "";
    if (/^#[0-9a-f]{6}$/i.test(raw)) return raw;
    if (/^#[0-9a-f]{3}$/i.test(raw)) return "#" + raw[1] + raw[1] + raw[2] + raw[2] + raw[3] + raw[3];
    const m = raw.match(/^rgba?\((\d+)\s*,\s*(\d+)\s*,\s*(\d+)/i);
    const toHex = (n) => Math.max(0, Math.min(255, Number(n) || 0)).toString(16).padStart(2, "0");
    if (m) return "#" + toHex(m[1]) + toHex(m[2]) + toHex(m[3]);
    try {
      const c = document.createElement("canvas");
      c.width = 1; c.height = 1;
      const ctx = c.getContext("2d");
      if (!ctx) return "";
      ctx.clearRect(0, 0, 1, 1);
      ctx.fillStyle = "#000000";
      ctx.fillStyle = raw;
      const resolved = String(ctx.fillStyle || "");
      const mm = resolved.match(/^rgba?\((\d+)\s*,\s*(\d+)\s*,\s*(\d+)/i);
      if (!mm) return "";
      return "#" + toHex(mm[1]) + toHex(mm[2]) + toHex(mm[3]);
    } catch (e) {
      return "";
    }
  }

  const insertIconSvg = (name) => {
    const key = String(name || "").toLowerCase();
    const getThemeIconColor = () => {
      return "#000000";
    };
    const svgMap = {
      heart: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#1f2937" d="M128 224c-5 0-10-2-14-6L44 148C16 120 16 75 44 48c27-27 70-27 98 0l-14 14 14-14c27-27 71-27 98 0 28 27 28 72 0 100l-70 70c-4 4-9 6-14 6z"/></svg>',
      star: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#1f2937" d="m128 20 30 61 67 10-48 47 12 66-61-32-61 32 12-66-48-47 67-10z"/></svg>',
      bolt: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#1f2937" d="M144 16 48 144h56l-16 96 120-144h-56z"/></svg>',
      check: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#1f2937" d="m44 132 28-28 44 44 88-88 28 28-116 116z"/></svg>',
      arrow: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#1f2937" d="M22 116h156l-44-44 22-22 82 82-82 82-22-22 44-44H22z"/></svg>',
      pin: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#1f2937" d="M128 16c42 0 76 34 76 76 0 52-76 148-76 148S52 144 52 92c0-42 34-76 76-76zm0 106a30 30 0 1 0 0-60 30 30 0 0 0 0 60z"/></svg>',
      plus: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#1f2937" d="M112 40h32v72h72v32h-72v72h-32v-72H40v-32h72z"/></svg>',
      minus: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#1f2937" d="M40 112h176v32H40z"/></svg>',
      close: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#1f2937" d="m64 42 64 64 64-64 22 22-64 64 64 64-22 22-64-64-64 64-22-22 64-64-64-64z"/></svg>',
      cart: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#1f2937" d="M20 32h28l14 116h122l20-84H62l-4-32h160l-34 144H50L34 48H20zM92 216a16 16 0 1 1 0-32 16 16 0 0 1 0 32zm88 0a16 16 0 1 1 0-32 16 16 0 0 1 0 32z"/></svg>',
      user: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#1f2937" d="M128 26a54 54 0 1 1 0 108 54 54 0 0 1 0-108zm0 136c56 0 102 30 102 68v6H26v-6c0-38 46-68 102-68z"/></svg>',
      mail: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#1f2937" d="M24 52h208v152H24zm16 20v4l88 64 88-64v-4zm176 108V100l-88 64-88-64v80z"/></svg>',
      home: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#1f2937" d="M128 30 28 114h22v110h62v-70h32v70h62V114h22z"/></svg>',
      search: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#1f2937" d="M108 28a80 80 0 1 1-56 136l-24 24 22 22 24-24A80 80 0 1 1 108 28zm0 30a50 50 0 1 0 0 100 50 50 0 0 0 0-100z"/></svg>',
      clock: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#1f2937" d="M128 24a104 104 0 1 1 0 208 104 104 0 0 1 0-208zm16 52h-32v60l48 32 18-26-34-22z"/></svg>',
      camera: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#1f2937" d="M70 54h26l10-18h44l10 18h26a28 28 0 0 1 28 28v104a28 28 0 0 1-28 28H70a28 28 0 0 1-28-28V82a28 28 0 0 1 28-28zm58 34a46 46 0 1 0 0 92 46 46 0 0 0 0-92z"/></svg>',
      gift: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#1f2937" d="M36 82h184v38H36zm12 50h160v88H48zm66-50h28v138h-28zM92 74c-20 0-34-10-34-26 0-15 11-26 27-26 20 0 33 14 43 32-6 13-19 20-36 20zm72 0c-17 0-30-7-36-20 10-18 23-32 43-32 16 0 27 11 27 26 0 16-14 26-34 26z"/></svg>',
      phone: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#1f2937" d="M74 24h40l12 44-24 20c13 27 35 49 62 62l20-24 44 12v40c0 16-14 30-30 30C116 208 48 140 48 54c0-16 10-30 26-30z"/></svg>',
      globe: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#1f2937" d="M128 24a104 104 0 1 1 0 208 104 104 0 0 1 0-208zm68 94h-34c-2-22-9-42-20-58 28 7 49 30 54 58zm-68-58c14 16 22 36 24 58h-48c2-22 10-42 24-58zm-78 58h34c2-22 9-42 20-58-28 7-49 30-54 58zm0 20c5 28 26 51 54 58-11-16-18-36-20-58zm78 58c-14-16-22-36-24-58h48c-2 22-10 42-24 58zm34-58h34c-5 28-26 51-54 58 11-16 18-36 20-58z"/></svg>',
      tag: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#1f2937" d="M24 120V40h80l128 128-72 72L24 120zm56-52a14 14 0 1 0 0 28 14 14 0 0 0 0-28z"/></svg>',
      lock: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#1f2937" d="M72 114V82a56 56 0 1 1 112 0v32h12a20 20 0 0 1 20 20v82a20 20 0 0 1-20 20H60a20 20 0 0 1-20-20v-82a20 20 0 0 1 20-20zm30 0h52V82a26 26 0 1 0-52 0zm26 34a22 22 0 0 1 12 40v16h-24v-16a22 22 0 0 1 12-40z"/></svg>',
      unlock: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#1f2937" d="M184 114h12a20 20 0 0 1 20 20v82a20 20 0 0 1-20 20H60a20 20 0 0 1-20-20v-82a20 20 0 0 1 20-20h94V82a26 26 0 1 0-52 0H72a56 56 0 1 1 112 0zm-56 34a22 22 0 0 1 12 40v16h-24v-16a22 22 0 0 1 12-40z"/></svg>',
      flag: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#1f2937" d="M44 24h28v208H44zm28 20h120l-22 40 22 40H72z"/></svg>',
      location: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#1f2937" d="M128 20c44 0 80 36 80 80 0 56-80 136-80 136S48 156 48 100c0-44 36-80 80-80zm0 112a32 32 0 1 0 0-64 32 32 0 0 0 0 64z"/></svg>',
      bell: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#1f2937" d="M128 24a68 68 0 0 0-68 68v40L40 170v22h176v-22l-20-38V92a68 68 0 0 0-68-68zm0 208a32 32 0 0 0 30-22H98a32 32 0 0 0 30 22z"/></svg>',
      settings: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#1f2937" d="m230 142 2-28-28-8a77 77 0 0 0-8-18l14-24-20-20-24 14a77 77 0 0 0-18-8l-8-28-28 2-8 28a77 77 0 0 0-18 8L66 44 46 64l14 24a77 77 0 0 0-8 18l-28 8 2 28 28 8a77 77 0 0 0 8 18l-14 24 20 20 24-14a77 77 0 0 0 18 8l8 28 28-2 8-28a77 77 0 0 0 18-8l24 14 20-20-14-24a77 77 0 0 0 8-18zM128 164a36 36 0 1 1 0-72 36 36 0 0 1 0 72z"/></svg>',
      cloud: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#1f2937" d="M92 200h102a50 50 0 0 0 0-100 66 66 0 0 0-126-18A44 44 0 0 0 62 200z"/></svg>',
      download: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#1f2937" d="M112 28h32v96h44l-60 60-60-60h44zM36 196h184v32H36z"/></svg>',
      upload: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#1f2937" d="M188 132h-44v96h-32v-96H68l60-60zM36 196h184v32H36z"/></svg>',
      play: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#1f2937" d="M72 44v168l136-84z"/></svg>',
      pause: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#1f2937" d="M60 40h44v176H60zm92 0h44v176h-44z"/></svg>',
      refresh: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#1f2937" d="M222 68v56h-56l20-20a66 66 0 1 0 8 74l26 18a98 98 0 1 1-13-115z"/></svg>',
      link: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#1f2937" d="M102 164 86 180a38 38 0 1 1-54-54l36-36a38 38 0 0 1 54 0l6 6-22 22-6-6a8 8 0 0 0-12 0l-36 36a8 8 0 0 0 12 12l16-16zm52-72 16-16a38 38 0 0 1 54 54l-36 36a38 38 0 0 1-54 0l-6-6 22-22 6 6a8 8 0 0 0 12 0l36-36a8 8 0 0 0-12-12l-16 16z"/></svg>',
      shield: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#1f2937" d="M128 16 40 48v66c0 70 45 111 88 126 43-15 88-56 88-126V48z"/></svg>',
      bookmark: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#1f2937" d="M64 28h128a20 20 0 0 1 20 20v180l-84-52-84 52V48a20 20 0 0 1 20-20z"/></svg>',
      chat: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#1f2937" d="M36 48h184v124H96l-44 36v-36H36z"/></svg>',
      calendar: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#1f2937" d="M72 20h24v24h64V20h24v24h24a20 20 0 0 1 20 20v152a20 20 0 0 1-20 20H48a20 20 0 0 1-20-20V64a20 20 0 0 1 20-20h24zM52 96v116h152V96z"/></svg>',
      lightbulb: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#1f2937" d="M128 24a76 76 0 0 0-44 138v26h88v-26a76 76 0 0 0-44-138zm-34 184h68v24H94z"/></svg>',
      print: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#1f2937" d="M60 32h136v48H60zm-12 64h160a28 28 0 0 1 28 28v48h-32v52H52v-52H20v-48a28 28 0 0 1 28-28z"/></svg>',
      box: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#1f2937" d="m24 76 104-52 104 52-104 52zm12 22 80 40v94l-80-40zm184 0v94l-80 40v-94z"/></svg>'
    };
    const iconColor = getThemeIconColor();
    const svg = String(svgMap[key] || "").replace(/fill="#1f2937"/gi, 'fill="' + iconColor + '"');
    if (!svg) return false;
    fabric.loadSVGFromString(svg, (objects, options) => {
      const obj = objects.length > 1 ? fabric.util.groupSVGElements(objects, options) : objects[0];
      if (!obj) return;
      obj.dsSourceType = "icon";
      obj.dsSticker = true;
      obj.set({
        left: Math.max(40, Math.round(widthPx * 0.26)),
        top: Math.max(40, Math.round(heightPx * 0.26))
      });
      const maxW = Math.max(80, Math.round(widthPx * 0.26));
      const maxH = Math.max(80, Math.round(heightPx * 0.26));
      const scale = Math.min(maxW / Math.max(1, obj.width || 1), maxH / Math.max(1, obj.height || 1), 1);
      obj.scale(scale);
      tuneObjectForTouch(obj);
      ensureUid(obj);
      canvas.add(obj).setActiveObject(obj);
      canvas.requestRenderAll();
      pushHistory();
    });
    return true;
  };

  const applyTemplateSelection = () => {
    const mobileTemplateSelectLive = root.querySelector('[data-role="mobile-template-select"]');
    const desktopSelect = getToolbarAction('.ds-toolbar [data-input="template-select"]');
    const templateApplyBtn = getToolbarAction('.ds-toolbar [data-action="template-apply"]');
    const selectedId = mobileTemplateSelectLive ? String(mobileTemplateSelectLive.value || "") : "";

    if (desktopSelect && selectedId !== "") {
      desktopSelect.value = selectedId;
      desktopSelect.dispatchEvent(new Event("change", { bubbles: true }));
    }

    if (selectedId !== "") {
      try {
        markStartupAutoOpenZonesForTemplate(selectedId);
        const u = new URL(window.location.href);
        u.searchParams.set("ds_template", selectedId);
        u.searchParams.set("ds_pick", "1");
        u.searchParams.delete("ds_start");
        u.searchParams.delete("ds_zsel");
        u.searchParams.delete("ds_step");
        u.searchParams.delete("ds_open_zones");
        u.searchParams.set("_dsr", String(Date.now()));
        window.location.href = u.toString();
        return true;
      } catch (e) {}
    }

    if (templateApplyBtn && typeof templateApplyBtn.click === "function") {
      templateApplyBtn.click();
      return true;
    }
    return false;
  };

  const setActiveNav = (key) => {
    if (!nav) return;
    const active = String(key || "");
    nav.querySelectorAll("[data-mobile-action]").forEach((btn) => {
      const on = String(btn.getAttribute("data-mobile-action") || "") === active;
      btn.classList.toggle("is-active", on);
    });
  };

  const canvasSheetMap = { insert: "insert", canvas: "canvas", library: "library", preview: "preview" };
  const objectSheetMap = { insert: "edit", canvas: "style", library: "arrange", preview: "more" };
  const objectNavFromSheet = { edit: "insert", style: "canvas", arrange: "library", more: "preview" };
  const mobileSheetRefs = {
    insert: sheetsWrap ? sheetsWrap.querySelector('[data-mobile-sheet="insert"]') : null,
    canvas: sheetsWrap ? sheetsWrap.querySelector('[data-mobile-sheet="canvas"]') : null,
    library: sheetsWrap ? sheetsWrap.querySelector('[data-mobile-sheet="library"]') : null,
    preview: sheetsWrap ? sheetsWrap.querySelector('[data-mobile-sheet="preview"]') : null,
    edit: sheetsWrap ? sheetsWrap.querySelector('[data-mobile-sheet="edit"]') : null,
    style: sheetsWrap ? sheetsWrap.querySelector('[data-mobile-sheet="style"]') : null,
    arrange: sheetsWrap ? sheetsWrap.querySelector('[data-mobile-sheet="arrange"]') : null,
    more: sheetsWrap ? sheetsWrap.querySelector('[data-mobile-sheet="more"]') : null,
    emoticons: sheetsWrap ? sheetsWrap.querySelector('[data-mobile-sheet="emoticons"]') : null,
    icons: sheetsWrap ? sheetsWrap.querySelector('[data-mobile-sheet="icons"]') : null
  };
  const mobileEmojiCatalog = [
    { action: "emoji-smile", glyph: "\uD83D\uDE42", label: "Smile" },
    { action: "emoji-heart", glyph: "\u2764\uFE0F", label: "Heart" },
    { action: "emoji-fire", glyph: "\uD83D\uDD25", label: "Fire" },
    { action: "emoji-star", glyph: "\u2B50", label: "Star" },
    { action: "emoji-cool", glyph: "\uD83D\uDE0E", label: "Cool" },
    { action: "emoji-party", glyph: "\uD83C\uDF89", label: "Party" },
    { action: "emoji-check", glyph: "\u2705", label: "Check" },
    { action: "emoji-thumbs-up", glyph: "\uD83D\uDC4D", label: "Thumbs Up" },
    { action: "emoji-lightbulb", glyph: "\uD83D\uDCA1", label: "Idea" },
    { action: "emoji-rocket", glyph: "\uD83D\uDE80", label: "Rocket" },
    { action: "emoji-gift", glyph: "\uD83C\uDF81", label: "Gift" },
    { action: "emoji-camera", glyph: "\uD83D\uDCF7", label: "Camera" },
    { action: "emoji-sparkles", glyph: "\u2728", label: "Sparkles" },
    { action: "emoji-rainbow", glyph: "\uD83C\uDF08", label: "Rainbow" },
    { action: "emoji-sun", glyph: "\u2600\uFE0F", label: "Sun" },
    { action: "emoji-moon", glyph: "\uD83C\uDF19", label: "Moon" },
    { action: "emoji-flower", glyph: "\uD83C\uDF38", label: "Flower" },
    { action: "emoji-leaf", glyph: "\uD83C\uDF43", label: "Leaf" },
    { action: "emoji-music", glyph: "\uD83C\uDFB5", label: "Music" },
    { action: "emoji-headphones", glyph: "\uD83C\uDFA7", label: "Headphones" },
    { action: "emoji-coffee", glyph: "\u2615", label: "Coffee" },
    { action: "emoji-lightning", glyph: "\u26A1", label: "Lightning" },
    { action: "emoji-target", glyph: "\uD83C\uDFAF", label: "Target" },
    { action: "emoji-trophy", glyph: "\uD83C\uDFC6", label: "Trophy" },
    { action: "emoji-clap", glyph: "\uD83D\uDC4F", label: "Clap" },
    { action: "emoji-pray", glyph: "\uD83D\uDE4F", label: "Pray" },
    { action: "emoji-laugh", glyph: "\uD83D\uDE02", label: "Laugh" },
    { action: "emoji-wink", glyph: "\uD83D\uDE09", label: "Wink" },
    { action: "emoji-thinking", glyph: "\uD83E\uDD14", label: "Thinking" },
    { action: "emoji-ok", glyph: "\uD83D\uDC4C", label: "OK" },
    { action: "emoji-100", glyph: "\uD83D\uDCAF", label: "100" },
    { action: "emoji-balloon", glyph: "\uD83C\uDF88", label: "Balloon" },
    { action: "emoji-palette", glyph: "\uD83C\uDFA8", label: "Palette" },
    { action: "emoji-pen", glyph: "\u270D\uFE0F", label: "Pen" },
    { action: "emoji-scissors", glyph: "\u2702\uFE0F", label: "Scissors" },
    { action: "emoji-magnet", glyph: "\uD83E\uDDF2", label: "Magnet" },
    { action: "emoji-diamond", glyph: "\uD83D\uDC8E", label: "Diamond" },
    { action: "emoji-crown", glyph: "\uD83D\uDC51", label: "Crown" },
    { action: "emoji-wave", glyph: "\uD83C\uDF0A", label: "Wave" },
    { action: "emoji-globe", glyph: "\uD83C\uDF10", label: "Globe" }
  ];
  const mobileIconCatalog = [
    { action: "icon-heart", icon: "heart", glyph: "\u2665", label: "Heart Icon" },
    { action: "icon-star", icon: "star", glyph: "\u2605", label: "Star Icon" },
    { action: "icon-bolt", icon: "bolt", glyph: "\u26A1", label: "Bolt Icon" },
    { action: "icon-check", icon: "check", glyph: "\u2713", label: "Check Icon" },
    { action: "icon-arrow", icon: "arrow", glyph: "\u279C", label: "Arrow Icon" },
    { action: "icon-pin", icon: "pin", glyph: "\u2316", label: "Pin Icon" },
    { action: "icon-plus", icon: "plus", glyph: "\uFF0B", label: "Plus Icon" },
    { action: "icon-minus", icon: "minus", glyph: "\u2212", label: "Minus Icon" },
    { action: "icon-close", icon: "close", glyph: "\u2715", label: "Close Icon" },
    { action: "icon-cart", icon: "cart", glyph: "\uD83D\uDED2", label: "Cart Icon" },
    { action: "icon-user", icon: "user", glyph: "\uD83D\uDC64", label: "User Icon" },
    { action: "icon-mail", icon: "mail", glyph: "\u2709", label: "Mail Icon" },
    { action: "icon-home", icon: "home", glyph: "\u2302", label: "Home Icon" },
    { action: "icon-search", icon: "search", glyph: "\u2315", label: "Search Icon" },
    { action: "icon-clock", icon: "clock", glyph: "\uD83D\uDD52", label: "Clock Icon" },
    { action: "icon-camera", icon: "camera", glyph: "\uD83D\uDCF7", label: "Camera Icon" },
    { action: "icon-gift", icon: "gift", glyph: "\uD83C\uDF81", label: "Gift Icon" },
    { action: "icon-phone", icon: "phone", glyph: "\u260E", label: "Phone Icon" },
    { action: "icon-globe", icon: "globe", glyph: "\uD83C\uDF10", label: "Globe Icon" },
    { action: "icon-tag", icon: "tag", glyph: "\uD83C\uDFF7", label: "Tag Icon" },
    { action: "icon-lock", icon: "lock", glyph: "\uD83D\uDD12", label: "Lock Icon" },
    { action: "icon-unlock", icon: "unlock", glyph: "\uD83D\uDD13", label: "Unlock Icon" },
    { action: "icon-flag", icon: "flag", glyph: "\u2691", label: "Flag Icon" },
    { action: "icon-location", icon: "location", glyph: "\uD83D\uDCCD", label: "Location Icon" },
    { action: "icon-bell", icon: "bell", glyph: "\uD83D\uDD14", label: "Bell Icon" },
    { action: "icon-settings", icon: "settings", glyph: "\u2699", label: "Settings Icon" },
    { action: "icon-cloud", icon: "cloud", glyph: "\u2601", label: "Cloud Icon" },
    { action: "icon-download", icon: "download", glyph: "\u2B07", label: "Download Icon" },
    { action: "icon-upload", icon: "upload", glyph: "\u2B06", label: "Upload Icon" },
    { action: "icon-play", icon: "play", glyph: "\u25B6", label: "Play Icon" },
    { action: "icon-pause", icon: "pause", glyph: "\u275A\u275A", label: "Pause Icon" },
    { action: "icon-refresh", icon: "refresh", glyph: "\u21BB", label: "Refresh Icon" },
    { action: "icon-link", icon: "link", glyph: "\u55357\u56599", label: "Link Icon" },
    { action: "icon-shield", icon: "shield", glyph: "\uD83D\uDEE1", label: "Shield Icon" },
    { action: "icon-bookmark", icon: "bookmark", glyph: "\uD83D\uDD16", label: "Bookmark Icon" },
    { action: "icon-chat", icon: "chat", glyph: "\uD83D\uDCAC", label: "Chat Icon" },
    { action: "icon-calendar", icon: "calendar", glyph: "\uD83D\uDCC5", label: "Calendar Icon" },
    { action: "icon-lightbulb", icon: "lightbulb", glyph: "\uD83D\uDCA1", label: "Lightbulb Icon" },
    { action: "icon-print", icon: "print", glyph: "\uD83D\uDDA8", label: "Print Icon" },
    { action: "icon-box", icon: "box", glyph: "\uD83D\uDCE6", label: "Box Icon" }
  ];
  const customEmojiGroupsForMobile = getCustomAssetGroups("emoticons");
  customEmojiGroupsForMobile.forEach((group, groupIdx) => {
    const items = Array.isArray(group.items) ? group.items : [];
    items.forEach((item, itemIdx) => {
      const glyph = String(item.emoji || "").trim();
      if (!glyph) return;
      const label = String(item.label || "Emoji").trim() || "Emoji";
      const action = "emoji-custom-" + groupIdx + "-" + itemIdx;
      mobileEmojiCatalog.push({ action: action, glyph: glyph, label: label });
    });
  });
  const customIconGroupsForMobile = getCustomAssetGroups("icons");
  customIconGroupsForMobile.forEach((group, groupIdx) => {
    const items = Array.isArray(group.items) ? group.items : [];
    items.forEach((item, itemIdx) => {
      const icon = String(item.icon || "").trim().toLowerCase();
      if (!icon) return;
      const label = String(item.label || "Icon").trim() || "Icon";
      const glyph = String(item.glyph || "").trim() || icon.slice(0, 1).toUpperCase();
      const action = "icon-custom-" + groupIdx + "-" + itemIdx;
      mobileIconCatalog.push({ action: action, icon: icon, glyph: glyph, label: label });
    });
  });
  let mobileColorPickerState = null;
  const clamp01 = (n) => Math.max(0, Math.min(1, Number(n) || 0));
  const rgbToHex = (r, g, b) => {
    const toHex = (n) => Math.max(0, Math.min(255, Number(n) || 0)).toString(16).padStart(2, "0");
    return "#" + toHex(r) + toHex(g) + toHex(b);
  };
  const hsvToHex = (h, s, v) => {
    const hh = ((Number(h) || 0) % 360 + 360) % 360;
    const ss = clamp01(s);
    const vv = clamp01(v);
    const c = vv * ss;
    const x = c * (1 - Math.abs(((hh / 60) % 2) - 1));
    const m = vv - c;
    let r = 0; let g = 0; let b = 0;
    if (hh < 60) { r = c; g = x; b = 0; }
    else if (hh < 120) { r = x; g = c; b = 0; }
    else if (hh < 180) { r = 0; g = c; b = x; }
    else if (hh < 240) { r = 0; g = x; b = c; }
    else if (hh < 300) { r = x; g = 0; b = c; }
    else { r = c; g = 0; b = x; }
    const toHex = (v8) => {
      const n = Math.max(0, Math.min(255, Math.round((v8 + m) * 255)));
      return n.toString(16).padStart(2, "0");
    };
    return "#" + toHex(r) + toHex(g) + toHex(b);
  };
  const colorFromSvPoint = (h, s, v) => {
    const c = document.createElement("canvas");
    c.width = 256;
    c.height = 256;
    const ctx = c.getContext("2d");
    if (!ctx) return hsvToHex(h, s, v);
    ctx.fillStyle = "hsl(" + Math.round(((Number(h) || 0) % 360 + 360) % 360) + ",100%,50%)";
    ctx.fillRect(0, 0, c.width, c.height);
    const whiteGrad = ctx.createLinearGradient(0, 0, c.width, 0);
    whiteGrad.addColorStop(0, "rgba(255,255,255,1)");
    whiteGrad.addColorStop(1, "rgba(255,255,255,0)");
    ctx.fillStyle = whiteGrad;
    ctx.fillRect(0, 0, c.width, c.height);
    const blackGrad = ctx.createLinearGradient(0, 0, 0, c.height);
    blackGrad.addColorStop(0, "rgba(0,0,0,0)");
    blackGrad.addColorStop(1, "rgba(0,0,0,1)");
    ctx.fillStyle = blackGrad;
    ctx.fillRect(0, 0, c.width, c.height);
    const x = Math.max(0, Math.min(c.width - 1, Math.round(clamp01(s) * (c.width - 1))));
    const y = Math.max(0, Math.min(c.height - 1, Math.round((1 - clamp01(v)) * (c.height - 1))));
    const px = ctx.getImageData(x, y, 1, 1).data;
    return rgbToHex(px[0], px[1], px[2]);
  };
  const hexToHsv = (hex) => {
    const raw = String(hex || "").trim();
    const m = raw.match(/^#?([0-9a-f]{6})$/i);
    if (!m) return { h: 0, s: 0, v: 0 };
    const v = m[1];
    const r = parseInt(v.slice(0, 2), 16) / 255;
    const g = parseInt(v.slice(2, 4), 16) / 255;
    const b = parseInt(v.slice(4, 6), 16) / 255;
    const max = Math.max(r, g, b);
    const min = Math.min(r, g, b);
    const d = max - min;
    let h = 0;
    if (d !== 0) {
      if (max === r) h = 60 * (((g - b) / d) % 6);
      else if (max === g) h = 60 * (((b - r) / d) + 2);
      else h = 60 * (((r - g) / d) + 4);
    }
    if (h < 0) h += 360;
    const s = max === 0 ? 0 : d / max;
    const vv = max;
    return { h: h, s: s, v: vv };
  };
  const ensureMobileColorPicker = () => {
    if (mobileColorPickerState) return mobileColorPickerState;
    if (typeof window !== "undefined" && window.iro && typeof window.iro.ColorPicker === "function") {
      const wrap = document.createElement("div");
      wrap.className = "ds-mobile-colorpicker";
      wrap.setAttribute("data-role", "mobile-colorpicker");
      wrap.setAttribute("aria-hidden", "true");
      wrap.innerHTML =
        '<div class="ds-mobile-colorpicker-card">' +
        '<div class="ds-mobile-colorpicker-head"><strong>Pick Color</strong><button type="button" data-role="mc-close">Close</button></div>' +
        '<div class="ds-mobile-colorpicker-iro" data-role="mc-iro"></div>' +
        '<div class="ds-mobile-colorpicker-foot"><span data-role="mc-preview"></span><code data-role="mc-hex">#000000</code></div>' +
        '</div>';
      root.appendChild(wrap);
      const closeBtn = wrap.querySelector('[data-role="mc-close"]');
      const iroHost = wrap.querySelector('[data-role="mc-iro"]');
      const preview = wrap.querySelector('[data-role="mc-preview"]');
      const hexEl = wrap.querySelector('[data-role="mc-hex"]');
      const st = { wrap: wrap, input: null, picker: null };
      const picker = new window.iro.ColorPicker(iroHost, {
        width: 280,
        color: "#000000",
        borderWidth: 1,
        borderColor: "#d1ddeb",
        layout: [
          { component: window.iro.ui.Box },
          { component: window.iro.ui.Slider, options: { sliderType: "hue" } }
        ]
      });
      st.picker = picker;
      picker.on("color:change", (color) => {
        const hex = String((color && color.hexString) || "#000000");
        if (preview) preview.style.background = hex;
        if (hexEl) hexEl.textContent = hex;
        if (st.input) {
          st.input.value = hex;
          const mapMobileProp = (propName) => {
            const p = String(propName || "");
            if (p === "fill-hex") return "fill";
            if (p === "stroke-hex") return "stroke";
            return p;
          };
          const styleProp = mapMobileProp(st.input.getAttribute ? st.input.getAttribute("data-mobile-style-prop") : "");
          const arrangeProp = String(st.input.getAttribute ? (st.input.getAttribute("data-mobile-arrange-prop") || "") : "");
          const syncColorUiFields = (prop, value, sourceEl) => {
            const p = String(prop || "");
            if (!p) return;
            try {
              root.querySelectorAll('[data-mobile-style-prop="' + p + '"]').forEach((el) => {
                if (!el || el === sourceEl) return;
                el.value = String(value || "");
              });
            } catch (e0) {}
            try {
              if (propsEl) {
                const propInput = propsEl.querySelector('[data-prop="' + p + '"]');
                if (propInput) propInput.value = String(value || "");
              }
            } catch (e1) {}
          };
          if (styleProp) {
            try { applyProp(styleProp, hex, { skipHistory: true, skipUiSync: true }); } catch (e0) {}
            syncColorUiFields(styleProp, hex, st.input);
          }
          if (arrangeProp) {
            try { applyProp(arrangeProp, hex, { skipHistory: true, skipUiSync: true }); } catch (e1) {}
          }
          try { st.input.dispatchEvent(new Event("input", { bubbles: true })); } catch (e1) {}
          try { st.input.dispatchEvent(new Event("change", { bubbles: true })); } catch (e2) {}
        }
      });
      if (closeBtn) {
        closeBtn.addEventListener("click", () => {
          wrap.classList.remove("is-open");
          wrap.setAttribute("aria-hidden", "true");
        });
      }
      wrap.addEventListener("click", (e) => {
        if (e.target === wrap) {
          wrap.classList.remove("is-open");
          wrap.setAttribute("aria-hidden", "true");
        }
      });
      st.open = (input) => {
        st.input = input || null;
        const startHex = normColor(String(input && input.value ? input.value : "#000000"));
        try { picker.color.hexString = startHex; } catch (e) {}
        if (preview) preview.style.background = startHex;
        if (hexEl) hexEl.textContent = startHex;
        wrap.classList.add("is-open");
        wrap.setAttribute("aria-hidden", "false");
      };
      mobileColorPickerState = st;
      return st;
    }
    const wrap = document.createElement("div");
    wrap.className = "ds-mobile-colorpicker";
    wrap.setAttribute("data-role", "mobile-colorpicker");
    wrap.setAttribute("aria-hidden", "true");
    wrap.innerHTML =
      '<div class="ds-mobile-colorpicker-card">' +
      '<div class="ds-mobile-colorpicker-head"><strong>Pick Color</strong><button type="button" data-role="mc-close">Close</button></div>' +
      '<div class="ds-mobile-colorpicker-sv" data-role="mc-sv"><span data-role="mc-dot"></span></div>' +
      '<div class="ds-mobile-colorpicker-hue"><div class="ds-mobile-colorpicker-hue-strip" data-role="mc-hue-strip"><span data-role="mc-hue-dot"></span></div></div>' +
      '<div class="ds-mobile-colorpicker-foot"><span data-role="mc-preview"></span><code data-role="mc-hex">#000000</code></div>' +
      '</div>';
    root.appendChild(wrap);
    const sv = wrap.querySelector('[data-role="mc-sv"]');
    const dot = wrap.querySelector('[data-role="mc-dot"]');
    const hueStrip = wrap.querySelector('[data-role="mc-hue-strip"]');
    const hueDot = wrap.querySelector('[data-role="mc-hue-dot"]');
    const preview = wrap.querySelector('[data-role="mc-preview"]');
    const hexEl = wrap.querySelector('[data-role="mc-hex"]');
    const closeBtn = wrap.querySelector('[data-role="mc-close"]');
    const st = { wrap: wrap, sv: sv, dot: dot, hueStrip: hueStrip, hueDot: hueDot, preview: preview, hexEl: hexEl, input: null, h: 0, s: 0, v: 0 };
    const render = (emit) => {
      const color = colorFromSvPoint(st.h, st.s, st.v);
      sv.style.setProperty("--hue-color", "hsl(" + Math.round(st.h) + ",100%,50%)");
      dot.style.left = (st.s * 100) + "%";
      dot.style.top = ((1 - st.v) * 100) + "%";
      if (hueDot) hueDot.style.left = ((st.h / 360) * 100) + "%";
      if (preview) preview.style.background = color;
      if (hexEl) hexEl.textContent = color;
      if (emit && st.input) {
        st.input.value = color;
        try { st.input.dispatchEvent(new Event("input", { bubbles: true })); } catch (e1) {}
        try { st.input.dispatchEvent(new Event("change", { bubbles: true })); } catch (e2) {}
      }
    };
    const setSVFromEvent = (e) => {
      if (!sv) return;
      const r = sv.getBoundingClientRect();
      const x = Math.max(0, Math.min(r.width, (e.clientX || 0) - r.left));
      const y = Math.max(0, Math.min(r.height, (e.clientY || 0) - r.top));
      st.s = r.width > 0 ? (x / r.width) : 0;
      st.v = r.height > 0 ? (1 - (y / r.height)) : 0;
      render(true);
    };
    const setHueFromEvent = (e) => {
      if (!hueStrip) return;
      const r = hueStrip.getBoundingClientRect();
      const x = Math.max(0, Math.min(r.width, (e.clientX || 0) - r.left));
      const ratio = r.width > 0 ? (x / r.width) : 0;
      st.h = Math.max(0, Math.min(360, ratio * 360));
      render(true);
    };
    const bindPointerDrag = (el, onMove) => {
      if (!el) return;
      let dragging = false;
      el.addEventListener("pointerdown", (e) => {
        e.preventDefault();
        dragging = true;
        dragDbg("picker:pointerdown", { target: el && el.getAttribute ? el.getAttribute("data-role") : "", pointerType: e && e.pointerType, buttons: e && e.buttons, id: e && e.pointerId });
        try { el.setPointerCapture(e.pointerId); } catch (er) {}
        onMove(e);
      });
      el.addEventListener("pointermove", (e) => {
        if (!dragging && !(e.buttons & 1)) return;
        e.preventDefault();
        onMove(e);
      });
      el.addEventListener("pointerup", (e) => {
        dragging = false;
        dragDbg("picker:pointerup", { target: el && el.getAttribute ? el.getAttribute("data-role") : "", pointerType: e && e.pointerType, buttons: e && e.buttons, id: e && e.pointerId });
      });
      el.addEventListener("pointercancel", (e) => {
        dragging = false;
        dragDbg("picker:pointercancel", { target: el && el.getAttribute ? el.getAttribute("data-role") : "", pointerType: e && e.pointerType, buttons: e && e.buttons, id: e && e.pointerId });
      });
    };
    bindPointerDrag(sv, setSVFromEvent);
    bindPointerDrag(hueStrip, setHueFromEvent);
    if (hueStrip) hueStrip.addEventListener("click", setHueFromEvent);
    if (closeBtn) {
      closeBtn.addEventListener("click", () => {
        wrap.classList.remove("is-open");
        wrap.setAttribute("aria-hidden", "true");
      });
    }
    wrap.addEventListener("click", (e) => {
      if (e.target === wrap) {
        wrap.classList.remove("is-open");
        wrap.setAttribute("aria-hidden", "true");
      }
    });
    st.open = (input) => {
      st.input = input || null;
      const start = hexToHsv(String(input && input.value ? input.value : "#000000"));
      st.h = start.h; st.s = start.s; st.v = start.v;
      render(false);
      wrap.classList.add("is-open");
      wrap.setAttribute("aria-hidden", "false");
    };
    mobileColorPickerState = st;
    return st;
  };
  const ensureColorMixers = (scope) => {
    const host = (scope && scope.querySelectorAll) ? scope : root;
    if (!host || !host.querySelectorAll) return;
    host.querySelectorAll('input[type="color"]').forEach((input) => {
      if (!input || input.hasAttribute("data-ds-mixer-bound")) return;
      input.setAttribute("data-ds-mixer-bound", "1");
      if (!input.hasAttribute("data-ds-mobile-picker-bound")) {
        input.setAttribute("data-ds-mobile-picker-bound", "1");
        const openMobilePicker = (e) => {
          if (!isMobileViewport()) return;
          e.preventDefault();
          e.stopPropagation();
          const picker = ensureMobileColorPicker();
          picker.open(input);
        };
        input.addEventListener("pointerdown", openMobilePicker, true);
        input.addEventListener("click", openMobilePicker, true);
      }
    });
  };
  const buildMobileSymbolSheets = () => {
    const emojiSheet = mobileSheetRefs.emoticons;
    const iconSheet = mobileSheetRefs.icons;
    const emojiGrid = emojiSheet ? emojiSheet.querySelector(".ds-mobile-icons-grid") : null;
    const iconGrid = iconSheet ? iconSheet.querySelector(".ds-mobile-icons-grid") : null;
    if (emojiGrid) {
      emojiGrid.innerHTML = mobileEmojiCatalog.map((item) =>
        '<button type="button" class="ds-mobile-icon-tile" data-mobile-sheet-action="' + escAttr(item.action) + '" data-emoji="' + escAttr(item.glyph) + '" aria-label="' + escAttr(item.label) + '">' + escHtml(item.glyph) + "</button>"
      ).join("");
    }
    if (iconGrid) {
      iconGrid.innerHTML = mobileIconCatalog.map((item) =>
        '<button type="button" class="ds-mobile-icon-tile" data-mobile-sheet-action="' + escAttr(item.action) + '" data-icon="' + escAttr(item.icon) + '" data-icon-color="#000000" style="color:#000000" aria-label="' + escAttr(item.label) + '">' + escHtml(item.glyph) + "</button>"
      ).join("");
    }
  };
  buildMobileSymbolSheets();
  ensureColorMixers(root);
  const mobileSheetBase = {};
  ["insert", "canvas", "library", "preview"].forEach((k) => {
    const s = mobileSheetRefs[k];
    if (!s) return;
    const h = s.querySelector(".ds-mobile-sheet-head h4");
    const g = s.querySelector(".ds-mobile-sheet-grid");
    mobileSheetBase[k] = {
      title: h ? String(h.textContent || "") : "",
      html: g ? String(g.innerHTML || "") : ""
    };
  });
  try {
    if (isClassicDesktopViewport()) {
      ensureDesktopClassicCanvasQuickActions();
    }
  } catch (e) {}

    const navModes = {
    canvas: {
      insert: tUi("insert", "Insert"),
      canvas: tUi("canvas", "Canvas"),
      library: tUi("library", "Library"),
      preview: tUi("preview", "Preview")
    },
    object: {
      insert: tUi("edit", "Edit"),
      canvas: tUi("style", "Style"),
      library: tUi("arrange", "Arrange"),
      preview: tUi("unselect", "Unselect")
    }
  };

  function getObjectInsertTabMeta() {
    const active = canvas.getActiveObject();
    if (!active || isSystemObject(active)) {
      return { label: tUi("edit", "Edit"), action: "open-edit-sheet" };
    }
    if (active.type === "activeSelection") {
      return { label: tUi("edit", "Edit"), action: "open-edit-sheet" };
    }
    if (active.type === "group") {
      return { label: tUi("edit", "Edit"), action: "open-edit-sheet" };
    }
    if (isTextObject(active) && !isStickerObject(active)) {
      return { label: tUi("edit", "Edit"), action: "edit-text" };
    }
    return { label: tUi("edit", "Edit"), action: "open-edit-sheet" };
  }

  const setNavModeLabels = (mode) => {
    const lockToPrimaryLandscape = isLandscapeMobileViewport() && !isModernUi();
    const effectiveMode = lockToPrimaryLandscape ? "canvas" : mode;
    const cfg = navModes[effectiveMode] || navModes.canvas;
    let insertBtn = null;
    if (nav) {
      nav.querySelectorAll("[data-mobile-action]").forEach((btn) => {
        const key = String(btn.getAttribute("data-mobile-action") || "");
        if (!cfg[key]) return;
        const txt = cfg[key];
        btn.textContent = txt;
        btn.setAttribute("data-label", txt);
        btn.setAttribute("aria-label", txt);
      });
      const previewBtn = nav.querySelector('[data-mobile-action="preview"]');
      if (previewBtn) {
        if (effectiveMode === "object") {
          previewBtn.style.display = "";
          previewBtn.classList.remove("is-active");
          previewBtn.textContent = tUi("unselect", "Unselect");
          previewBtn.setAttribute("data-label", tUi("unselect", "Unselect"));
          previewBtn.setAttribute("aria-label", tUi("unselect", "Unselect"));
          previewBtn.setAttribute("data-mobile-object-action", "unselect-object");
        } else {
          previewBtn.style.display = "";
          previewBtn.removeAttribute("data-mobile-object-action");
          const txt = navModes.canvas.preview;
          previewBtn.textContent = txt;
          previewBtn.setAttribute("data-label", txt);
          previewBtn.setAttribute("aria-label", txt);
        }
      }
      insertBtn = nav.querySelector('[data-mobile-action="insert"]');
      if (insertBtn) {
        if (effectiveMode === "object") {
          const meta = getObjectInsertTabMeta();
          if (meta && meta.label) {
            insertBtn.textContent = meta.label;
            insertBtn.setAttribute("data-label", meta.label);
            insertBtn.setAttribute("aria-label", meta.label);
          }
          insertBtn.setAttribute("data-mobile-object-action", String((meta && meta.action) || "open-edit-sheet"));
        } else {
          insertBtn.removeAttribute("data-mobile-object-action");
        }
      }
    }
    // Mirror mode labels/actions to desktop classic left rail.
    const desktopNav = root.querySelector(".ds-desktop-sheet-nav");
    if (desktopNav) {
      const setDesktopLabel = (actionKey, text) => {
        const btn = desktopNav.querySelector('.ds-desktop-sheet-nav-btn[data-mobile-action="' + String(actionKey || "") + '"]');
        if (!btn) return null;
        const labelEl = btn.querySelector(".ds-desktop-sheet-nav-label");
        if (labelEl) labelEl.textContent = String(text || "");
        btn.setAttribute("data-label", String(text || ""));
        btn.setAttribute("aria-label", String(text || ""));
        btn.setAttribute("title", String(text || ""));
        return btn;
      };
      const insertText = String((insertBtn && insertBtn.getAttribute("data-label")) || cfg.insert || tUi("insert", "Insert"));
      setDesktopLabel("insert", insertText);
      setDesktopLabel("canvas", String(cfg.canvas || tUi("canvas", "Canvas")));
      setDesktopLabel("library", String(cfg.library || tUi("library", "Library")));
      const previewText = effectiveMode === "object" ? tUi("unselect", "Unselect") : String(cfg.preview || tUi("preview", "Preview"));
      const previewBtnDesktop = setDesktopLabel("preview", previewText);
      const insertBtnDesktop = desktopNav.querySelector('.ds-desktop-sheet-nav-btn[data-mobile-action="insert"]');
      if (insertBtnDesktop) {
        const insertObjectAction = String((insertBtn && insertBtn.getAttribute("data-mobile-object-action")) || "");
        if (effectiveMode === "object" && insertObjectAction) insertBtnDesktop.setAttribute("data-mobile-object-action", insertObjectAction);
        else insertBtnDesktop.removeAttribute("data-mobile-object-action");
      }
      if (previewBtnDesktop) {
        if (effectiveMode === "object") previewBtnDesktop.setAttribute("data-mobile-object-action", "unselect-object");
        else previewBtnDesktop.removeAttribute("data-mobile-object-action");
      }
      try { applyUnifiedUiIcons(desktopNav); } catch (e) {}
    }
    if (nav) {
      try { applyUnifiedUiIcons(nav); } catch (e) {}
    }
  };
  root.__dsSetNavModeLabels = setNavModeLabels;

  const resolveSheetKey = (navAction) => {
    const action = String(navAction || "");
    if (!action) return "";
    const isPrimaryNavAction = (action === "insert" || action === "canvas" || action === "library" || action === "preview");
    const mode = (editorUiState.mobileMode === "object" || String(root.getAttribute("data-mobile-mode") || "") === "object")
      ? "object"
      : "canvas";
    const map = mode === "object" ? objectSheetMap : canvasSheetMap;
    if (isPrimaryNavAction) return String(map[action] || action);
    if (mobileSheetRefs[action]) return action;
    return String(map[action] || action);
  };

  const navActionFromSheetKey = (sheetKey) => {
    const key = String(sheetKey || "");
    if (!key) return "";
    if (objectNavFromSheet[key]) return objectNavFromSheet[key];
    return key;
  };

  const fontOptionsHtmlForMobile = () => {
    const src = propsEl ? propsEl.querySelector('[data-prop="fontFamily"]') : null;
    if (!src || !src.options) return "";
    return Array.from(src.options).map((opt) => {
      const v = String(opt.value || "");
      const t = String(opt.textContent || v);
      return '<option value="' + escAttr(v) + '">' + escHtml(t) + '</option>';
    }).join("");
  };

  const getMobileFontEntries = () => {
    const src = propsEl ? propsEl.querySelector('[data-prop="fontFamily"]') : null;
    if (!src || !src.options) return [];
    return Array.from(src.options).map((opt) => {
      const value = String(opt.value || "").trim();
      const label = String(opt.textContent || value || "").trim();
      return { value, label };
    }).filter((it) => !!it.value);
  };

  let mobileFontPickerPreloadPromise = null;
  let mobileFontPickerPreloadKey = "";

  const preloadMobileFontPickerFonts = () => {
    const list = getMobileFontEntries();
    if (!list.length) return Promise.resolve(false);
    const stacks = [];
    const seen = {};
    list.forEach((it) => {
      const stack = String((it && it.value) || "").trim();
      const key = String(primaryFontFamilyFromStack(stack) || stack).toLowerCase();
      if (!stack || !key || seen[key]) return;
      seen[key] = 1;
      stacks.push(stack);
    });
    const key = stacks.join("|");
    if (mobileFontPickerPreloadPromise && mobileFontPickerPreloadKey === key) return mobileFontPickerPreloadPromise;
    mobileFontPickerPreloadKey = key;
    mobileFontPickerPreloadPromise = Promise.all(
      stacks.map((stack) => ensureWebFontLoaded(stack).catch(() => false))
    ).then(() => true).catch(() => false);
    return mobileFontPickerPreloadPromise;
  };

  const findMobileFontEntryByValue = (value) => {
    const v = String(value || "").trim();
    if (!v) return null;
    const list = getMobileFontEntries();
    for (let i = 0; i < list.length; i += 1) {
      if (String(list[i].value || "") === v) return list[i];
    }
    return null;
  };

  const getCurrentMobileFontValue = () => {
    const active = getSingleActive() || getPreferredMobileObjectRef();
    if (active && isTextObject(active) && !isStickerObject(active)) {
      return String(active.fontFamily || "").trim();
    }
    const fromProps = propsEl ? propsEl.querySelector('[data-prop="fontFamily"]') : null;
    if (fromProps) return String(fromProps.value || "").trim();
    return "";
  };

  const syncMobileFontFieldUi = (value) => {
    const styleSheet = mobileSheetRefs.style;
    if (!styleSheet) return;
    const btn = styleSheet.querySelector('[data-mobile-sheet-action="open-font-picker"]');
    if (!btn) return;
    const display = btn.querySelector('[data-role="mobile-font-current-label"]');
    const entry = findMobileFontEntryByValue(value);
    const label = String((entry && entry.label) || value || tUi("choose_font", "Choose font"));
    if (display) display.textContent = label;
    try { btn.style.fontFamily = String(value || "inherit"); } catch (e) {}
  };

  const syncMobileStrokeSideToggleUi = (value) => {
    const styleSheet = mobileSheetRefs.style;
    if (!styleSheet) return;
    const btn = styleSheet.querySelector('[data-role="mobile-stroke-side-toggle"]');
    if (!btn) return;
    const mode = String(value || "").trim() === "outside" ? "outside" : "inside";
    btn.setAttribute("data-value", mode);
    btn.setAttribute("aria-pressed", mode === "outside" ? "true" : "false");
  };

  const syncMobileTextAlignButtons = (value) => {
    const styleSheet = mobileSheetRefs.style;
    if (!styleSheet) return;
    const mode = String(value || "").trim().toLowerCase();
    const current = (mode === "center" || mode === "right") ? mode : "left";
    const map = [
      { action: "text-align-left", mode: "left" },
      { action: "text-align-center", mode: "center" },
      { action: "text-align-right", mode: "right" }
    ];
    map.forEach((item) => {
      styleSheet.querySelectorAll('[data-mobile-sheet-action="' + item.action + '"]').forEach((btn) => {
        const on = item.mode === current;
        btn.classList.toggle("is-active", on);
        btn.setAttribute("aria-pressed", on ? "true" : "false");
      });
    });
  };

  const closeMobileFontPickerModal = () => {
    const modal = root.querySelector('[data-role="mobile-font-picker-modal"]');
    if (!modal) return;
    modal.classList.remove("is-open");
    modal.setAttribute("aria-hidden", "true");
  };

  const openMobileFontPickerModal = () => {
    let modal = root.querySelector('[data-role="mobile-font-picker-modal"]');
    if (!modal) {
      modal = document.createElement("div");
      modal.className = "ds-font-picker-modal";
      modal.setAttribute("data-role", "mobile-font-picker-modal");
      modal.setAttribute("aria-hidden", "true");
      modal.innerHTML =
        '<div class="ds-font-picker-dialog" role="dialog" aria-modal="true" aria-label="' + escAttr(tUi("choose_font_title", "Choose Font")) + '">' +
          '<div class="ds-font-picker-top">' +
            "<h3>" + escHtml(tUi("choose_font_title", "Choose Font")) + "</h3>" +
            '<button type="button" data-action="font-picker-close">' + escHtml(tUi("close", "Close")) + '</button>' +
          "</div>" +
          '<div class="ds-font-picker-list" data-role="mobile-font-picker-list"></div>' +
        "</div>";
      modal.addEventListener("click", (e) => {
        if (e.target === modal) closeMobileFontPickerModal();
      });
      modal.addEventListener("click", (e) => {
        const t = e.target && e.target.closest ? e.target.closest("button[data-action]") : null;
        if (!t) return;
        const action = String(t.getAttribute("data-action") || "");
        if (action === "font-picker-close") {
          closeMobileFontPickerModal();
          return;
        }
        if (action === "font-picker-apply") {
          const value = String(t.getAttribute("data-font-value") || "").trim();
          if (!value) return;
          applyProp("fontFamily", value);
          syncMobileFontFieldUi(value);
          window.setTimeout(() => {
            try { hydrateMobileStyleFieldsFromObject(getSingleActive() || getPreferredMobileObjectRef()); } catch (e2) {}
          }, 0);
          closeMobileFontPickerModal();
        }
      });
      root.appendChild(modal);
    }

    modal.classList.add("is-open");
    modal.setAttribute("aria-hidden", "false");
    const listEl = modal.querySelector('[data-role="mobile-font-picker-list"]');
    const renderFontList = () => {
      if (!listEl) return;
      const current = getCurrentMobileFontValue();
      const list = getMobileFontEntries();
      listEl.innerHTML = list.map((font) => {
        const isActive = String(font.value) === String(current) ? " is-active" : "";
        return (
          '<button type="button" class="ds-font-picker-item' + isActive + '" data-action="font-picker-apply" data-font-value="' + escAttr(font.value) + '" style="font-family:' + escAttr(font.value) + ';">' +
            '<span class="ds-font-picker-item-name">' + escHtml(font.label) + "</span>" +
          "</button>"
        );
      }).join("");
    };
    if (listEl) {
      listEl.innerHTML = '<div class="ds-font-picker-loading">' + escHtml(tUi("loading_fonts", "Loading fonts...")) + '</div>';
    }
    preloadMobileFontPickerFonts().then(() => {
      renderFontList();
    });
  };

  const ensureMobileSelectHasOption = (selectEl, value) => {
    if (!selectEl) return;
    const v = String(value || "").trim();
    if (!v) return;
    const has = Array.from(selectEl.options || []).some((opt) => String(opt.value || "") === v);
    if (has) return;
    const opt = document.createElement("option");
    opt.value = v;
    opt.textContent = v;
    selectEl.appendChild(opt);
  };

  const hydrateMobileStyleFieldsFromObject = (obj) => {
    const snap = state.lastObjectUiValues || null;
    if (!obj && !snap) return;
    const styleSheet = mobileSheetRefs.style;
    const editSheet = mobileSheetRefs.edit;
    const arrangeSheet = mobileSheetRefs.arrange;
    const styleGrid = styleSheet ? styleSheet.querySelector(".ds-mobile-sheet-grid") : null;
    const editGrid = editSheet ? editSheet.querySelector(".ds-mobile-sheet-grid") : null;
    const arrangeGrid = arrangeSheet ? arrangeSheet.querySelector(".ds-mobile-sheet-grid") : null;
    const setVal = (scope, selector, value) => {
      if (!scope) return;
      const el = scope.querySelector(selector);
      if (!el) return;
      if (el.tagName && el.tagName.toLowerCase() === "select") ensureMobileSelectHasOption(el, value);
      el.value = String(value == null ? "" : value);
      if (selector === '[data-mobile-style-prop="fontFamily"]') {
        try { el.style.fontFamily = String(value || "inherit"); } catch (e) {}
      }
      if (selector === '[data-mobile-style-prop="fontFamily"]') {
        try { syncMobileFontFieldUi(String(value || "")); } catch (e2) {}
      }
      if (selector === '[data-mobile-style-prop="strokeSide"]') {
        try { syncMobileStrokeSideToggleUi(String(value || "inside")); } catch (e3) {}
      }
    };

    if (obj && isTextObject(obj) && !isStickerObject(obj)) {
      setVal(styleGrid, '[data-mobile-style-prop="fontFamily"]', String(obj.fontFamily || ""));
      syncMobileTextAlignButtons(String(obj.textAlign || "left"));
      setVal(styleGrid, '[data-mobile-style-prop="fontSize"]', String(obj.fontSize || ""));
      setVal(styleGrid, '[data-mobile-style-prop="fill"]', normColor(obj.fill));
      setVal(styleGrid, '[data-mobile-style-prop="fill-hex"]', normColor(obj.fill));
      setVal(styleGrid, '[data-mobile-style-prop="stroke"]', normColor(obj.stroke));
      setVal(styleGrid, '[data-mobile-style-prop="stroke-hex"]', normColor(obj.stroke));
      setVal(styleGrid, '[data-mobile-style-prop="strokeWidth"]', String(obj.strokeWidth || 0));
      setVal(styleGrid, '[data-mobile-style-prop="strokeSide"]', String((obj.paintFirst === "stroke") ? "outside" : "inside"));
      setVal(styleGrid, '[data-mobile-style-prop="opacity"]', String(Math.round((obj.opacity == null ? 1 : obj.opacity) * 100)));
      const angle = String(Math.round(obj.angle || 0));
      setVal(editGrid, '[data-mobile-arrange-prop="angle"]', angle);
      setVal(arrangeGrid, '[data-mobile-arrange-prop="angle"]', angle);
      return;
    }

    if (obj && (obj.type === "image" || isStickerObject(obj))) {
      if (isStickerObject(obj)) {
        setVal(styleGrid, '[data-mobile-style-prop="fill"]', normColor(obj.fill));
      }
      setVal(styleGrid, '[data-mobile-style-prop="stroke"]', normColor(obj.stroke));
      setVal(styleGrid, '[data-mobile-style-prop="stroke-hex"]', normColor(obj.stroke));
      setVal(styleGrid, '[data-mobile-style-prop="strokeWidth"]', String(obj.strokeWidth || 0));
      setVal(styleGrid, '[data-mobile-style-prop="opacity"]', String(Math.round((obj.opacity == null ? 1 : obj.opacity) * 100)));
      if (obj.type === "image") {
        setVal(styleGrid, '[data-mobile-style-prop="cornerRadius"]', String(Math.max(0, Number(obj.dsCornerRadius || 0))));
      }
      const angle = String(Math.round(obj.angle || 0));
      setVal(editGrid, '[data-mobile-arrange-prop="angle"]', angle);
      setVal(arrangeGrid, '[data-mobile-arrange-prop="angle"]', angle);
      return;
    }

    if (obj) {
      setVal(styleGrid, '[data-mobile-style-prop="fill"]', normColor(obj.fill));
      setVal(styleGrid, '[data-mobile-style-prop="fill-hex"]', normColor(obj.fill));
      setVal(styleGrid, '[data-mobile-style-prop="stroke"]', normColor(obj.stroke));
      setVal(styleGrid, '[data-mobile-style-prop="stroke-hex"]', normColor(obj.stroke));
      setVal(styleGrid, '[data-mobile-style-prop="strokeWidth"]', String(obj.strokeWidth || 0));
      setVal(styleGrid, '[data-mobile-style-prop="opacity"]', String(Math.round((obj.opacity == null ? 1 : obj.opacity) * 100)));
      const angle = String(Math.round(obj.angle || 0));
      setVal(editGrid, '[data-mobile-arrange-prop="angle"]', angle);
      setVal(arrangeGrid, '[data-mobile-arrange-prop="angle"]', angle);
      return;
    }

    setVal(styleGrid, '[data-mobile-style-prop="fontFamily"]', snap.fontFamily);
    syncMobileTextAlignButtons(snap.textAlign);
    setVal(styleGrid, '[data-mobile-style-prop="fontSize"]', snap.fontSize);
    setVal(styleGrid, '[data-mobile-style-prop="fill"]', snap.fill);
    setVal(styleGrid, '[data-mobile-style-prop="fill-hex"]', snap.fill);
    setVal(styleGrid, '[data-mobile-style-prop="stroke"]', snap.stroke);
    setVal(styleGrid, '[data-mobile-style-prop="stroke-hex"]', snap.stroke);
    setVal(styleGrid, '[data-mobile-style-prop="strokeWidth"]', snap.strokeWidth);
    setVal(styleGrid, '[data-mobile-style-prop="strokeSide"]', snap.strokeSide);
    setVal(styleGrid, '[data-mobile-style-prop="opacity"]', snap.opacity);
    setVal(styleGrid, '[data-mobile-style-prop="cornerRadius"]', snap.cornerRadius);
    setVal(editGrid, '[data-mobile-arrange-prop="angle"]', snap.angle);
    setVal(arrangeGrid, '[data-mobile-arrange-prop="angle"]', snap.angle);
  };

  const renderObjectModeSheets = () => {
    if (!sheetsWrap) return;
    const active = canvas.getActiveObject();
    const hasActiveMulti = !!(active && !isSystemObject(active) && active.type === "activeSelection");
    const fallbackObj = getPreferredMobileObjectRef();
    const singleTarget = (active && !isSystemObject(active) && active.type !== "activeSelection")
      ? active
      : fallbackObj;
    let selectedType = hasActiveMulti ? "multi" : getSelectedObjectType(singleTarget);
    if (!selectedType && state.lastObjectUiValues && state.lastObjectUiValues.type) {
      selectedType = String(state.lastObjectUiValues.type || "");
    }
    editorUiState.selectedObjectType = selectedType;

    const editSheet = mobileSheetRefs.edit;
    const styleSheet = mobileSheetRefs.style;
    const arrangeSheet = mobileSheetRefs.arrange;
    const moreSheet = mobileSheetRefs.more;

    const setSheet = (sheet, title, html) => {
      if (!sheet) return;
      const h = sheet.querySelector(".ds-mobile-sheet-head h4");
      const g = sheet.querySelector(".ds-mobile-sheet-grid");
      if (h) h.textContent = title;
      if (g) g.innerHTML = html;
    };

    const actionTarget = hasActiveMulti ? active : singleTarget;
    const isTextSel = !!(singleTarget && isTextObject(singleTarget) && !isStickerObject(singleTarget));
    const isRasterImageSel = !!(singleTarget && singleTarget.type === "image");
    const isStickerSel = !!(singleTarget && isStickerObject(singleTarget));
    const isIconSel = !!(singleTarget && singleTarget.dsSourceType === "icon");
    const isImageSel = isRasterImageSel || isStickerSel;
    const canCrop = isRasterImageSel;
    const canAi = isRasterImageSel;
    let editHtml = "";
    if (selectedType === "multi" && active && active.type === "activeSelection") {
      editHtml += '<button type="button" data-mobile-sheet-action="group">' + escHtml(tUi("group", "Group")) + '</button>';
      if (canMaskActiveSelection(active)) {
        editHtml += '<button type="button" data-mobile-sheet-action="mask">' + escHtml(tUi("mask", "Mask")) + '</button>';
      }
    } else if (selectedType === "group") {
      editHtml += '<button type="button" data-mobile-sheet-action="ungroup">' + escHtml(tUi("ungroup", "Ungroup")) + '</button>';
    } else if (isTextSel) {
      editHtml += '<button type="button" data-mobile-sheet-action="edit-text">' + escHtml(tUi("edit_text", "Edit Text")) + '</button>';
    } else if (isImageSel) {
      editHtml += (canCrop ? ('<button type="button" data-mobile-sheet-action="crop-image">' + escHtml(tUi("crop", "Crop")) + '</button>') : '');
      editHtml += (canAi ? ('<button type="button" data-mobile-sheet-action="ai-remove-bg">' + escHtml(tUi("ai_remove_bg", "AI Remove BG")) + '</button>') : '');
    }
    if (actionTarget) {
      editHtml += '<label class="ds-mobile-style-field">' + escHtml(tUi("rotate", "Rotate")) + '<input type="number" step="1" data-mobile-arrange-prop="angle"></label>';
      editHtml += '<button type="button" data-mobile-sheet-action="duplicate-object">' + escHtml(tUi("duplicate", "Duplicate")) + '</button>';
      editHtml += '<button type="button" data-mobile-sheet-action="delete-object">' + escHtml(tUi("delete", "Delete")) + '</button>';
    }
    if (state.cropSession && state.cropSession.image && state.cropSession.frame) {
      editHtml += '<button type="button" data-mobile-sheet-action="crop-apply">' + escHtml(tUi("apply_crop", "Apply Crop")) + '</button>';
      editHtml += '<button type="button" data-mobile-sheet-action="crop-cancel">' + escHtml(tUi("crop_cancel", "Crop Cancel")) + '</button>';
    }
    setSheet(editSheet, tUi("edit", "Edit"), editHtml);

    let styleHtml = "";
    if (selectedType === "text") {
      try { preloadMobileFontPickerFonts(); } catch (e) {}
      const currentFontValue = String((singleTarget && singleTarget.fontFamily) || "");
      const currentFontEntry = findMobileFontEntryByValue(currentFontValue);
      const currentFontLabel = String((currentFontEntry && currentFontEntry.label) || currentFontValue || tUi("choose_font", "Choose font"));
      const desktopClassicTextStyle = isDesktopClassicUi() && !isMobileViewport();
      if (desktopClassicTextStyle) {
        styleHtml += '<div class="ds-mobile-sheet-section ds-mobile-text-style-group"><h5>' + escHtml(tUi("font", "Font")) + '</h5><label class="ds-mobile-style-field ds-mobile-font-field">' + escHtml(tUi("choose_font_title", "Choose Font")) + '<button type="button" class="ds-mobile-font-picker-open" data-mobile-sheet-action="open-font-picker" style="font-family:' + escAttr(currentFontValue || "inherit") + '"><span data-role="mobile-font-current-label">' + escHtml(currentFontLabel) + '</span></button></label></div>';
        styleHtml += '<div class="ds-mobile-sheet-section ds-mobile-text-style-group"><h5>' + escHtml(tUi("size", "Size")) + '</h5><label class="ds-mobile-style-field"><input type="number" min="1" step="1" placeholder="XX" data-mobile-style-prop="fontSize"></label></div>';
        styleHtml += '<div class="ds-mobile-sheet-section ds-mobile-text-style-group ds-mobile-inline-toggles"><h5>' + escHtml(tUi("text_align", "Text Align")) + '</h5><button type="button" data-mobile-sheet-action="text-align-left" aria-pressed="false">' + escHtml(tUi("left", "Left")) + '</button><button type="button" data-mobile-sheet-action="text-align-center" aria-pressed="false">' + escHtml(tUi("center", "Center")) + '</button><button type="button" data-mobile-sheet-action="text-align-right" aria-pressed="false">' + escHtml(tUi("right", "Right")) + '</button></div>';
        styleHtml += '<div class="ds-mobile-sheet-section ds-mobile-text-style-group"><h5>' + escHtml(tUi("fill", "Fill")) + '</h5><label class="ds-mobile-style-field">' + escHtml(tUi("the_color", "The color")) + '<input type="color" data-mobile-style-prop="fill"></label></div>';
        styleHtml += '<div class="ds-mobile-sheet-section ds-mobile-text-style-group"><h5>' + escHtml(tUi("stroke", "Stroke")) + '</h5><label class="ds-mobile-style-field">' + escHtml(tUi("the_color", "The color")) + '<input type="color" data-mobile-style-prop="stroke"></label><div class="ds-mobile-text-stroke-row"><label class="ds-mobile-style-field">' + escHtml(tUi("width", "Width")) + '<input type="number" min="0" step="1" data-mobile-style-prop="strokeWidth"></label><label class="ds-mobile-style-field ds-mobile-stroke-side-field">' + escHtml(tUi("side", "Side")) + '<input type="hidden" data-mobile-style-prop="strokeSide" value="inside"><button type="button" class="ds-mobile-stroke-side-toggle" data-role="mobile-stroke-side-toggle" data-value="inside" aria-pressed="false"><span data-role="mobile-stroke-side-inside">' + escHtml(tUi("inside", "Inside")) + '</span><span data-role="mobile-stroke-side-outside">' + escHtml(tUi("outside", "Outside")) + '</span></button></label></div></div>';
        styleHtml += '<div class="ds-mobile-sheet-section ds-mobile-text-style-group"><h5>' + escHtml(tUi("opacity", "Opacity")) + '</h5><label class="ds-mobile-style-field"><input type="range" min="0" max="100" step="1" data-mobile-style-prop="opacity"></label></div>';
      } else {
        styleHtml += '<label class="ds-mobile-style-field ds-mobile-font-field">' + escHtml(tUi("font", "Font")) + '<button type="button" class="ds-mobile-font-picker-open" data-mobile-sheet-action="open-font-picker" style="font-family:' + escAttr(currentFontValue || "inherit") + '"><span data-role="mobile-font-current-label">' + escHtml(currentFontLabel) + '</span></button></label>';
        styleHtml += '<label class="ds-mobile-style-field">' + escHtml(tUi("size", "Size")) + '<input type="number" min="1" step="1" data-mobile-style-prop="fontSize"></label>';
        styleHtml += '<div class="ds-mobile-sheet-section ds-mobile-inline-toggles"><h5>' + escHtml(tUi("text_align", "Text Align")) + '</h5><button type="button" data-mobile-sheet-action="text-align-left" aria-pressed="false">' + escHtml(tUi("left", "Left")) + '</button><button type="button" data-mobile-sheet-action="text-align-center" aria-pressed="false">' + escHtml(tUi("center", "Center")) + '</button><button type="button" data-mobile-sheet-action="text-align-right" aria-pressed="false">' + escHtml(tUi("right", "Right")) + '</button></div>';
        styleHtml += '<label class="ds-mobile-style-field">' + escHtml(tUi("fill", "Fill")) + '<input type="color" data-mobile-style-prop="fill"></label>';
        styleHtml += '<label class="ds-mobile-style-field">' + escHtml(tUi("stroke", "Stroke")) + '<input type="color" data-mobile-style-prop="stroke"></label>';
        styleHtml += '<label class="ds-mobile-style-field">' + escHtml(tUi("stroke_width_short", "Stroke W")) + '<input type="number" min="0" step="1" data-mobile-style-prop="strokeWidth"></label>';
        styleHtml += '<label class="ds-mobile-style-field ds-mobile-stroke-side-field">' + escHtml(tUi("stroke_side", "Stroke Side")) + '<input type="hidden" data-mobile-style-prop="strokeSide" value="inside"><button type="button" class="ds-mobile-stroke-side-toggle" data-role="mobile-stroke-side-toggle" data-value="inside" aria-pressed="false"><span data-role="mobile-stroke-side-inside">' + escHtml(tUi("inside", "Inside")) + '</span><span data-role="mobile-stroke-side-outside">' + escHtml(tUi("outside", "Outside")) + '</span></button></label>';
        styleHtml += '<label class="ds-mobile-style-field">' + escHtml(tUi("opacity", "Opacity")) + '<input type="range" min="0" max="100" step="1" data-mobile-style-prop="opacity"></label>';
      }
    } else if (selectedType === "image") {
      styleHtml += '<label class="ds-mobile-style-field">' + escHtml(tUi("opacity", "Opacity")) + '<input type="range" min="0" max="100" step="1" data-mobile-style-prop="opacity"></label>';
      if (isStickerSel || isIconSel) {
        styleHtml += '<label class="ds-mobile-style-field">' + escHtml(tUi("fill", "Fill")) + '<input type="color" data-mobile-style-prop="fill"></label>';
      }
      styleHtml += '<label class="ds-mobile-style-field">' + escHtml(tUi("stroke", "Stroke")) + '<input type="color" data-mobile-style-prop="stroke"></label>';
      styleHtml += '<label class="ds-mobile-style-field">' + escHtml(tUi("stroke_width_short", "Stroke W")) + '<input type="number" min="0" step="1" data-mobile-style-prop="strokeWidth"></label>';
      if (isRasterImageSel) {
        styleHtml += '<label class="ds-mobile-style-field">' + escHtml(tUi("corners", "Corners")) + '<input type="number" min="0" step="1" data-mobile-style-prop="cornerRadius"></label>';
        styleHtml += '<button type="button" data-mobile-sheet-action="ai-remove-bg">' + escHtml(tUi("ai_remove_bg", "AI Remove BG")) + '</button>';
      }
    } else if (selectedType === "group") {
      styleHtml += '<label class="ds-mobile-style-field">' + escHtml(tUi("opacity", "Opacity")) + '<input type="range" min="0" max="100" step="1" data-mobile-style-prop="opacity"></label>';
    } else {
      styleHtml += '<label class="ds-mobile-style-field">' + escHtml(tUi("fill", "Fill")) + '<input type="color" data-mobile-style-prop="fill"></label>';
      styleHtml += '<label class="ds-mobile-style-field">' + escHtml(tUi("stroke", "Stroke")) + '<input type="color" data-mobile-style-prop="stroke"></label>';
      styleHtml += '<label class="ds-mobile-style-field">' + escHtml(tUi("stroke_width_short", "Stroke W")) + '<input type="number" min="0" step="1" data-mobile-style-prop="strokeWidth"></label>';
      styleHtml += '<label class="ds-mobile-style-field">' + escHtml(tUi("opacity", "Opacity")) + '<input type="range" min="0" max="100" step="1" data-mobile-style-prop="opacity"></label>';
    }
    setSheet(styleSheet, tUi("style", "Style"), styleHtml);
    ensureColorMixers(styleSheet || sheetsWrap || root);

    const rotationSectionHtml = "";

    const layersSectionHtml =
      '<div class="ds-mobile-sheet-section ds-mobile-layer-section">' +
        "<h5>" + escHtml(tUi("layers", "Layers")) + "</h5>" +
        '<ul class="ds-layer-list ds-layer-list-inline" data-role="arrange-layer-list"></ul>' +
      "</div>";

    const arrangeHtml =
      rotationSectionHtml +
      '<div class="ds-mobile-sheet-section">' +
        "<h5>" + escHtml(tUi("horizontal", "Horizontal")) + "</h5>" +
        '<button type="button" data-mobile-sheet-action="align-left">' + escHtml(tUi("align_left", "Align Left")) + '</button>' +
        '<button type="button" data-mobile-sheet-action="align-center">' + escHtml(tUi("align_center", "Align Center")) + '</button>' +
        '<button type="button" data-mobile-sheet-action="align-right">' + escHtml(tUi("align_right", "Align Right")) + '</button>' +
      "</div>" +
      '<div class="ds-mobile-sheet-section">' +
        "<h5>" + escHtml(tUi("vertical", "Vertical")) + "</h5>" +
        '<button type="button" data-mobile-sheet-action="align-top">' + escHtml(tUi("align_top", "Align Top")) + '</button>' +
        '<button type="button" data-mobile-sheet-action="align-middle">' + escHtml(tUi("align_middle", "Align Middle")) + '</button>' +
        '<button type="button" data-mobile-sheet-action="align-bottom">' + escHtml(tUi("align_bottom", "Align Bottom")) + '</button>' +
      "</div>" +
      '<div class="ds-mobile-sheet-section">' +
        "<h5>" + escHtml(tUi("layers", "Layers")) + "</h5>" +
        '<button type="button" data-mobile-sheet-action="layer-up">' + escHtml(tUi("layer_up", "Layer Up")) + '</button>' +
        '<button type="button" data-mobile-sheet-action="layer-down">' + escHtml(tUi("layer_down", "Layer Down")) + '</button>' +
        '<button type="button" data-mobile-sheet-action="layer-front">' + escHtml(tUi("bring_front", "Bring Front")) + '</button>' +
        '<button type="button" data-mobile-sheet-action="layer-back">' + escHtml(tUi("send_back", "Send Back")) + '</button>' +
      "</div>" +
      layersSectionHtml;
    setSheet(arrangeSheet, tUi("arrange", "Arrange"), arrangeHtml);

    const moreHtml =
      '<button type="button" data-mobile-sheet-action="object-props">' + escHtml(tUi("object_properties", "Object Properties")) + '</button>';
    setSheet(moreSheet, tUi("more", "More"), moreHtml);

    // Populate object values in freshly rendered controls.
    const obj = actionTarget || null;
    const styleGrid = styleSheet ? styleSheet.querySelector(".ds-mobile-sheet-grid") : null;
    const editGrid = editSheet ? editSheet.querySelector(".ds-mobile-sheet-grid") : null;
    const arrangeGrid = arrangeSheet ? arrangeSheet.querySelector(".ds-mobile-sheet-grid") : null;
    const setVal = (scope, sel, val) => {
      if (!scope) return;
      const el = scope.querySelector(sel);
      if (!el) return;
      if (el.tagName && el.tagName.toLowerCase() === "select") {
        ensureMobileSelectHasOption(el, val);
      }
      el.value = val;
      if (sel === '[data-mobile-style-prop="fontFamily"]') {
        try { el.style.fontFamily = String(val || "inherit"); } catch (e) {}
        try { syncMobileFontFieldUi(String(val || "")); } catch (e2) {}
      }
      if (sel === '[data-mobile-style-prop="strokeSide"]') {
        try { syncMobileStrokeSideToggleUi(String(val || "inside")); } catch (e3) {}
      }
    };
    if (obj) {
      if (selectedType === "text") {
        setVal(styleGrid, '[data-mobile-style-prop="fontFamily"]', String(obj.fontFamily || ""));
        syncMobileTextAlignButtons(String(obj.textAlign || "left"));
        setVal(styleGrid, '[data-mobile-style-prop="fontSize"]', String(obj.fontSize || ""));
        setVal(styleGrid, '[data-mobile-style-prop="strokeSide"]', String((obj.paintFirst === "stroke") ? "outside" : "inside"));
      }
      if (selectedType !== "image" && selectedType !== "group") {
        setVal(styleGrid, '[data-mobile-style-prop="fill"]', normColor(obj.fill));
        setVal(styleGrid, '[data-mobile-style-prop="fill-hex"]', normColor(obj.fill));
        setVal(styleGrid, '[data-mobile-style-prop="stroke"]', normColor(obj.stroke));
        setVal(styleGrid, '[data-mobile-style-prop="stroke-hex"]', normColor(obj.stroke));
        setVal(styleGrid, '[data-mobile-style-prop="strokeWidth"]', String(obj.strokeWidth || 0));
      }
      if (selectedType === "image") {
        if (isStickerSel || isIconSel) {
          setVal(styleGrid, '[data-mobile-style-prop="fill"]', normColor(obj.fill));
        }
        setVal(styleGrid, '[data-mobile-style-prop="stroke"]', normColor(obj.stroke));
        setVal(styleGrid, '[data-mobile-style-prop="stroke-hex"]', normColor(obj.stroke));
        setVal(styleGrid, '[data-mobile-style-prop="strokeWidth"]', String(obj.strokeWidth || 0));
        setVal(styleGrid, '[data-mobile-style-prop="cornerRadius"]', String(Math.max(0, Number(obj.dsCornerRadius || 0))));
      }
      setVal(styleGrid, '[data-mobile-style-prop="opacity"]', String(Math.round((obj.opacity == null ? 1 : obj.opacity) * 100)));
      const angle = String(Math.round(obj.angle || 0));
      setVal(editGrid, '[data-mobile-arrange-prop="angle"]', angle);
      setVal(arrangeGrid, '[data-mobile-arrange-prop="angle"]', angle);
    }
    hydrateMobileStyleFieldsFromObject(obj);
    updateLayerList();
    try { syncDesktopClassicLightButtonStyles(); } catch (e) {}
  };
  root.__dsApplyObjectSheets = renderObjectModeSheets;

  const hydrateMobileObjectSheetsFromProps = () => {
    const styleSheet = mobileSheetRefs.style;
    const editSheet = mobileSheetRefs.edit;
    const arrangeSheet = mobileSheetRefs.arrange;
    const editGrid = editSheet ? editSheet.querySelector(".ds-mobile-sheet-grid") : null;
    const styleGrid = styleSheet ? styleSheet.querySelector(".ds-mobile-sheet-grid") : null;
    const arrangeGrid = arrangeSheet ? arrangeSheet.querySelector(".ds-mobile-sheet-grid") : null;
    if (!styleGrid && !arrangeGrid && !editGrid) return;

    const single = getSingleActive() || getPreferredMobileObjectRef();
    const srcProp = (name) => (propsEl ? propsEl.querySelector('[data-prop="' + String(name || "") + '"]') : null);
    const readProp = (name) => {
      const el = srcProp(name);
      if (!el) return "";
      return String(el.value == null ? "" : el.value);
    };
    const setVal = (scope, selector, value) => {
      if (!scope) return;
      const el = scope.querySelector(selector);
      if (!el) return;
      if (el.tagName && el.tagName.toLowerCase() === "select") ensureMobileSelectHasOption(el, value);
      el.value = String(value == null ? "" : value);
      if (selector === '[data-mobile-style-prop="fontFamily"]') {
        try { el.style.fontFamily = String(value || "inherit"); } catch (e) {}
        try { syncMobileFontFieldUi(String(value || "")); } catch (e2) {}
      }
      if (selector === '[data-mobile-style-prop="strokeSide"]') {
        try { syncMobileStrokeSideToggleUi(String(value || "inside")); } catch (e3) {}
      }
    };

    setVal(styleGrid, '[data-mobile-style-prop="fontFamily"]', readProp("fontFamily"));
    setVal(styleGrid, '[data-mobile-style-prop="fontSize"]', readProp("fontSize"));
    setVal(styleGrid, '[data-mobile-style-prop="fill"]', readProp("fill"));
    setVal(styleGrid, '[data-mobile-style-prop="fill-hex"]', readProp("fill"));
    setVal(styleGrid, '[data-mobile-style-prop="stroke"]', readProp("stroke"));
    setVal(styleGrid, '[data-mobile-style-prop="stroke-hex"]', readProp("stroke"));
    setVal(styleGrid, '[data-mobile-style-prop="strokeWidth"]', readProp("strokeWidth"));
    setVal(styleGrid, '[data-mobile-style-prop="strokeSide"]', readProp("strokeSide"));
    setVal(styleGrid, '[data-mobile-style-prop="opacity"]', readProp("opacity"));
    const angle = readProp("angle");
    setVal(editGrid, '[data-mobile-arrange-prop="angle"]', angle);
    setVal(arrangeGrid, '[data-mobile-arrange-prop="angle"]', angle);

    if (single && single.type === "image") {
      setVal(styleGrid, '[data-mobile-style-prop="cornerRadius"]', String(Math.max(0, Number(single.dsCornerRadius || 0))));
    }
  };

  const restoreCanvasModeSheets = () => {
    ["insert", "canvas", "library", "preview"].forEach((k) => {
      const s = mobileSheetRefs[k];
      const b = mobileSheetBase[k];
      if (!s || !b) return;
      const h = s.querySelector(".ds-mobile-sheet-head h4");
      const g = s.querySelector(".ds-mobile-sheet-grid");
      if (h) h.textContent = b.title;
      if (g) g.innerHTML = b.html;
    });
    try {
      if (isClassicDesktopViewport()) {
        ensureDesktopClassicCanvasQuickActions();
      }
    } catch (e) {}
    try { ensureStartPanelToggleInLibrarySheet(); } catch (e3) {}
    try { syncDesktopClassicLightButtonStyles(); } catch (e2) {}
  };
  root.__dsRestoreCanvasSheets = restoreCanvasModeSheets;

  const switchToObjectMode = () => {
    if (!isMobileViewport() && !isClassicDesktopViewport()) return;
    const activeEl = (typeof document !== "undefined") ? document.activeElement : null;
    const isEditingField = !!(activeEl && activeEl.closest && activeEl.closest(".ds-mobile-sheet") &&
      (activeEl.tagName === "INPUT" || activeEl.tagName === "SELECT" || activeEl.tagName === "TEXTAREA"));
    editorUiState.mobileMode = "object";
    root.setAttribute("data-mobile-mode", "object");
    setNavModeLabels("object");
    // Do not close active object sheets while user edits values.
    // Only close if a canvas-mode sheet is open.
    if (root.classList.contains("ds-mobile-sheet-open")) {
      const openKey = String(root.getAttribute("data-mobile-sheet-open") || "");
      const isCanvasSheet =
        openKey === "insert" ||
        openKey === "canvas" ||
        openKey === "library" ||
        openKey === "preview";
      if (isCanvasSheet) closeSheets();
    }
    if (!isEditingField) {
      try { renderObjectModeSheets(); } catch (err) {}
    }
  };

  const switchToCanvasMode = () => {
    editorUiState.mobileMode = "canvas";
    editorUiState.selectedObjectType = null;
    root.setAttribute("data-mobile-mode", "canvas");
    setNavModeLabels("canvas");
    restoreCanvasModeSheets();
  };

  const syncMobileModeFromSelection = () => {
    const activeEl = (typeof document !== "undefined") ? document.activeElement : null;
    const isEditingField = !!(activeEl && activeEl.closest && activeEl.closest(".ds-mobile-sheet") &&
      (activeEl.tagName === "INPUT" || activeEl.tagName === "SELECT" || activeEl.tagName === "TEXTAREA"));
    if (isEditingField) return;
    if (!isMobileViewport() && !isClassicDesktopViewport()) {
      switchToCanvasMode();
      return;
    }
    const obj = canvas.getActiveObject();
    if (obj && !isSystemObject(obj)) {
      switchToObjectMode();
    } else {
      switchToCanvasMode();
    }
  };

  const forceLandscapeClassicFloatingSheet = (_open, _activeKey) => {
    if (!sheetsWrap) return;
    const isLandscapeClassic = isLandscapeMobileViewport() && !isModernUi();
    const clearInline = () => {
      const clearProps = (el, props) => {
        if (!el) return;
        props.forEach((p) => el.style.removeProperty(p));
      };
      clearProps(sheetsWrap, [
        "position", "left", "top", "right", "bottom", "transform", "width", "max-width", "max-height",
        "z-index", "opacity", "visibility", "pointer-events", "overflow", "background", "border", "border-radius", "box-shadow"
      ]);
      if (sheetBackdrop) {
        clearProps(sheetBackdrop, ["display", "pointer-events", "opacity", "visibility"]);
      }
      const host = root.querySelector(".ds-desktop-sheet-host");
      if (host) clearProps(host, ["pointer-events"]);
      sheetsWrap.querySelectorAll(".ds-mobile-sheet").forEach((sheet) => {
        clearProps(sheet, ["display", "grid-template-rows", "height", "max-height", "overflow"]);
      });
      sheetsWrap.querySelectorAll(".ds-mobile-sheet-grid").forEach((grid) => {
        clearProps(grid, ["min-height", "overflow", "-webkit-overflow-scrolling"]);
      });
    };

    // Legacy centered-floating override caused landscape sheets to open in screen center.
    // Keep this function only as inline-style cleanup so CSS controls sheet placement.
    clearInline();
    if (!isLandscapeClassic) return;
  };

  const setSheetUiState = (isOpen, key, navActionKey) => {
    const open = !!isOpen;
    const activeKey = String(key || "");

    root.classList.toggle("ds-mobile-sheet-open", open);
    root.classList.remove("ds-mobile-more-open");
    if (open && activeKey) root.setAttribute("data-mobile-sheet-open", activeKey);
    else root.removeAttribute("data-mobile-sheet-open");
    if (open && (isMobileLandscape() || isClassicDesktopViewport())) {
      const forcedLandscapeRight = isLandscapeMobileViewport() && !isModernUi();
      root.setAttribute("data-mobile-sheet-side", forcedLandscapeRight ? "right" : (pendingSheetSide || "right"));
    } else if (!open || (!isMobileLandscape() && !isClassicDesktopViewport())) {
      root.removeAttribute("data-mobile-sheet-side");
    }

    if (sheetsWrap) {
      sheetsWrap.setAttribute("aria-hidden", open ? "false" : "true");
      let hasActiveSheet = false;
      sheetsWrap.querySelectorAll(".ds-mobile-sheet").forEach((sheet) => {
        const isActive = open && sheet.getAttribute("data-mobile-sheet") === activeKey;
        sheet.classList.toggle("is-active", isActive);
        sheet.setAttribute("aria-hidden", isActive ? "false" : "true");
        if (isActive) hasActiveSheet = true;
      });
      if (open && !hasActiveSheet) {
        const fallbackKey = activeKey || "insert";
        const fallbackSheet =
          sheetsWrap.querySelector('.ds-mobile-sheet[data-mobile-sheet="' + String(fallbackKey) + '"]') ||
          sheetsWrap.querySelector(".ds-mobile-sheet");
        if (fallbackSheet) {
          fallbackSheet.classList.add("is-active");
          fallbackSheet.setAttribute("aria-hidden", "false");
          if (!root.getAttribute("data-mobile-sheet-open")) {
            const fallbackName = String(fallbackSheet.getAttribute("data-mobile-sheet") || "");
            if (fallbackName) root.setAttribute("data-mobile-sheet-open", fallbackName);
          }
        }
      }
      if (!open) {
        sheetsWrap.querySelectorAll('[data-role="mobile-shapes-picker"], [data-role="mobile-emoticons-picker"], [data-role="mobile-icons-picker"]').forEach((el) => {
          el.setAttribute("hidden", "");
        });
      }
    }

    if (sheetBackdrop) {
      sheetBackdrop.setAttribute("aria-hidden", open ? "false" : "true");
    }

    const activeNav = open ? String(navActionKey || navActionFromSheetKey(activeKey)) : "";
    setActiveNav(activeNav);
    if (isClassicDesktopViewport()) {
      root.setAttribute("data-dc-pane-expanded", open ? "1" : "0");
    }
    const desktopNav = root.querySelector(".ds-desktop-sheet-nav");
    if (desktopNav) {
      desktopNav.querySelectorAll(".ds-desktop-sheet-nav-btn").forEach((btn) => {
        const on = open && String(btn.getAttribute("data-mobile-action") || "") === activeNav;
        btn.classList.toggle("is-active", on);
      });
    }
    if (cta) {
      cta.setAttribute("aria-hidden", open ? "true" : "false");
    }
    try { forceLandscapeClassicFloatingSheet(open, activeKey); } catch (e5) {}
    try { syncDesktopClassicLightButtonStyles(); } catch (e4) {}
    try { syncMobileEmptyStateOverlay(); } catch (err) {}
  };

  const openSheet = (name) => {
    const navAction = String(name || "");
    if (!navAction || !sheetsWrap || !sheetBackdrop || !canUseMobileSheetUi()) return;
    try { initClassicDesktopSheetLaunchers(); } catch (e) {}
    if (isStartupInteractionLocked()) {
      triggerStartupFlowRequiredStatus();
      return;
    }
    if (isZoneSelectionInteractionLocked()) {
      triggerZoneSelectionRequiredStatus();
      return;
    }
    if (isMobileViewport() && !MOBILE_LANDSCAPE_ENABLED && window.matchMedia("(orientation: landscape)").matches) return;

    // Ensure sheet content always matches current selection mode before opening.
    const activeObj = canvas.getActiveObject();
    const hasSelection = !!(activeObj && !isSystemObject(activeObj));
    if (hasSelection) {
      editorUiState.mobileMode = "object";
      root.setAttribute("data-mobile-mode", "object");
      setNavModeLabels("object");
      try { renderObjectModeSheets(); } catch (err) {}
    } else {
      editorUiState.mobileMode = "canvas";
      root.setAttribute("data-mobile-mode", "canvas");
      setNavModeLabels("canvas");
      restoreCanvasModeSheets();
    }

    const targetSheetKey = resolveSheetKey(navAction);
    if (!targetSheetKey || !mobileSheetRefs[targetSheetKey]) return;
    const current = String(root.getAttribute("data-mobile-sheet-open") || "");
    if (current === targetSheetKey && root.classList.contains("ds-mobile-sheet-open")) {
      closeSheets();
      return;
    }
    setSheetUiState(true, targetSheetKey, navAction);
    if (isLandscapeMobileViewport()) {
      try {
        Object.keys(mobileSheetRefs || {}).forEach((key) => {
          const el = mobileSheetRefs[key];
          if (!el) return;
          const active = key === targetSheetKey;
          el.classList.toggle("is-active", active);
          el.setAttribute("aria-hidden", active ? "false" : "true");
        });
        root.setAttribute("data-mobile-sheet-open", targetSheetKey);
      } catch (e) {}
    }
    if (targetSheetKey === "canvas") syncCanvasToggleButtons();
    try { syncDesktopClassicLightButtonStyles(); } catch (e7) {}
    const isObjectSheet = (targetSheetKey === "edit" || targetSheetKey === "style" || targetSheetKey === "arrange" || targetSheetKey === "more");
    if (isObjectSheet) {
      const refreshObjectSheet = () => {
        const activeEl = (typeof document !== "undefined") ? document.activeElement : null;
        const isEditingField = !!(activeEl && activeEl.closest && activeEl.closest(".ds-mobile-sheet") &&
          (activeEl.tagName === "INPUT" || activeEl.tagName === "SELECT" || activeEl.tagName === "TEXTAREA"));
        if (isEditingField) return;
        try {
          const live = canvas.getActiveObject();
          const fallback = getPreferredMobileObjectRef();
          const candidate = (live && !isSystemObject(live) && live.type !== "activeSelection") ? live : fallback;
          if (candidate) captureObjectUiValues(candidate);
          if (candidate) {
            try { updateProps(); } catch (e0) {}
            try { renderObjectModeSheets(); } catch (e2) {}
            try { hydrateMobileObjectSheetsFromProps(); } catch (e4) {}
            try { hydrateMobileStyleFieldsFromObject(candidate); } catch (e5) {}
          } else {
            try { hydrateMobileStyleFieldsFromObject(null); } catch (e6) {}
          }
        } catch (e3) {}
      };
      window.setTimeout(refreshObjectSheet, 0);
      window.setTimeout(refreshObjectSheet, 120);
      window.setTimeout(refreshObjectSheet, 300);
      window.setTimeout(refreshObjectSheet, 600);
    }
  };

  const closeSheets = () => {
    setSheetUiState(false, "");
    if (isLandscapeMobileViewport()) {
      try {
        Object.keys(mobileSheetRefs || {}).forEach((key) => {
          const el = mobileSheetRefs[key];
          if (!el) return;
          el.classList.remove("is-active");
          el.setAttribute("aria-hidden", "true");
        });
      } catch (e) {}
    }
  };

  const toggleSheet = (name) => {
    const navAction = String(name || "");
    const targetSheetKey = resolveSheetKey(navAction);
    const current = root.getAttribute("data-mobile-sheet-open") || "";
    if (targetSheetKey && current === targetSheetKey) {
      closeSheets();
      return;
    }
    openSheet(navAction);
  };

  const ensureDesktopClassicWorkspaceSplit = () => {
    const grid = root.querySelector(".ds-workspace-grid");
    if (!grid) return;
    const enabled = isClassicDesktopViewport() && !isModernUi();
    const kids = Array.from(grid.children || []);
    const directByClass = (cls) => kids.find((el) => el && el.classList && el.classList.contains(cls)) || null;

    let leftHost = directByClass("ds-dc-left-host");
    let rightHost = directByClass("ds-dc-right-host");
    const leftDirect = directByClass("ds-left-rail");
    const rightDirect = directByClass("ds-center-stage");
    const leftNode = leftDirect || (leftHost ? leftHost.querySelector(".ds-left-rail") : null);
    const rightNode = rightDirect || (rightHost ? rightHost.querySelector(".ds-center-stage") : null);

    if (!enabled) {
      if (leftHost && leftNode) grid.insertBefore(leftNode, leftHost);
      if (rightHost && rightNode) grid.appendChild(rightNode);
      if (leftHost && leftHost.parentNode) leftHost.parentNode.removeChild(leftHost);
      if (rightHost && rightHost.parentNode) rightHost.parentNode.removeChild(rightHost);
      return;
    }

    if (!leftHost) {
      leftHost = document.createElement("div");
      leftHost.className = "ds-dc-left-host";
      grid.insertBefore(leftHost, grid.firstChild);
    }
    if (!rightHost) {
      rightHost = document.createElement("div");
      rightHost.className = "ds-dc-right-host";
      grid.appendChild(rightHost);
    }

    if (leftNode && leftNode.parentNode !== leftHost) leftHost.appendChild(leftNode);
    if (rightNode && rightNode.parentNode !== rightHost) rightHost.appendChild(rightNode);
  };

  const buildDesktopSheetNavButtonsMarkup = () => {
    const insertLabel = tUi("insert", "Insert");
    const canvasLabel = tUi("canvas", "Canvas");
    const libraryLabel = tUi("library", "Library");
    const previewLabel = tUi("preview", "Preview");
    return ""
      + '<button type="button" class="ds-desktop-sheet-nav-btn" data-mobile-action="insert" aria-label="' + escAttr(insertLabel) + '" title="' + escAttr(insertLabel) + '"><span class="ds-desktop-sheet-nav-ic" aria-hidden="true"></span><span class="ds-desktop-sheet-nav-label">' + escHtml(insertLabel) + "</span></button>"
      + '<button type="button" class="ds-desktop-sheet-nav-btn" data-mobile-action="canvas" aria-label="' + escAttr(canvasLabel) + '" title="' + escAttr(canvasLabel) + '"><span class="ds-desktop-sheet-nav-ic" aria-hidden="true"></span><span class="ds-desktop-sheet-nav-label">' + escHtml(canvasLabel) + "</span></button>"
      + '<button type="button" class="ds-desktop-sheet-nav-btn" data-mobile-action="library" aria-label="' + escAttr(libraryLabel) + '" title="' + escAttr(libraryLabel) + '"><span class="ds-desktop-sheet-nav-ic" aria-hidden="true"></span><span class="ds-desktop-sheet-nav-label">' + escHtml(libraryLabel) + "</span></button>"
      + '<button type="button" class="ds-desktop-sheet-nav-btn" data-mobile-action="preview" aria-label="' + escAttr(previewLabel) + '" title="' + escAttr(previewLabel) + '"><span class="ds-desktop-sheet-nav-ic" aria-hidden="true"></span><span class="ds-desktop-sheet-nav-label">' + escHtml(previewLabel) + "</span></button>";
  };

  const initClassicDesktopSheetLaunchers = () => {
    try { ensureDesktopClassicWorkspaceSplit(); } catch (e) {}
    const leftTools = root.querySelector('[data-role="left-tools"]');
    if (!leftTools) return;
    const enabled = isClassicDesktopViewport();
    let nav = leftTools.querySelector(".ds-desktop-sheet-nav");
    const moveBack = () => {
      if (sheetsWrap && classicDesktopSheetOriginParent) {
        if (classicDesktopSheetOriginNext && classicDesktopSheetOriginNext.parentNode === classicDesktopSheetOriginParent) {
          classicDesktopSheetOriginParent.insertBefore(sheetsWrap, classicDesktopSheetOriginNext);
        } else {
          classicDesktopSheetOriginParent.appendChild(sheetsWrap);
        }
      }
      if (sheetBackdrop && classicDesktopBackdropOriginParent) {
        if (classicDesktopBackdropOriginNext && classicDesktopBackdropOriginNext.parentNode === classicDesktopBackdropOriginParent) {
          classicDesktopBackdropOriginParent.insertBefore(sheetBackdrop, classicDesktopBackdropOriginNext);
        } else {
          classicDesktopBackdropOriginParent.appendChild(sheetBackdrop);
        }
      }
      if (classicDesktopSheetHost && classicDesktopSheetHost.parentNode) {
        classicDesktopSheetHost.parentNode.removeChild(classicDesktopSheetHost);
      }
      classicDesktopSheetHost = null;
    };
    if (!enabled) {
      if (nav && nav.parentNode) nav.parentNode.removeChild(nav);
      moveBack();
      return;
    }
    if (!root.hasAttribute("data-dc-pane-expanded")) {
      root.setAttribute("data-dc-pane-expanded", "0");
    }
    if (!classicDesktopSheetHost) {
      classicDesktopSheetHost = document.createElement("div");
      classicDesktopSheetHost.className = "ds-desktop-sheet-host";
      if (sheetsWrap) {
        if (!classicDesktopSheetOriginParent) {
          classicDesktopSheetOriginParent = sheetsWrap.parentNode || null;
          classicDesktopSheetOriginNext = sheetsWrap.nextSibling || null;
        }
        classicDesktopSheetHost.appendChild(sheetsWrap);
      }
      if (sheetBackdrop) {
        if (!classicDesktopBackdropOriginParent) {
          classicDesktopBackdropOriginParent = sheetBackdrop.parentNode || null;
          classicDesktopBackdropOriginNext = sheetBackdrop.nextSibling || null;
        }
        classicDesktopSheetHost.appendChild(sheetBackdrop);
      }
      leftTools.appendChild(classicDesktopSheetHost);
    } else if (classicDesktopSheetHost.parentNode !== leftTools) {
      leftTools.appendChild(classicDesktopSheetHost);
    }
    if (!nav) {
      nav = document.createElement("div");
      nav.className = "ds-desktop-sheet-nav";
      nav.setAttribute("aria-label", tUi("desktop_classic_sheet_nav", "Desktop Classic Sheet Nav"));
      nav.innerHTML = buildDesktopSheetNavButtonsMarkup();
      leftTools.insertBefore(nav, leftTools.firstChild);
    }
    if (!nav.querySelector(".ds-desktop-sheet-nav-btn")) {
      nav.innerHTML = buildDesktopSheetNavButtonsMarkup();
    }
    if (!nav.hasAttribute("data-delegated")) {
      nav.addEventListener("click", (e) => {
        const btn = e.target && e.target.closest ? e.target.closest("[data-mobile-action]") : null;
        if (!btn) return;
        const action = String(btn.getAttribute("data-mobile-action") || "");
        if (!action) return;
        e.preventDefault();
        setPendingSheetSide(isLandscapeMobileViewport() && !isModernUi() ? "right" : "left");
        const currentMode = String(root.getAttribute("data-mobile-mode") || editorUiState.mobileMode || "canvas");
        const objectAction = String(btn.getAttribute("data-mobile-object-action") || "");
        const lockToPrimaryLandscape = isLandscapeMobileViewport() && !isModernUi();
        if (!lockToPrimaryLandscape && currentMode === "object" && action === "preview" && objectAction) {
          resolveMobileAction(objectAction);
          return;
        }
        if (!lockToPrimaryLandscape && currentMode === "object" && action === "insert" && objectAction && objectAction !== "open-edit-sheet") {
          resolveMobileAction(objectAction);
          return;
        }
        const key = resolveSheetKey(action);
        if (!key || !mobileSheetRefs[key]) return;
        root.setAttribute("data-dc-pane-expanded", "1");
        toggleSheet(action);
        if (key === "canvas") syncCanvasToggleButtons();
      });
      nav.setAttribute("data-delegated", "1");
    }
    // No default sheet open in desktop classic.
    const activeAction = String(navActionFromSheetKey(String(root.getAttribute("data-mobile-sheet-open") || "")) || "");
    try {
      const hasOpenSheet = root.classList.contains("ds-mobile-sheet-open") && !!String(root.getAttribute("data-mobile-sheet-open") || "");
      root.setAttribute("data-dc-pane-expanded", hasOpenSheet ? "1" : "0");
    } catch (e) {}
    nav.querySelectorAll(".ds-desktop-sheet-nav-btn").forEach((btn) => {
      const on = String(btn.getAttribute("data-mobile-action") || "") === activeAction;
      btn.classList.toggle("is-active", on);
    });
    // Ensure left-rail icons are always present/visible even if earlier icon pass was skipped.
    try { applyUnifiedUiIcons(nav); } catch (e) {}
    try {
      nav.querySelectorAll(".ds-desktop-sheet-nav-btn .ds-desktop-sheet-nav-ic").forEach((iconHost) => {
        if (!iconHost) return;
        const hasSvg = !!iconHost.querySelector("svg");
        if (hasSvg) {
          // If SVG icon is present, keep icon host text empty to prevent duplicate glyph rendering.
          iconHost.childNodes.forEach((n) => {
            if (n && n.nodeType === 3 && String(n.nodeValue || "").trim().length) {
              n.nodeValue = "";
            }
          });
        }
      });
    } catch (e2) {}
  };

  const ensureLandscapeClassicRailFallback = () => {
    try {
      if (isModernUi()) return;
      if (!(typeof window !== "undefined" && window.matchMedia && window.matchMedia("(max-width: 1023px) and (orientation: landscape)").matches)) return;
      const leftTools = root.querySelector('[data-role="left-tools"]');
      if (!leftTools) return;
      let nav = leftTools.querySelector(".ds-desktop-sheet-nav");
      if (!nav) {
        nav = document.createElement("div");
        nav.className = "ds-desktop-sheet-nav ds-landscape-fallback-nav";
        nav.setAttribute("aria-label", tUi("landscape_classic_rail", "Landscape Classic Rail"));
        nav.innerHTML = buildDesktopSheetNavButtonsMarkup();
        leftTools.insertBefore(nav, leftTools.firstChild);
      }
      if (!nav.hasAttribute("data-fallback-bound")) {
        nav.addEventListener("click", (e) => {
          const btn = e.target && e.target.closest ? e.target.closest("[data-mobile-action]") : null;
          if (!btn) return;
          const action = String(btn.getAttribute("data-mobile-action") || "");
          if (!action) return;
          e.preventDefault();
          setPendingSheetSide(isLandscapeMobileViewport() && !isModernUi() ? "right" : "left");
          root.setAttribute("data-dc-pane-expanded", "1");
          try {
            const key = resolveSheetKey(action);
            if (key && mobileSheetRefs[key]) {
              toggleSheet(action);
              if (key === "canvas") syncCanvasToggleButtons();
            } else {
              resolveMobileAction(action);
            }
          } catch (err) {}
        });
        nav.setAttribute("data-fallback-bound", "1");
      }
      try { applyUnifiedUiIcons(nav); } catch (e3) {}
    } catch (e) {}
  };

  const resolveMobileAction = (action) => {
    const actionName = String(action || "");
    if (guardZoneSelectionLock(actionName)) return true;
    const emojiMap = {
      "emoji-smile": "\uD83D\uDE42",
      "emoji-heart": "\u2764\uFE0F",
      "emoji-fire": "\uD83D\uDD25",
      "emoji-star": "\u2B50",
      "emoji-cool": "\uD83D\uDE0E",
      "emoji-party": "\uD83C\uDF89",
      "emoji-check": "\u2705",
      "emoji-thumbs-up": "\uD83D\uDC4D",
      "emoji-lightbulb": "\uD83D\uDCA1",
      "emoji-rocket": "\uD83D\uDE80",
      "emoji-gift": "\uD83C\uDF81",
      "emoji-camera": "\uD83D\uDCF7",
      "emoji-sparkles": "\u2728",
      "emoji-rainbow": "\uD83C\uDF08",
      "emoji-sun": "\u2600\uFE0F",
      "emoji-moon": "\uD83C\uDF19",
      "emoji-flower": "\uD83C\uDF38",
      "emoji-leaf": "\uD83C\uDF43",
      "emoji-music": "\uD83C\uDFB5",
      "emoji-headphones": "\uD83C\uDFA7",
      "emoji-coffee": "\u2615",
      "emoji-lightning": "\u26A1",
      "emoji-target": "\uD83C\uDFAF",
      "emoji-trophy": "\uD83C\uDFC6",
      "emoji-clap": "\uD83D\uDC4F",
      "emoji-pray": "\uD83D\uDE4F",
      "emoji-laugh": "\uD83D\uDE02",
      "emoji-wink": "\uD83D\uDE09",
      "emoji-thinking": "\uD83E\uDD14",
      "emoji-ok": "\uD83D\uDC4C",
      "emoji-100": "\uD83D\uDCAF",
      "emoji-balloon": "\uD83C\uDF88",
      "emoji-palette": "\uD83C\uDFA8",
      "emoji-pen": "\u270D\uFE0F",
      "emoji-scissors": "\u2702\uFE0F",
      "emoji-magnet": "\uD83E\uDDF2",
      "emoji-diamond": "\uD83D\uDC8E",
      "emoji-crown": "\uD83D\uDC51",
      "emoji-wave": "\uD83C\uDF0A",
      "emoji-globe": "\uD83C\uDF10"
    };
    if (Object.prototype.hasOwnProperty.call(emojiMap, actionName)) {
      return insertEmojiText(emojiMap[actionName]);
    }
    if (actionName === "open-emoticons") {
      if (isClassicDesktopViewport()) {
        openAssetLibraryModal("emoticons");
        return true;
      }
      openSheet("emoticons");
      return true;
    }
    if (actionName === "open-icons") {
      if (isClassicDesktopViewport()) {
        openAssetLibraryModal("icons");
        return true;
      }
      openSheet("icons");
      return true;
    }
    if (actionName.indexOf("icon-") === 0) {
      return insertIconSvg(actionName.slice(5));
    }
    switch (action) {
      case "exit-flow":
        openMobileExitConfirmModal();
        return true;
      case "open-font-picker":
        openMobileFontPickerModal();
        return true;
      case "hard-refresh":
        // Portrait UX: refresh must always respond immediately.
        if (!isLandscapeMobileViewport()) {
          runHardRefreshNow();
          return true;
        }
        openMobileRefreshConfirmModal();
        return true;

      case "open-arrange":
        openSheet("arrange");
        return true;

      case "toggle-theme": {
        const current = String(root.getAttribute("data-dc-theme") || "dark").toLowerCase();
        const next = current === "light" ? "dark" : "light";
        applyDesktopClassicTheme(next);
        return true;
      }

      case "set-theme-dark":
        applyDesktopClassicTheme("dark");
        return true;

      case "set-theme-light":
        applyDesktopClassicTheme("light");
        return true;

      case "save":
        return clickRealControl('.ds-toolbar [data-action="save"]')
          || clickRealControl('.ds-toolbar [data-action="final-save"]');

      case "apply":
        (async () => {
          const ok = await ensureProtectedActionAllowed("apply", false);
          if (!ok) return;
          if (typeof openApplyProductModal === "function") openApplyProductModal();
        })();
        return true;
 
      case "final-save":
        (async () => {
          const ok = await ensureProtectedActionAllowed("save", false);
          if (!ok) return;
          if (typeof saveDesign === "function") saveDesign();
        })();
        return true;

      case "upload-image":
        if (guardZoneSelectionLock("upload-image")) return true;
        return triggerFileInput('input[data-action="upload-image"]');

      case "upload-svg":
        if (guardZoneSelectionLock("upload-svg")) return true;
        return triggerFileInput('input[data-action="upload-svg"]');

      case "add-text":
        return clickRealControl('.ds-toolbar [data-action="add-text"]');

      case "open-textures":
        return clickRealControl('.ds-toolbar [data-action="open-textures"]');

      case "open-frames":
        return clickRealControl('.ds-toolbar [data-action="open-frames"]');

      case "open-shapes":
        openAssetLibraryModal("shapes");
        return true;

      case "open-art-sheet":
        openSheet("art");
        return true;

      case "object-props":
        if (clickRealControl('.ds-toolbar [data-action="object-props"]')) return true;
        if (typeof openPropsModal === "function") {
          openPropsModal();
          return true;
        }
        return false;
      case "edit-text":
        if (typeof root.__dsEnterTextEdit === "function") {
          try { return !!root.__dsEnterTextEdit(); } catch (e) {}
        }
        return false;

      case "my-designs":
        if (clickRealControl('.ds-toolbar [data-action="my-designs"]')) return true;
        if (typeof openMyDesignsModal === "function") {
          openMyDesignsModal();
          return true;
        }
        return false;

      case "load-design":
        if (mobileDesignIdInput && designIdInput) {
          designIdInput.value = String(mobileDesignIdInput.value || "").trim();
        }
        if (clickRealControl('.ds-toolbar [data-action="load-design"]')) return true;
        if (typeof loadDesignById === "function") {
          const id = designIdInput ? parseInt(designIdInput.value || "0", 10) : 0;
          if (id > 0) {
            loadDesignById(id);
            return true;
          }
        }
        return false;

      case "export":
        return clickRealControl('.ds-toolbar [data-action="export"]');

      case "template-apply":
        return applyTemplateSelection();

      case "zoom-in":
        return nudgeZoom(1);

      case "zoom-out":
        return nudgeZoom(-1);

      case "ai-remove-bg":
        return clickRealControl('.ds-toolbar [data-action="ai-remove-bg"]');

      case "crop-image":
        if (clickRealControl('.ds-toolbar [data-action="crop-image"]')) return true;
        if (typeof cropSelectedImage === "function") {
          try {
            cropSelectedImage();
            return true;
          } catch (e) {}
        }
        return false;

      case "crop-apply":
        if (clickRealControl('.ds-toolbar [data-action="crop-apply"]')) return true;
        if (typeof applyCropSession === "function") {
          try {
            applyCropSession();
            return true;
          } catch (e) {}
        }
        return false;

      case "crop-cancel":
        if (clickRealControl('.ds-toolbar [data-action="crop-cancel"]')) return true;
        if (typeof cancelCropSession === "function") {
          try {
            cancelCropSession("Crop canceled.");
            return true;
          } catch (e) {}
        }
        return false;

      case "duplicate-object":
        if (duplicateActiveObject()) return true;
        return clickRealControl('.ds-toolbar [data-action="duplicate-object"]');

      case "delete-object":
        if (deleteActiveObject()) { pushHistory(); return true; }
        return clickRealControl('.ds-toolbar [data-action="delete-object"]');

      case "unselect-object":
        try {
          if (canvas && canvas.getActiveObject()) {
            canvas.discardActiveObject();
            canvas.requestRenderAll();
            if (typeof root.__dsSyncMobileObjectMode === "function") root.__dsSyncMobileObjectMode();
            syncMobileModeDirectFromSelection();
          }
          closeSheets();
          return true;
        } catch (e) {
          return false;
        }

      case "undo":
        return clickRealControl('.ds-toolbar [data-action="undo"]');

      case "redo":
        return clickRealControl('.ds-toolbar [data-action="redo"]');

      case "group":
        return clickRealControl('.ds-toolbar [data-action="group"]');

      case "ungroup":
        return clickRealControl('.ds-toolbar [data-action="ungroup"]');

      case "layer-up":
        return clickRealControl('.ds-toolbar [data-action="layer-up"]');

      case "layer-down":
        return clickRealControl('.ds-toolbar [data-action="layer-down"]');

      case "layer-front":
        return clickRealControl('.ds-toolbar [data-action="layer-front"]');

      case "layer-back":
        return clickRealControl('.ds-toolbar [data-action="layer-back"]');

      case "fit-canvas":
        return clickRealControl('.ds-toolbar [data-action="fit-canvas"]');

      case "zoom-reset":
        return clickRealControl('.ds-toolbar [data-action="zoom-reset"]');

      case "toggle-fullscreen":
        return clickRealControl('.ds-toolbar [data-action="toggle-fullscreen"]');

      case "mask":
        return clickRealControl('.ds-toolbar [data-action="mask"]');

      case "mask-bake":
        return clickRealControl('.ds-toolbar [data-action="mask-bake"]');

      case "align-left":
        return clickRealControl('.ds-toolbar [data-align="left"]');

      case "align-center":
        return clickRealControl('.ds-toolbar [data-align="center"]');

      case "align-right":
        return clickRealControl('.ds-toolbar [data-align="right"]');

      case "align-top":
        return clickRealControl('.ds-toolbar [data-align="top"]');

      case "align-middle":
        return clickRealControl('.ds-toolbar [data-align="middle"]');

      case "align-bottom":
        return clickRealControl('.ds-toolbar [data-align="bottom"]');

      case "text-align-left":
        applyProp("textAlign", "left");
        syncMobileTextAlignButtons("left");
        return true;

      case "text-align-center":
        applyProp("textAlign", "center");
        syncMobileTextAlignButtons("center");
        return true;

      case "text-align-right":
        applyProp("textAlign", "right");
        syncMobileTextAlignButtons("right");
        return true;

      case "toggle-grid": {
        const cb = getToolbarAction('.ds-toolbar input[data-opt="grid"]');
        if (!cb) return false;
        cb.checked = !cb.checked;
        try { cb.dispatchEvent(new Event("input", { bubbles: true })); } catch (e) {}
        try { cb.dispatchEvent(new Event("change", { bubbles: true })); } catch (e) {}
        try { syncCanvasToggleButtons(); } catch (e) {}
        return true;
      }

      case "toggle-snap": {
        const cb = getToolbarAction('.ds-toolbar input[data-opt="snap"]');
        if (!cb) return false;
        cb.checked = !cb.checked;
        try { cb.dispatchEvent(new Event("input", { bubbles: true })); } catch (e) {}
        try { cb.dispatchEvent(new Event("change", { bubbles: true })); } catch (e) {}
        try { syncCanvasToggleButtons(); } catch (e) {}
        return true;
      }

      case "toggle-bleed-marks": {
        const cb = getToolbarAction('.ds-toolbar input[data-opt="bleed-marks"]');
        if (!cb) return false;
        cb.checked = !cb.checked;
        try { cb.dispatchEvent(new Event("input", { bubbles: true })); } catch (e) {}
        try { cb.dispatchEvent(new Event("change", { bubbles: true })); } catch (e) {}
        try { syncCanvasToggleButtons(); } catch (e) {}
        return true;
      }

      case "shape-rect":
        return clickRealControl('.ds-toolbar [data-shape="rect"]');

      case "shape-circle":
        return clickRealControl('.ds-toolbar [data-shape="circle"]');

      case "shape-triangle":
        return clickRealControl('.ds-toolbar [data-shape="triangle"]');

      case "shape-star":
        return clickRealControl('.ds-toolbar [data-shape="star"]');

      case "shape-heart":
        return clickRealControl('.ds-toolbar [data-shape="heart"]');

      case "shape-polygon":
        return clickRealControl('.ds-toolbar [data-shape="polygon"]');

      case "emoji-smile":
        return insertEmojiText("\ud83d\ude42");

      case "emoji-heart":
        return insertEmojiText("\u2764\uFE0F");

      case "emoji-fire":
        return insertEmojiText("\ud83d\udd25");

      case "emoji-star":
        return insertEmojiText("\u2B50");

      case "emoji-cool":
        return insertEmojiText("\ud83d\ude0e");

      case "emoji-party":
        return insertEmojiText("\ud83c\udf89");

      case "emoji-check":
        return insertEmojiText("\u2705");

      case "emoji-thumbs-up":
        return insertEmojiText("\ud83d\udc4d");

      case "emoji-lightbulb":
        return insertEmojiText("\ud83d\udca1");

      case "emoji-rocket":
        return insertEmojiText("\ud83d\ude80");

      case "emoji-gift":
        return insertEmojiText("\ud83c\udf81");

      case "emoji-camera":
        return insertEmojiText("\ud83d\udcf7");

      case "mug-open":
        if (enablePreview3D) {
          if (typeof openPreviewModal === "function") {
            openPreviewModal();
            return true;
          }
          return clickRealControl('[data-action="mug-open"]')
            || clickRealControl('[data-role="mug-canvas-mini"]');
        }
        if (typeof openProductPreview2DModal === "function") {
          openProductPreview2DModal();
          return true;
        }
        return false;

      case "preview":
        if (enablePreview3D) {
          if (typeof openPreviewModal === "function") {
            openPreviewModal();
            return true;
          }
          return clickRealControl('[data-action="mug-open"]')
            || clickRealControl('[data-role="mug-canvas-mini"]');
        }
        openSheet("preview");
        return true;

      case "open-zones":
        if (enablePreview3D) {
          if (typeof openPreviewModal === "function") {
            openPreviewModal();
            return true;
          }
          return clickRealControl('[data-action="mug-open"]')
            || clickRealControl('[data-role="mug-canvas-mini"]')
            || false;
        }
        if (typeof openZonePickerModal === "function") {
          openZonePickerModal();
          return true;
        }
        return clickRealControl('.ds-toolbar .ds-zone-preview')
          || clickRealControl('.ds-toolbar [data-action="toggle-zones"]')
          || clickRealControl('.ds-toolbar [data-action="zone-picker-open"]')
          || clickRealControl('.ds-toolbar [data-action="object-props"]')
          || false;

      case "open-advanced":
        return clickRealControl('.ds-toolbar [data-action="object-props"]');

      default:
        return false;
    }
  };

  const updateMobileUiState = () => {
    hardStripLandscapeExitUi();
    ensurePortraitNeverFullscreen();
    ensureLandscapeAlwaysFullscreen();
    const ensureLandscapeZonePickerAssist = () => {
      try {
        if (!isLandscapeMobileViewport() || isModernUi()) return;
        if (!root.classList.contains("ds-no-active-canvas")) return;
        if (!zoneState || !zoneState.enabled) return;
        if (getSelectedZoneIds().length > 0) return;
        const now = Date.now();
        const last = Number(root.__dsLandscapeZoneAssistTs || 0);
        if (now - last < 900) return;
        root.__dsLandscapeZoneAssistTs = now;
        const attemptOpen = () => {
          try { setZoneSelectionGate(true); } catch (e) {}
          try { openZonePickerModal(); } catch (e) {}
        };
        attemptOpen();
        window.setTimeout(attemptOpen, 120);
        window.setTimeout(attemptOpen, 320);
      } catch (e) {}
    };
    syncDesktopClassicViewportLock();
    ensureLandscapeClassicRailFallback();
    initClassicDesktopSheetLaunchers();
    ensureLandscapeZonePickerAssist();
    const measureMobileBarGap = () => {
      if (!isMobileViewport()) return null;
      const topBarEl = root.querySelector('[data-role="mobile-topbar"]');
      const bottomNavEl = root.querySelector('[data-role="mobile-bottom-nav"]');
      if (!topBarEl || !bottomNavEl) return null;
      const topRect = topBarEl.getBoundingClientRect();
      const navRect = bottomNavEl.getBoundingClientRect();
      const viewportH = Math.max(
        1,
        Math.round((window.visualViewport && window.visualViewport.height) || window.innerHeight || 0)
      );
      const topBottom = Math.round(topRect.bottom || 0);
      const navTop = Math.round(navRect.top || viewportH);
      const gap = Math.max(0, navTop - topBottom);
      const out = {
        viewport_h: viewportH,
        topbar_bottom: topBottom,
        bottombar_top: navTop,
        workspace_gap_px: gap
      };
      try { root.__dsMobileGap = out; } catch (e) {}
      try {
        if (window.localStorage && window.localStorage.getItem("ds_debug_bars") === "1") {
          if (typeof console !== "undefined" && typeof console.debug === "function") {
            console.debug("[DS mobile bars]", out);
          }
        }
      } catch (e2) {}
      return out;
    };
    root.__dsMeasureBars = measureMobileBarGap;

    const enforceMobilePortraitWorkspaceFullHeight = () => {
      const isPortrait = isMobileViewport() && !window.matchMedia("(orientation: landscape)").matches;
      const workspace = root.querySelector(".ds-workspace");
      const wsGrid = root.querySelector(".ds-workspace-grid");
      const centerStage = root.querySelector(".ds-center-stage");
      const canvasShell = root.querySelector(".ds-canvas-shell");
      const canvasWrap = root.querySelector(".ds-canvas-wrap");
      const canvasViewport = root.querySelector(".ds-canvas-viewport");
      const topBarEl = root.querySelector('[data-role="mobile-topbar"]');
      const bottomNavEl = root.querySelector('[data-role="mobile-bottom-nav"]');
      const targets = [wsGrid, centerStage, canvasShell, canvasWrap, canvasViewport].filter(Boolean);

      if (!isPortrait) {
        root.classList.remove("ds-mobile-kb-open");
        if (workspace) {
          workspace.style.removeProperty("position");
          workspace.style.removeProperty("inset");
          workspace.style.removeProperty("width");
          workspace.style.removeProperty("height");
          workspace.style.removeProperty("min-height");
          workspace.style.removeProperty("max-height");
        }
        targets.forEach((el) => {
          el.style.removeProperty("position");
          el.style.removeProperty("top");
          el.style.removeProperty("right");
          el.style.removeProperty("bottom");
          el.style.removeProperty("left");
          el.style.removeProperty("height");
          el.style.removeProperty("min-height");
          el.style.removeProperty("max-height");
        });
        return;
      }

      const vv = (typeof window !== "undefined" && window.visualViewport) ? window.visualViewport : null;
      const vvH = Math.max(1, Math.round((vv && vv.height) || window.innerHeight || 0));
      const winH = Math.max(1, Math.round(window.innerHeight || vvH));
      if (!updateMobileUiState.__portraitBaseVh) {
        updateMobileUiState.__portraitBaseVh = Math.max(vvH, winH);
      }
      let baseVh = Math.max(1, Number(updateMobileUiState.__portraitBaseVh) || Math.max(vvH, winH));
      const keyboardOpen = !!(vv && vvH < (baseVh - 120));
      if (!keyboardOpen) {
        baseVh = Math.max(baseVh, vvH, winH);
        updateMobileUiState.__portraitBaseVh = baseVh;
      }
      const vh = keyboardOpen ? baseVh : Math.max(vvH, winH);
      root.classList.toggle("ds-mobile-kb-open", keyboardOpen);
      root.style.setProperty("height", vh + "px", "important");
      root.style.setProperty("min-height", vh + "px", "important");
      root.style.setProperty("max-height", vh + "px", "important");
      root.style.setProperty("overflow", "hidden", "important");

      if (workspace) {
        workspace.style.setProperty("position", "fixed", "important");
        workspace.style.setProperty("inset", "0", "important");
        workspace.style.setProperty("width", "100vw", "important");
        workspace.style.setProperty("height", vh + "px", "important");
        workspace.style.setProperty("min-height", vh + "px", "important");
        workspace.style.setProperty("max-height", vh + "px", "important");
      }

      targets.forEach((el) => {
        el.style.setProperty("height", "100%", "important");
        el.style.setProperty("min-height", "0", "important");
        el.style.setProperty("max-height", "none", "important");
      });

      // Direct canvas viewport sizing by real bar geometry (most reliable on mobile).
      if (canvasViewport) {
        const topRect = topBarEl ? topBarEl.getBoundingClientRect() : { bottom: 0 };
        const navRect = bottomNavEl ? bottomNavEl.getBoundingClientRect() : { top: vh };
        const canvasTopSafeGap = 4; // keep a tiny visual gap below top bar during zoom/pan
        const topInset = Math.max(0, Math.round(topRect.bottom || 0) + canvasTopSafeGap);
        const bottomInset = Math.max(0, Math.round(vh - (navRect.top || vh)));
        canvasViewport.style.setProperty("position", "fixed", "important");
        canvasViewport.style.setProperty("top", topInset + "px", "important");
        canvasViewport.style.setProperty("right", "0", "important");
        canvasViewport.style.setProperty("bottom", bottomInset + "px", "important");
        canvasViewport.style.setProperty("left", "0", "important");
        canvasViewport.style.setProperty("height", "auto", "important");
        canvasViewport.style.setProperty("min-height", "0", "important");
        canvasViewport.style.setProperty("max-height", "none", "important");
      }
    };

    if (typeof updateMobileUiState.__didInitialFit === "undefined") {
      updateMobileUiState.__didInitialFit = false;
    }
    if (typeof updateMobileUiState.__lastViewportKey === "undefined") {
      updateMobileUiState.__lastViewportKey = "";
    }
    if (typeof updateMobileUiState.__lastOrientMode === "undefined") {
      updateMobileUiState.__lastOrientMode = "";
    }
    const orientMode = isLandscapeMobileViewport() ? "landscape" : (isMobileViewport() ? "portrait" : "desktop");
    const orientChanged = updateMobileUiState.__lastOrientMode !== orientMode;
    updateMobileUiState.__lastOrientMode = orientMode;

    // When rotating back to portrait, reset mobile nav/sheet state to avoid mixed button modes.
    if (orientChanged && orientMode === "portrait") {
      try { closeSheets(); } catch (e1) {}
      try { switchToCanvasMode(); } catch (e2) {}
      try { restoreCanvasModeSheets(); } catch (e3) {}
      try { setNavModeLabels("canvas"); } catch (e4) {}
      try { root.setAttribute("data-dc-pane-expanded", "0"); } catch (e5) {}
    }
    if (!isMobileViewport()) {
      if (isClassicDesktopViewport()) {
        enforceMobilePortraitWorkspaceFullHeight();
        try { syncMobileEmptyStateOverlay(); } catch (err) {}
        syncMobileZoomUi();
        syncMobileModeFromSelection();
        hardStripLandscapeExitUi();
        return;
      }
      closeSheets();
      switchToCanvasMode();
      enforceMobilePortraitWorkspaceFullHeight();
      updateMobileUiState.__didInitialFit = false;
      updateMobileUiState.__lastViewportKey = "";
      return;
    }
    if (!MOBILE_LANDSCAPE_ENABLED && window.matchMedia("(orientation: landscape)").matches) {
      closeSheets();
      switchToCanvasMode();
      enforceMobilePortraitWorkspaceFullHeight();
      updateMobileUiState.__lastViewportKey = "";
      return;
    }
    enforceMobilePortraitWorkspaceFullHeight();
    measureMobileBarGap();
    const vpKey = String(window.innerWidth || 0) + "x" + String(window.innerHeight || 0);
    const viewportChanged = updateMobileUiState.__lastViewportKey !== vpKey;
    if (!updateMobileUiState.__didInitialFit || viewportChanged) {
      resolveMobileAction("fit-canvas");
      updateMobileUiState.__didInitialFit = true;
      updateMobileUiState.__lastViewportKey = vpKey;
    }
    try { syncMobileEmptyStateOverlay(); } catch (err) {}
    syncMobileZoomUi();
    syncMobileModeFromSelection();
    hardStripLandscapeExitUi();
  };

  const getPreferredSheetSide = () => (isLandscapeMobileViewport() ? "left" : "left");

  if (top) {
    top.addEventListener("click", (e) => {
      if (!MOBILE_LANDSCAPE_ENABLED && window.matchMedia("(max-width: 1023px) and (orientation: landscape)").matches) return;
      const btn = e.target && e.target.closest ? e.target.closest("[data-mobile-action]") : null;
      if (!btn) return;
      if (btn.tagName && String(btn.tagName).toLowerCase() === "a") e.preventDefault();
      const action = String(btn.getAttribute("data-mobile-action") || "");
      if (!action) return;
      setPendingSheetSide(getPreferredSheetSide());
      resolveMobileAction(action);
      window.setTimeout(syncMobileZoomUi, 0);
    });
  }

  // Portrait hard-refresh safety: direct handler for topbar refresh button.
  if (!root.__dsPortraitRefreshBound) {
    root.addEventListener("click", (e) => {
      const btn = e.target && e.target.closest ? e.target.closest('[data-mobile-action="hard-refresh"]') : null;
      if (!btn) return;
      if (!isMobileViewport() || isLandscapeMobileViewport()) return;
      e.preventDefault();
      e.stopPropagation();
      runHardRefreshNow();
    }, true);
    root.__dsPortraitRefreshBound = true;
  }

  if (desktopTopActions) {
    desktopTopActions.addEventListener("click", (e) => {
      const btn = e.target && e.target.closest ? e.target.closest("[data-mobile-action]") : null;
      if (!btn) return;
      const action = String(btn.getAttribute("data-mobile-action") || "");
      if (!action) return;
      e.preventDefault();
      setPendingSheetSide(getPreferredSheetSide());
      resolveMobileAction(action);
      window.setTimeout(syncMobileZoomUi, 0);
    });
  }

  // Desktop/Landscape resilient topbar bridge:
  // handles dynamically rendered top bars (classic topbar and dc-topbar utility panel).
  if (!root.__dsDesktopTopbarDelegated) {
    root.addEventListener("click", (e) => {
      if (isMobilePortrait()) return;
      const btn = e.target && e.target.closest
        ? e.target.closest('.ds-topbar [data-mobile-action], [data-role="dc-topbar"] [data-mobile-sheet-action], .ds-topbar-back, [data-role="dc-topbar"] .ds-topbar-back')
        : null;
      if (!btn) return;
      const inTopbar = !!(btn.closest(".ds-topbar") || btn.closest('[data-role="dc-topbar"]'));
      if (!inTopbar) return;
      let action = String(btn.getAttribute("data-mobile-action") || btn.getAttribute("data-mobile-sheet-action") || "");
      if (!action && (btn.classList.contains("ds-topbar-back") || btn.classList.contains("ds-mobile-topbar-back"))) {
        action = "exit-flow";
      }
      if (!action) return;
      e.preventDefault();
      setPendingSheetSide(getPreferredSheetSide());
      resolveMobileAction(action);
      window.setTimeout(syncMobileZoomUi, 0);
    });
    root.__dsDesktopTopbarDelegated = true;
  }

  if (cta) {
    cta.addEventListener("click", () => {
      if (!MOBILE_LANDSCAPE_ENABLED && window.matchMedia("(max-width: 1023px) and (orientation: landscape)").matches) return;
      setPendingSheetSide(getPreferredSheetSide());
      const ok = resolveMobileAction("apply");
      if (!ok) setStatus("Save design first, then use Apply to Product.");
    });
  }

  if (nav) {
    nav.addEventListener("click", (e) => {
      if (!MOBILE_LANDSCAPE_ENABLED && window.matchMedia("(max-width: 1023px) and (orientation: landscape)").matches) return;
      const btn = e.target && e.target.closest ? e.target.closest("[data-mobile-action]") : null;
      if (!btn) return;
      const action = String(btn.getAttribute("data-mobile-action") || "");
      if (!action) return;
      if (isStartupInteractionLocked()) {
        triggerStartupFlowRequiredStatus();
        return;
      }
      if (isZoneSelectionInteractionLocked()) {
        triggerZoneSelectionRequiredStatus();
        return;
      }
      setPendingSheetSide("right");

      const navLabel = String(btn.getAttribute("data-label") || btn.textContent || "").trim().toLowerCase();
      if (navLabel === "edit" || navLabel === "style" || navLabel === "arrange") {
        editorUiState.mobileMode = "object";
        root.setAttribute("data-mobile-mode", "object");
        setNavModeLabels("object");
        try { renderObjectModeSheets(); } catch (err) {}
        const byLabel = { edit: "edit", style: "style", arrange: "arrange" };
        const target = byLabel[navLabel] || "";
        if (target) {
          setSheetUiState(true, target, action);
          window.setTimeout(syncMobileZoomUi, 0);
          return;
        }
      }

      if (action === "insert" || action === "canvas" || action === "library" || action === "preview" || action === "art" || action === "upload" || action === "text") {
        const currentMode = String(root.getAttribute("data-mobile-mode") || editorUiState.mobileMode || "canvas");
        if (currentMode === "object" && (action === "insert" || action === "canvas" || action === "library")) {
          editorUiState.mobileMode = "object";
          root.setAttribute("data-mobile-mode", "object");
          setNavModeLabels("object");
          try { renderObjectModeSheets(); } catch (err) {}
          const objectSheetByNav = { insert: "edit", canvas: "style", library: "arrange" };
          const forcedSheet = objectSheetByNav[action] || "";
          if (forcedSheet) {
            setSheetUiState(true, forcedSheet, action);
            window.setTimeout(syncMobileZoomUi, 0);
            return;
          }
        }
        if (currentMode === "object" && action === "preview") {
          const objectPreviewAction = String(btn.getAttribute("data-mobile-object-action") || "");
          if (objectPreviewAction) {
            resolveMobileAction(objectPreviewAction);
            window.setTimeout(syncMobileZoomUi, 0);
            return;
          }
        }
        if (action === "preview" && enablePreview3D) {
          closeSheets();
          resolveMobileAction("preview");
          window.setTimeout(syncMobileZoomUi, 0);
          return;
        }
        editorUiState.mobileMode = "canvas";
        root.setAttribute("data-mobile-mode", "canvas");
        setNavModeLabels("canvas");
        restoreCanvasModeSheets();
        if (action === "canvas" && isClassicDesktopViewport()) {
          try { ensureDesktopClassicCanvasQuickActions(); } catch (e) {}
        }
        toggleSheet(action);
        return;
      }

      resolveMobileAction(action);
      window.setTimeout(syncMobileZoomUi, 0);
    });
  }

  if (sheetsWrap) {
    let mobileRangeRaf = 0;
    let mobileRangePending = null;
    sheetsWrap.addEventListener("input", (e) => {
      const zoomEl = e.target && e.target.closest ? e.target.closest('[data-role="mobile-zoom-slider"]') : null;
      if (zoomEl) {
        setZoomFromMobileSlider(zoomEl.value);
        return;
      }
      const styleEl = e.target && e.target.closest ? e.target.closest("[data-mobile-style-prop], [data-mobile-arrange-prop]") : null;
      if (!styleEl) return;
      const arrangeProp = styleEl.getAttribute("data-mobile-arrange-prop");
      const inputType = String(styleEl.type || "").toLowerCase();
      const isLiveField = (inputType === "range" || inputType === "color");
      const isLiveArrangeNumber = !!arrangeProp && inputType === "number";
      if (!isLiveField && !isLiveArrangeNumber) return;
      let styleProp = styleEl.getAttribute("data-mobile-style-prop");
      let styleValue = styleEl.value;
      if (styleProp === "fill-hex") styleProp = "fill";
      else if (styleProp === "stroke-hex") styleProp = "stroke";
      if (isLiveArrangeNumber) {
        const next = String(styleValue == null ? "" : styleValue).trim();
        if (next === "" || next === "-" || next === "+") return;
        applyProp(arrangeProp, next, { skipHistory: true, skipUiSync: true });
        return;
      }
      if (inputType === "range") {
        mobileRangePending = {
          styleProp: styleProp || "",
          arrangeProp: arrangeProp || "",
          value: styleValue
        };
        if (!mobileRangeRaf) {
          mobileRangeRaf = window.requestAnimationFrame(() => {
            const p = mobileRangePending;
            mobileRangeRaf = 0;
            mobileRangePending = null;
            if (!p) return;
            if (p.styleProp) applyProp(p.styleProp, p.value, { skipHistory: true, skipUiSync: true });
            if (p.arrangeProp) applyProp(p.arrangeProp, p.value, { skipHistory: true, skipUiSync: true });
          });
        }
        return;
      }
      if (styleProp) applyProp(styleProp, styleValue, { skipHistory: true, skipUiSync: true });
      if (arrangeProp) applyProp(arrangeProp, styleEl.value, { skipHistory: true, skipUiSync: true });
    });
    sheetsWrap.addEventListener("change", (e) => {
      const tplEl = e.target && e.target.closest ? e.target.closest('[data-role="mobile-template-select"]') : null;
      if (tplEl) {
        applyTemplateSelection();
        return;
      }
      const zoomEl = e.target && e.target.closest ? e.target.closest('[data-role="mobile-zoom-slider"]') : null;
      if (zoomEl) {
        setZoomFromMobileSlider(zoomEl.value);
        return;
      }
      const styleEl = e.target && e.target.closest ? e.target.closest("[data-mobile-style-prop], [data-mobile-arrange-prop]") : null;
      if (!styleEl) return;
      let styleProp = styleEl.getAttribute("data-mobile-style-prop");
      let styleValue = styleEl.value;
      if (styleProp === "fill-hex") styleProp = "fill";
      else if (styleProp === "stroke-hex") styleProp = "stroke";
      const arrangeProp = styleEl.getAttribute("data-mobile-arrange-prop");
      if (styleProp) applyProp(styleProp, styleValue);
      if (arrangeProp) applyProp(arrangeProp, styleEl.value);
    });
    sheetsWrap.addEventListener("click", (e) => {
      const closeSheetsAfterAction = () => {
        if (isClassicDesktopViewport()) return;
        closeSheets();
      };
      const closeBtn = e.target && e.target.closest ? e.target.closest("[data-mobile-sheet-close]") : null;
      if (closeBtn) {
        closeSheets();
        return;
      }

      const strokeToggleBtn = e.target && e.target.closest ? e.target.closest('[data-role="mobile-stroke-side-toggle"]') : null;
      if (strokeToggleBtn) {
        const current = String(strokeToggleBtn.getAttribute("data-value") || "inside");
        const next = current === "outside" ? "inside" : "outside";
        const host = strokeToggleBtn.closest(".ds-mobile-stroke-side-field");
        const hidden = host ? host.querySelector('[data-mobile-style-prop="strokeSide"]') : null;
        if (hidden) hidden.value = next;
        syncMobileStrokeSideToggleUi(next);
        applyProp("strokeSide", next);
        return;
      }

      const actionBtn = e.target && e.target.closest ? e.target.closest("[data-mobile-sheet-action]") : null;
      if (!actionBtn) return;
      const sheetEl = actionBtn.closest ? actionBtn.closest("[data-mobile-sheet]") : null;
      const sheetKey = String(sheetEl && sheetEl.getAttribute ? (sheetEl.getAttribute("data-mobile-sheet") || "") : "");

      if (sheetKey === "emoticons") {
        const glyphDirect = String(actionBtn.getAttribute("data-emoji") || "").trim()
          || String(actionBtn.textContent || "").trim();
        const okDirect = !!glyphDirect && insertEmojiText(glyphDirect);
        if (okDirect) {
          window.setTimeout(syncMobileZoomUi, 0);
          closeSheetsAfterAction();
        }
        return;
      }

      if (sheetKey === "icons") {
        const iconNameDirect = String(actionBtn.getAttribute("data-icon") || "").trim()
          || String(actionBtn.getAttribute("data-mobile-sheet-action") || "").replace(/^icon-/, "");
        let okDirect = false;
        if (iconNameDirect) okDirect = insertIconSvg(iconNameDirect);
        if (!okDirect) {
          const glyphFallback = String(actionBtn.textContent || "").trim();
          if (glyphFallback) okDirect = insertEmojiText(glyphFallback);
        }
        if (okDirect) {
          window.setTimeout(syncMobileZoomUi, 0);
          closeSheetsAfterAction();
        }
        return;
      }

      const action = String(actionBtn.getAttribute("data-mobile-sheet-action") || "");
      if (!action) return;

      if (action === "open-art-sheet") {
        openSheet("art");
        return;
      }
      if (action === "open-font-picker") {
        resolveMobileAction(action);
        return;
      }

      if (action === "open-shapes" || action === "open-emoticons" || action === "open-icons") {
        resolveMobileAction(action);
        return;
      }
      if (action.indexOf("emoji-") === 0) {
        const glyph = String(actionBtn.getAttribute("data-emoji") || "");
        const ok = resolveMobileAction(action) || (glyph ? insertEmojiText(glyph) : false);
        if (ok) {
          window.setTimeout(syncMobileZoomUi, 0);
          closeSheetsAfterAction();
        }
        return;
      }
      if (action.indexOf("icon-") === 0) {
        const iconName = String(actionBtn.getAttribute("data-icon") || action.slice(5));
        const ok = insertIconSvg(iconName) || resolveMobileAction(action);
        if (ok) {
          window.setTimeout(syncMobileZoomUi, 0);
          closeSheetsAfterAction();
        }
        return;
      }

      if (action === "toggle-grid" || action === "toggle-snap" || action === "toggle-bleed-marks") {
        resolveMobileAction(action);
        window.setTimeout(syncCanvasToggleButtons, 0);
        return;
      }

      if (action === "object-props") {
        closeSheetsAfterAction();
        window.setTimeout(() => {
          resolveMobileAction(action);
          window.setTimeout(syncMobileZoomUi, 0);
        }, 40);
        return;
      }
      if (action === "edit-text") {
        const ok = resolveMobileAction(action);
        window.setTimeout(syncMobileZoomUi, 0);
        if (ok) {
          // Let virtual keyboard initialize before collapsing the sheet.
          window.setTimeout(() => closeSheetsAfterAction(), 140);
        } else {
          closeSheetsAfterAction();
        }
        return;
      }

      resolveMobileAction(action);
      window.setTimeout(syncMobileZoomUi, 0);
      closeSheetsAfterAction();
    });
  }

  if (sheetBackdrop) {
    sheetBackdrop.addEventListener("click", closeSheets);
  }

  if (mobileTemplateSelect) {
    mobileTemplateSelect.addEventListener("change", () => {
      applyTemplateSelection();
    });
  }

  syncMobileZoomUi();
  syncCanvasToggleButtons();

  // Mobile gestures: two-finger pan + pinch zoom.
  // Single-finger is reserved for object selection/manipulation to avoid UX conflict with multi-select/edit flows.
  const pinch = {
    active: false,
    startDist: 0,
    startZoom: 100,
    startCenterX: 0,
    startCenterY: 0,
    startLeft: 0,
    startTop: 0
  };
  const pinchDistance = (t0, t1) => {
    const dx = Number(t1.clientX || 0) - Number(t0.clientX || 0);
    const dy = Number(t1.clientY || 0) - Number(t0.clientY || 0);
    return Math.sqrt(dx * dx + dy * dy);
  };
  const pinchCenter = (t0, t1) => ({
    x: (Number(t0.clientX || 0) + Number(t1.clientX || 0)) / 2,
    y: (Number(t0.clientY || 0) + Number(t1.clientY || 0)) / 2
  });
  const pinchTarget = viewportEl || root.querySelector(".ds-canvas-viewport");
  if (pinchTarget) {
    const pinchSurfaces = [pinchTarget, canvas && canvas.upperCanvasEl, canvas && canvas.lowerCanvasEl]
      .filter((el, idx, arr) => !!el && arr.indexOf(el) === idx);
    const onPinchStart = (e) => {
      if (!isMobileViewport()) return;
      if (root.classList.contains("ds-mobile-sheet-open")) return;
      if (!e.touches || e.touches.length < 2) return;
      const t0 = e.touches[0];
      const t1 = e.touches[1];
      const d = pinchDistance(t0, t1);
      if (d <= 0) return;
      const c = pinchCenter(t0, t1);
      const baseZoomInput = getToolbarAction('.ds-toolbar [data-input="zoom"]');
      pinch.active = true;
      pinch.startDist = d;
      pinch.startZoom = Number(baseZoomInput && baseZoomInput.value || 100) || 100;
      pinch.startCenterX = c.x;
      pinch.startCenterY = c.y;
      pinch.startLeft = Number(pinchTarget.scrollLeft || 0);
      pinch.startTop = Number(pinchTarget.scrollTop || 0);
      e.preventDefault();
    };
    const onPinchMove = (e) => {
      if (!isMobileViewport()) return;
      if (root.classList.contains("ds-mobile-sheet-open")) return;
      if (!pinch.active || !e.touches || e.touches.length < 2) return;
      const t0 = e.touches[0];
      const t1 = e.touches[1];
      const d = pinchDistance(t0, t1);
      if (d <= 0 || pinch.startDist <= 0) return;

      const ratio = d / pinch.startDist;
      const next = Math.max(25, Math.min(300, Math.round(pinch.startZoom * ratio)));
      setZoomFromMobileSlider(next);

      const c = pinchCenter(t0, t1);
      const dx = c.x - pinch.startCenterX;
      const dy = c.y - pinch.startCenterY;
      pinchTarget.scrollLeft = Math.max(0, pinch.startLeft - dx);
      pinchTarget.scrollTop = Math.max(0, pinch.startTop - dy);

      e.preventDefault();
    };
    const stopPinch = () => {
      pinch.active = false;
      pinch.startDist = 0;
      pinch.startCenterX = 0;
      pinch.startCenterY = 0;
    };
    const onPinchEnd = (e) => {
      if (!e.touches || e.touches.length < 2) stopPinch();
    };
    pinchSurfaces.forEach((surface) => {
      surface.addEventListener("touchstart", onPinchStart, { passive: false });
      surface.addEventListener("touchmove", onPinchMove, { passive: false });
      surface.addEventListener("touchend", onPinchEnd, { passive: true });
      surface.addEventListener("touchcancel", stopPinch, { passive: true });
    });
  }

  document.addEventListener("keydown", (e) => {
    if (!isMobileViewport()) return;
    if (e.key !== "Escape" && e.key !== "Esc") return;
    if (!root.classList.contains("ds-mobile-sheet-open")) return;
    closeSheets();
  });

  window.addEventListener("resize", updateMobileUiState, { passive: true });
  window.addEventListener("orientationchange", updateMobileUiState, { passive: true });
  window.addEventListener("orientationchange", () => {
    window.setTimeout(updateMobileUiState, 80);
    window.setTimeout(updateMobileUiState, 260);
  }, { passive: true });
  window.addEventListener("resize", syncDesktopClassicViewportLock, { passive: true });
  window.addEventListener("orientationchange", syncDesktopClassicViewportLock, { passive: true });

  updateMobileUiState();
  syncDesktopClassicViewportLock();
  syncMobileModeFromSelection();

  // expose for other flows if needed
  root.__dsResolveMobileAction = resolveMobileAction;
  root.__dsInsertEmoji = insertEmojiText;
  root.__dsInsertIcon = insertIconSvg;
  root.__dsCloseMobileSheets = closeSheets;
  root.__dsOpenMobileSheet = openSheet;
  root.__dsSyncMobileObjectMode = syncMobileModeFromSelection;
  root.__dsMobileBridgeReady = true;
}

  function isMobilePortraitUi() {
    try {
      const byFlags = !!(root &&
        root.getAttribute("data-vp-mobile") === "1" &&
        root.getAttribute("data-vp-orient") === "portrait");
      const byViewport = !!(typeof window !== "undefined" &&
        window.matchMedia &&
        window.matchMedia("(max-width: 1023px)").matches &&
        (window.matchMedia("(orientation: portrait)").matches || (window.innerHeight >= window.innerWidth)));
      return byFlags || byViewport;
    } catch (e) {
      return false;
    }
  }

  function isDesktopClassicUi() {
    try {
      return isClassicDesktopViewport();
    } catch (e) {
      return false;
    }
  }

  function applyDesktopClassicTheme(theme) {
    try {
      const requested = String(theme || "").toLowerCase();
      let next = "light";
      if (requested === "dark" || requested === "light") {
        next = requested;
      } else {
        try {
          const saved = String(window.localStorage.getItem("ds_dc_theme") || "").toLowerCase();
          if (saved === "dark" || saved === "light") next = saved;
        } catch (e0) {}
      }
      root.setAttribute("data-dc-theme", next);
      root.classList.toggle("ds-dc-theme-light", next === "light");
      try { syncDesktopClassicLightButtonStyles(); } catch (e0) {}
      try { window.localStorage.setItem("ds_dc_theme", next); } catch (e) {}
      return next;
    } catch (e) {
      return "light";
    }
  }

  function syncDesktopClassicLightButtonStyles() {
    try {
      if (!isDesktopClassicUi()) return;
      const light = String(root.getAttribute("data-dc-theme") || "").toLowerCase() === "light";
      const cs = (typeof window !== "undefined" && window.getComputedStyle) ? window.getComputedStyle(root) : null;
      const uiBtnBg = (cs && String(cs.getPropertyValue("--ds-button-surface") || "").trim())
        || (cs && String(cs.getPropertyValue("--panel") || "").trim())
        || "#ffffff";
      const uiText = (cs && String(cs.getPropertyValue("--text") || "").trim()) || "#000000";
      const uiLine = (cs && String(cs.getPropertyValue("--line") || "").trim()) || "rgba(0,0,0,0.24)";
      const btns = Array.from(root.querySelectorAll("button, [data-mobile-sheet-action], .ds-upload"));
      btns.forEach((el) => {
        if (!el || !el.style) return;
        const inNoTemplateStart = !!(el.closest && el.closest(".ds-no-template-start"));
        if (inNoTemplateStart) return;
        const inExitConfirmModal = !!(el.closest && el.closest(".ds-exit-confirm-modal"));
        if (inExitConfirmModal) return;
        const role = String((el.getAttribute && el.getAttribute("data-role")) || "");
        if (
          role === "no-template-next" ||
          role === "no-template-create-new" ||
          role === "no-template-open-load" ||
          role === "no-template-load-design" ||
          role === "no-template-design-use" ||
          role === "no-template-design-load" ||
          role === "startup-hard-refresh" ||
          role === "no-template-back-step-1" ||
          role === "no-template-back-step-2" ||
          role === "startup-back-link"
        ) return;
        const action = String((el.getAttribute && el.getAttribute("data-action")) || "");
        if (
          action === "exit-save" ||
          action === "exit-apply" ||
          action === "exit-leave" ||
          action === "exit-cancel"
        ) return;
        const inAssetLibrary = !!(el.closest && el.closest('[data-role="asset-library-modal"]'));
        const isGlyphAssetTile = !!(el.classList && (el.classList.contains("ds-asset-card-glyph") || el.classList.contains("ds-mobile-icon-tile")));
        if (inAssetLibrary && isGlyphAssetTile) return;
        const isApply = el.classList.contains("ds-dc-apply-btn")
          || String(el.getAttribute("data-mobile-sheet-action") || "") === "apply"
          || String(el.getAttribute("data-mobile-action") || "") === "apply"
          || String(el.getAttribute("data-action") || "") === "apply-product"
          || String(el.getAttribute("data-action") || "") === "post-save-apply";
        const isSaveDesign = String(el.getAttribute("data-mobile-sheet-action") || "") === "final-save"
          || String(el.getAttribute("data-mobile-action") || "") === "final-save"
          || String(el.getAttribute("data-action") || "") === "final-save";
        if (isApply || isSaveDesign) return;
        if (light) {
          el.style.setProperty("background", uiBtnBg, "important");
          el.style.setProperty("background-color", uiBtnBg, "important");
          el.style.setProperty("background-image", "none", "important");
          el.style.setProperty("color", uiText, "important");
          el.style.setProperty("border-color", uiLine, "important");
          el.style.setProperty("text-shadow", "none", "important");
        } else {
          el.style.removeProperty("background");
          el.style.removeProperty("background-color");
          el.style.removeProperty("background-image");
          el.style.removeProperty("color");
          el.style.removeProperty("border-color");
          el.style.removeProperty("text-shadow");
        }
      });
      const icons = Array.from(root.querySelectorAll("button .ds-ui-icon, button .ds-ui-icon svg, .dashicons"));
      icons.forEach((el) => {
        if (!el || !el.style) return;
        if (light) {
          el.style.setProperty("color", uiText, "important");
          el.style.setProperty("stroke", uiText, "important");
        } else {
          el.style.removeProperty("color");
          el.style.removeProperty("stroke");
        }
      });
    } catch (e) {}
  }

  function initDesktopClassicTheme() {
    try {
      if (!isDesktopClassicUi()) return;
      const applied = applyDesktopClassicTheme("light");
      root.setAttribute("data-dc-theme", applied);
    } catch (e) {}
  }

  function ensureDesktopClassicCanvasQuickActions() {
    try {
      if (!isDesktopClassicUi()) return;
      const canvasSheet = sheetsWrap ? sheetsWrap.querySelector('[data-mobile-sheet="canvas"]') : null;
      if (!canvasSheet) return;
      const grid = canvasSheet.querySelector(".ds-mobile-sheet-grid");
      if (!grid) return;
      if (grid.querySelector('[data-role="dc-canvas-quick-actions"]')) return;

      const wrap = document.createElement("div");
      wrap.className = "ds-mobile-sheet-section ds-dc-quick-actions";
      wrap.setAttribute("data-role", "dc-canvas-quick-actions");
      wrap.innerHTML =
        '<h5>' + escHtml(tUi("quick_actions", "Quick Actions")) + '</h5>' +
        '<div class="ds-dc-quick-actions-grid">' +
          '<button type="button" data-mobile-sheet-action="open-arrange">' + escHtml(tUi("arrange", "Arrange")) + '</button>' +
          '<button type="button" data-mobile-sheet-action="final-save">' + escHtml(tUi("save_design", "Save Design")) + '</button>' +
        '</div>';
      grid.appendChild(wrap);
      try { applyUnifiedUiIcons(wrap); } catch (e) {}
    } catch (e) {}
  }

  function ensureDesktopClassicUtilityPanel() {
    try {
      if (isLandscapeMobileViewport()) {
        const existingTopbar = root.querySelector('[data-role="dc-topbar"]');
        if (existingTopbar && existingTopbar.parentNode) {
          existingTopbar.parentNode.removeChild(existingTopbar);
        }
        return;
      }
      if (!isDesktopClassicUi()) return;
      const host = root.querySelector(".ds-workspace");
      if (!host) return;

      let topbar = root.querySelector('[data-role="dc-topbar"]');
      if (!topbar) {
        topbar = document.createElement("div");
        topbar.className = "ds-dc-topbar";
        topbar.setAttribute("data-role", "dc-topbar");
        root.insertBefore(topbar, host);
      }

      let panel = topbar.querySelector('[data-role="dc-utility-panel"]');
      if (!panel) {
        panel = document.createElement("div");
        panel.className = "ds-dc-utility-panel";
        panel.setAttribute("data-role", "dc-utility-panel");
        panel.innerHTML =
          '<div class="ds-dc-topbar-row">' +
            '<button type="button" data-mobile-sheet-action="exit-flow">' + escHtml(tUi("back", "Back")) + '</button>' +
            '<button type="button" data-mobile-sheet-action="undo">' + escHtml(tUi("undo", "Undo")) + '</button>' +
            '<button type="button" data-mobile-sheet-action="hard-refresh">' + escHtml(tUi("refresh", "Refresh")) + '</button>' +
            '<button type="button" data-mobile-sheet-action="redo">' + escHtml(tUi("redo", "Redo")) + '</button>' +
            '<div class="ds-dc-zone-inline">' +
              '<button type="button" class="ds-dc-zone-preview-btn" data-mobile-sheet-action="open-zones" title="' + escAttr(tUi("zone_tools", "Zone Tools")) + '" aria-label="' + escAttr(tUi("zone_tools", "Zone Tools")) + '">' +
                '<img data-role="dc-active-zone-icon" alt="' + escAttr(tUi("active_mockup", "Active mockup")) + '" loading="lazy" />' +
                '<span class="ds-dc-zone-preview-text" aria-hidden="true">' + escHtml(tUi("preview", "Preview")) + '</span>' +
              '</button>' +
              '<span class="ds-dc-zone-label">' + escHtml(tUi("active_canvas", "Active Canvas")) + '</span>' +
              '<select data-role="dc-active-zone-select"><option value="">' + escHtml(tUi("select", "Select")) + '</option></select>' +
            '</div>' +
            '<button type="button" class="ds-dc-apply-btn" data-mobile-sheet-action="apply">' + escHtml(tUi("apply_to_product", "Apply to Product")) + '</button>' +
          '</div>';
        topbar.appendChild(panel);
      }

      // Remove old left utility container if it exists from previous versions.
      const oldInLeft = root.querySelector('.ds-desktop-sheet-host [data-role="dc-utility-panel"]');
      if (oldInLeft && oldInLeft !== panel && oldInLeft.parentNode) {
        oldInLeft.parentNode.removeChild(oldInLeft);
      }

      const sourceSelect =
        root.querySelector('[data-role="active-zone-select"]')
        || root.querySelector('.ds-zoneblock [data-role="active-zone-select"]')
        || root.querySelector('.ds-zoneblock select');
      const dcSelect = panel.querySelector('[data-role="dc-active-zone-select"]');
      const dcIcon = panel.querySelector('[data-role="dc-active-zone-icon"]');
      const previewBtn = panel.querySelector('.ds-dc-zone-preview-btn');
      const previewText = panel.querySelector('.ds-dc-zone-preview-text');
      const dcLabel = panel.querySelector(".ds-dc-zone-label");
      const dcZoneInline = panel.querySelector(".ds-dc-zone-inline");
      if (dcSelect) {
        const syncFromSource = () => {
          const is3DTopPreview = !!enablePreview3D;
          if (dcZoneInline && dcZoneInline.classList) dcZoneInline.classList.toggle("ds-3d-preview-only", is3DTopPreview);
          if (dcLabel) dcLabel.style.display = is3DTopPreview ? "none" : "";
          dcSelect.style.display = is3DTopPreview ? "none" : "";
          dcSelect.disabled = !!is3DTopPreview;
          const srcOptions = sourceSelect ? Array.from(sourceSelect.options || []) : [];
          const current = String((sourceSelect && sourceSelect.value) || "");
          const prev = String(dcSelect.value || "");
          if (srcOptions.length) {
            dcSelect.innerHTML = srcOptions.map((opt) =>
              '<option value="' + escAttr(String(opt.value || "")) + '">' + escHtml(String(opt.textContent || opt.value || "")) + "</option>"
            ).join("");
            dcSelect.value = current;
            if (dcSelect.value !== current && prev) dcSelect.value = prev;
          } else if (zoneState && zoneState.enabled) {
            const all = Array.isArray(zoneState.zones) ? zoneState.zones : [];
            const selectedIds = getSelectedZoneIds();
            const enabledIds = selectedIds.length ? selectedIds : all.map((z) => String(z.id || "")).filter(Boolean);
            dcSelect.innerHTML = enabledIds.map((id) => {
              const z = all.find((row) => String(row.id || "") === id);
              return '<option value="' + escAttr(id) + '">' + escHtml(String((z && z.label) || id)) + "</option>";
            }).join("");
            const zCurrent = String(zoneState.currentId || "");
            if (zCurrent && enabledIds.includes(zCurrent)) dcSelect.value = zCurrent;
            else if (enabledIds.length) dcSelect.value = enabledIds[0];
            else dcSelect.value = "";
          }
          const iconZoneId = String(dcSelect.value || current || "");
          const mock = pickZoneMockup(iconZoneId);
          const iconSrc = String((mock && mock.image_url) || "");
          if (previewBtn) {
            previewBtn.setAttribute("data-mobile-sheet-action", is3DTopPreview ? "mug-open" : "open-zones");
            previewBtn.setAttribute("title", is3DTopPreview ? tUi("preview_3d", "3D Preview") : tUi("zone_tools", "Zone Tools"));
            previewBtn.setAttribute("aria-label", is3DTopPreview ? tUi("preview_3d", "3D Preview") : tUi("zone_tools", "Zone Tools"));
            if (previewBtn.classList) previewBtn.classList.toggle("ds-is-3d-preview", is3DTopPreview);
          }
          if (previewText) {
            previewText.style.display = is3DTopPreview ? "inline-block" : "none";
            previewText.textContent = is3DTopPreview ? tUi("preview_3d", "3D Preview") : tUi("preview", "Preview");
          }
          if (dcIcon) {
            if (is3DTopPreview) {
              dcIcon.removeAttribute("src");
              dcIcon.style.display = "none";
              dcIcon.setAttribute("alt", tUi("preview_3d", "3D Preview"));
            } else if (iconSrc) {
              dcIcon.src = iconSrc;
              dcIcon.style.display = "block";
              dcIcon.setAttribute("alt", String((mock && mock.name) || tUi("active_mockup", "Active mockup")));
            } else {
              dcIcon.removeAttribute("src");
              dcIcon.style.display = "none";
            }
          }
        };
        syncFromSource();
        if (sourceSelect && !sourceSelect.__dcSyncBound) {
          sourceSelect.addEventListener("change", syncFromSource);
          sourceSelect.__dcSyncBound = true;
        }
        if (!dcSelect.__dcSyncBound) {
          dcSelect.addEventListener("change", () => {
            const zid = String(dcSelect.value || "");
            if (!zid) return;
            try { switchToZone(zid); } catch (e0) {}
            try { renderActiveZoneSelector(); } catch (e01) {}
            try { updateZoneToolbarUi(); } catch (e02) {}
            try { syncFromSource(); } catch (e03) {}
            if (sourceSelect) {
              sourceSelect.value = zid;
              try { sourceSelect.dispatchEvent(new Event("input", { bubbles: true })); } catch (e1) {}
              try { sourceSelect.dispatchEvent(new Event("change", { bubbles: true })); } catch (e2) {}
            }
          });
          dcSelect.__dcSyncBound = true;
        }
        if (!dcSelect.__dcResyncBound) {
          const resync = () => { try { syncFromSource(); } catch (e) {} };
          try { canvas.on("selection:created", resync); } catch (e1) {}
          try { canvas.on("selection:updated", resync); } catch (e2) {}
          try { canvas.on("selection:cleared", resync); } catch (e3) {}
          dcSelect.__dcResyncBound = true;
        }
      }

      if (previewBtn && !previewBtn.__dcPreviewBound) {
        previewBtn.addEventListener("click", (e) => {
          e.preventDefault();
          e.stopPropagation();
          if (enablePreview3D) {
            try {
              if (typeof openPreviewModal === "function") {
                openPreviewModal();
                return;
              }
            } catch (e3) {}
            try { resolveMobileAction("mug-open"); } catch (e4) {}
            return;
          }
          if (typeof openZonePickerModal === "function") {
            try { openZonePickerModal(); return; } catch (err) {}
          }
          try { resolveMobileAction("open-zones"); } catch (e2) {}
        });
        previewBtn.__dcPreviewBound = true;
      }

      if (!panel.__dcActionsBound) {
        panel.addEventListener("click", (e) => {
          const btn = e.target && e.target.closest ? e.target.closest("[data-mobile-sheet-action]") : null;
          if (!btn) return;
          const action = String(btn.getAttribute("data-mobile-sheet-action") || "");
          if (!action) return;
          if (action === "open-zones") return;
          e.preventDefault();
          try { resolveMobileAction(action); } catch (err) {}
        });
        panel.__dcActionsBound = true;
      }
      try { applyUnifiedUiIcons(panel); } catch (e) {}
    } catch (e) {}
  }

  function syncDesktopClassicViewportLock() {
    try {
      const variant = String(root.getAttribute("data-ds-ui-variant") || "").toLowerCase();
      const active = isDesktopClassicUi() && variant === "classic";
      const html = document.documentElement;
      const body = document.body;
      const vh = Math.max(
        1,
        Math.round((window.visualViewport && window.visualViewport.height) || window.innerHeight || 0)
      );
      if (active) {
        initDesktopClassicTheme();
        ensureDesktopClassicCanvasQuickActions();
        ensureDesktopClassicUtilityPanel();
        syncDesktopClassicLightButtonStyles();
        root.style.setProperty("--ds-dc-vh", vh + "px");
        if (html) html.classList.add("ds-dc-lock-scroll");
        if (body) body.classList.add("ds-dc-lock-scroll");
      } else {
        const topbar = root.querySelector('[data-role="dc-topbar"]');
        if (topbar && topbar.parentNode) {
          topbar.parentNode.removeChild(topbar);
        }
        root.style.removeProperty("--ds-dc-vh");
        if (html) html.classList.remove("ds-dc-lock-scroll");
        if (body) body.classList.remove("ds-dc-lock-scroll");
      }
    } catch (e) {}
  }

  function hardStripLandscapeExitUi() {
    try {
      if (!isLandscapeMobileViewport()) return;
      const selectors = [
        '[data-role="dc-topbar"]',
        '.ds-topbar',
        '.ds-topbar-back',
        '.ds-topbar-exit'
      ];
      selectors.forEach((sel) => {
        const nodes = Array.from(root.querySelectorAll(sel));
        nodes.forEach((node) => {
          try {
            if (node && node.parentNode) node.parentNode.removeChild(node);
          } catch (e) {}
        });
      });
    } catch (e) {}
  }

  function ensureLandscapeAlwaysFullscreen() {
    try {
      if (!isLandscapeMobileViewport()) return;
      if (root.classList.contains("ds-fullscreen")) return;
      root.classList.add("ds-fullscreen");
      try { window.sessionStorage.setItem("ds_editor_fullscreen", "1"); } catch (e0) {}
      if (document.fullscreenElement) return;
      if (document.fullscreenEnabled && root.requestFullscreen) {
        root.requestFullscreen().catch(function () {});
      }
      // Some mobile browsers require explicit user gesture to enter fullscreen.
      // Bind one-time nudges in landscape so first tap retries fullscreen request.
      if (!root.__dsLandscapeFsNudgeBound) {
        const nudge = function () {
          try {
            if (!isLandscapeMobileViewport()) return;
            if (document.fullscreenElement || !document.fullscreenEnabled || !root.requestFullscreen) return;
            root.requestFullscreen().catch(function () {});
          } catch (e1) {}
        };
        root.__dsLandscapeFsNudgeBound = true;
        document.addEventListener("pointerdown", nudge, { passive: true });
        document.addEventListener("touchstart", nudge, { passive: true });
        document.addEventListener("click", nudge, { passive: true });
      }
    } catch (e) {}
  }

  function ensurePortraitNeverFullscreen() {
    try {
      if (!isMobileViewport() || isLandscapeMobileViewport()) return;
      root.classList.remove("ds-fullscreen");
      try { window.sessionStorage.removeItem("ds_editor_fullscreen"); } catch (e0) {}
      if (document.fullscreenElement && document.exitFullscreen) {
        document.exitFullscreen().catch(function () {});
      }
    } catch (e) {}
  }

  function ensureMobileEmptyStateOverlay() {
    const host = root.querySelector(".ds-center-stage") || root.querySelector(".ds-canvas-shell");
    if (!host) return null;
    let overlay = host.querySelector('[data-role="mobile-empty-state"]');
    if (overlay) return overlay;
    overlay = document.createElement("div");
    overlay.className = "ds-mobile-empty-state";
    overlay.setAttribute("data-role", "mobile-empty-state");
    overlay.setAttribute("data-disabled", "0");
    overlay.setAttribute("aria-hidden", "true");
    overlay.innerHTML =
      '<div class="ds-mobile-empty-card">' +
      '<h4>' + escHtml(tUi("start_designing", "Start Designing")) + '</h4>' +
      '<p>' + escHtml(tUi("add_first_element_to_canvas", "Add your first element to the canvas.")) + '</p>' +
      '<div class="ds-mobile-empty-actions">' +
      '<button type="button" data-mobile-empty-action="upload-image"><span class="ds-mobile-empty-btn-ico" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M4 6a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6zm2 0v9.4l3.2-3.2a1 1 0 0 1 1.4 0l2.4 2.4 2.2-2.2a1 1 0 0 1 1.4 0L18 14V6H6zm3.5 2.2a1.3 1.3 0 1 0 0 2.6 1.3 1.3 0 0 0 0-2.6z"/></svg></span><span>' + escHtml(tUi("add_image", "Add Image")) + '</span></button>' +
      '<button type="button" data-mobile-empty-action="add-text"><span class="ds-mobile-empty-btn-ico" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M4 6V4h16v2h-7v14h-2V6H4z"/></svg></span><span>' + escHtml(tUi("add_text", "Add Text")) + '</span></button>' +
      '</div>' +
      '<p class="ds-mobile-empty-help">' + escHtml(tUi("for_more_visual_elements_go_to_insert_menu", "For more visual elements, go to Insert menu.")) + '</p>' +
      '</div>';
    overlay.addEventListener("click", (e) => {
      const btn = e.target && e.target.closest ? e.target.closest("[data-mobile-empty-action]") : null;
      if (!btn) return;
      const action = String(btn.getAttribute("data-mobile-empty-action") || "");
      if (!action) return;
      if (action === "upload-image") {
        if (guardZoneSelectionLock("upload-image")) return;
        const target = root.querySelector('input[data-action="upload-image"]');
        if (target && typeof target.click === "function") target.click();
      } else if (action === "add-text") {
        if (guardZoneSelectionLock("add-text")) return;
        const addTextBtn = root.querySelector('.ds-toolbar [data-action="add-text"]');
        if (addTextBtn && typeof addTextBtn.click === "function") {
          addTextBtn.click();
        } else if (typeof root.__dsResolveMobileAction === "function") {
          root.__dsResolveMobileAction("add-text");
        }
      } else if (action === "browse-art") {
        if (typeof root.__dsOpenMobileSheet === "function" && (isMobilePortraitUi() || isDesktopClassicUi())) {
          root.__dsOpenMobileSheet("art");
          return;
        }
        const target = root.querySelector('.ds-toolbar [data-action="open-textures"]')
          || root.querySelector('.ds-toolbar [data-action="open-frames"]');
        if (target && typeof target.click === "function") target.click();
      }
    });
    host.appendChild(overlay);
    return overlay;
  }

  function syncMobileEmptyStateOverlay() {
    const overlay = ensureMobileEmptyStateOverlay();
    if (!overlay) return;
    const hasOpenSheet = root.classList.contains("ds-mobile-sheet-open");
    const allowDesktopEmpty = isDesktopClassicUi() && !isMobileViewport();
    const hasTemplate = !root.classList.contains("ds-no-template");
    const hasActiveCanvas = !root.classList.contains("ds-no-active-canvas");
    const allowLandscapeEmpty = isLandscapeMobileViewport();
    const show = hasTemplate
      && hasActiveCanvas
      && (isMobilePortraitUi() || allowLandscapeEmpty || allowDesktopEmpty)
      && !hasOpenSheet
      && getDesignObjects().length === 0;
    overlay.classList.toggle("is-visible", show);
    overlay.setAttribute("aria-hidden", show ? "false" : "true");
    root.classList.toggle("ds-mobile-empty", show);
  }

  function loadDesignJSON(data, done, expectedZoneId) {
    restoring = true;
    canvas.clear();
    canvas.backgroundColor = "#fff";
    canvas.loadFromJSON(data, () => {
      getDesignObjects().forEach((o) => ensureUid(o));
      getDesignObjects().forEach((o) => tuneObjectForTouch(o));
      redrawSystem();
      fitCanvas(false);
      canvas.discardActiveObject();
      canvas.requestRenderAll();
      updateLayerList();
      updateProps();
      syncMobileEmptyStateOverlay();
      const finalizeLoadedZone = () => {
        if (zoneState && zoneState.enabled) {
          const loadedZoneId = String(expectedZoneId || zoneState.currentId || "");
          if (loadedZoneId) {
            zoneState.snapshots[loadedZoneId] = designJSON();
            copyMainCanvasToZone(loadedZoneId);
          }
        }
        restoring = false;
        if (typeof done === "function") done();
      };
      if (typeof window !== "undefined" && typeof window.requestAnimationFrame === "function") {
        window.requestAnimationFrame(() => {
          window.requestAnimationFrame(finalizeLoadedZone);
        });
      } else {
        window.setTimeout(finalizeLoadedZone, 0);
      }
    });
  }

  function draftStorageKey() {
    return "ds_local_draft_v1_" + String(cfg.id || 0);
  }

  function getCurrentDesignRef() {
    const id = designIdInput ? parseInt(String(designIdInput.value || "0"), 10) : 0;
    return id > 0 ? ("design:" + String(id)) : String(unsavedDesignRef || "new");
  }

  function clearLocalDraft() {
    try { window.localStorage.removeItem(draftStorageKey()); } catch (e) {}
  }

  function loadLocalDraft() {
    try {
      const raw = window.localStorage.getItem(draftStorageKey());
      if (!raw) return null;
      const parsed = JSON.parse(raw);
      if (!parsed || typeof parsed !== "object") return null;
      if (Number(parsed.templateId || 0) !== Number(cfg.id || 0)) return null;
      return parsed;
    } catch (e) { return null; }
  }

  function persistLocalDraft(draft) {
    try {
      window.localStorage.setItem(draftStorageKey(), JSON.stringify(draft || {}));
      return true;
    } catch (e) { return false; }
  }

  // New editor session (no explicit ds_design) must not inherit stale local draft.
  // This prevents cross-design zone mixing when user starts a brand new design.
  if (Number(initialDesignId || 0) <= 0) {
    clearLocalDraft();
  }

  function collectAllZoneIds() {
    if (zoneState && Array.isArray(zoneState.zones) && zoneState.zones.length) {
      return zoneState.zones.map((z) => String(z.id || "")).filter(Boolean);
    }
    return ["front_full"];
  }

  function saveCurrentZoneToLocalDraft() {
    if (!zoneState || !zoneState.enabled) {
      setStatus("Local draft is used only for multi-zone templates.");
      return false;
    }
    captureCurrentZone();
    const zoneId = String(zoneState.currentId || "");
    if (!zoneId) {
      setStatus("No active zone.");
      return false;
    }
    return saveZoneToLocalDraftById(zoneId, true);
  }

  function saveZoneToLocalDraftById(zoneId, withStatus) {
    const zid = String(zoneId || "");
    if (!zoneState || !zoneState.enabled || !zid) return false;
    const selectedIds = getSelectedZoneIds();
    const allZoneIds = collectAllZoneIds();
    const draft = loadLocalDraft() || {
      templateId: Number(cfg.id || 0),
      zones: {},
      savedZoneIds: [],
      selectedIds: [],
      allZoneIds: [],
      activeZone: "",
      updatedAt: 0
    };
    const snap = zoneState.snapshots[zid];
    draft.zones[zid] = {
      snapshot: (snap && Array.isArray(snap.objects)) ? snap : { version: fabric.version, objects: [] },
      orientation: getCurrentZoneOrientation(zid),
      pngData: (function () {
        try { return withSystemGuidesHidden(() => canvas.toDataURL({ format: "png", multiplier: 1 })); } catch (e) { return ""; }
      })()
    };
    const savedIds = Array.isArray(draft.savedZoneIds) ? draft.savedZoneIds.map((z) => String(z || "")) : [];
    if (!savedIds.includes(zid)) savedIds.push(zid);
    draft.savedZoneIds = savedIds;
    draft.selectedIds = selectedIds.slice();
    draft.allZoneIds = allZoneIds.slice();
    draft.activeZone = zid;
    draft.designRef = getCurrentDesignRef();
    draft.updatedAt = Date.now();
    if (!persistLocalDraft(draft)) {
      setStatus("Local draft save failed.");
      return false;
    }
    if (withStatus) setStatus(setZoneStatusPrefix() + tUi("zone_saved_locally", "Zone saved locally."));
    return true;
  }

  let autoZoneDraftTimer = null;
  function scheduleAutoZoneDraftSave() {
    if (!zoneState || !zoneState.enabled || !zoneState.currentId) return;
    if (restoring) return;
    try { if (autoZoneDraftTimer) window.clearTimeout(autoZoneDraftTimer); } catch (e) {}
    autoZoneDraftTimer = window.setTimeout(() => {
      autoZoneDraftTimer = null;
      try {
        captureCurrentZone();
        saveZoneToLocalDraftById(zoneState.currentId, false);
      } catch (e2) {}
    }, 260);
  }

  function applyLocalDraftToState(draft) {
    if (!draft || !zoneState || !zoneState.enabled) return false;
    const all = collectAllZoneIds();
    const zmap = draft.zones && typeof draft.zones === "object" ? draft.zones : {};
    all.forEach((zid) => {
      const row = zmap[zid];
      if (row && row.snapshot && Array.isArray(row.snapshot.objects)) {
        zoneState.snapshots[zid] = row.snapshot;
      }
      if (row && (row.orientation === "portrait" || row.orientation === "landscape")) {
        zoneState.orientations[zid] = row.orientation;
      }
    });
    const selected = Array.isArray(draft.selectedIds) ? draft.selectedIds.map((z) => String(z || "")) : [];
    const validSelected = selected.filter((z) => all.includes(z));
    if (validSelected.length) {
      zoneState.selectedIds = validSelected.slice();
      zoneState.userSelectionReady = true;
    }
    const active = String(draft.activeZone || "");
    if (active && all.includes(active)) zoneState.currentId = active;
    return true;
  }

  function buildZonedPayloadFromLocalDraft() {
    const base = buildPersistedLayersPayload();
    if (!zoneState || !zoneState.enabled || !base || base.type !== "zoned") {
      return { layersPayload: base, usedZones: collectUsedZonesForSave(), allZones: collectAllZoneIds() };
    }
    const draft = loadLocalDraft();
    const currentDesignRef = getCurrentDesignRef();
    const draftDesignRef = draft ? String(draft.designRef || "new") : "";
    const draftMatchesCurrent = !!(draft && draftDesignRef === currentDesignRef);
    if (draftMatchesCurrent) applyLocalDraftToState(draft);
    const allZones = collectAllZoneIds();
    const selected = Array.isArray(zoneState.selectedIds) ? zoneState.selectedIds.map((z) => String(z || "")) : [];
    const savedZoneIds = (draftMatchesCurrent && Array.isArray(draft.savedZoneIds))
      ? draft.savedZoneIds.map((z) => String(z || "")).filter(Boolean)
      : [];
    // Mobile-safe rule:
    // Selected zones are authoritative. Saved-zone history is additive, not overriding selection.
    const usedBySaved = savedZoneIds.filter((z) => allZones.includes(z));
    const usedBySelection = selected.filter((z) => allZones.includes(z));
    const usedZonesMap = {};
    usedBySelection.forEach((z) => { usedZonesMap[z] = z; });
    usedBySaved.forEach((z) => { usedZonesMap[z] = z; });
    const usedZones = Object.keys(usedZonesMap);
    // Live zone state is authoritative for final save.
    // Local draft is only a fallback for missing zones/snapshots.
    let layersPayload = base;
    const draftZonePngMap = {};

    // Merge missing zone snapshots from draft only when live state is empty.
    if (draftMatchesCurrent && draft.zones && typeof draft.zones === "object") {
      const zPayload = (layersPayload && layersPayload.zones && typeof layersPayload.zones === "object")
        ? layersPayload.zones
        : {};
      const oPayload = (layersPayload && layersPayload.orientations && typeof layersPayload.orientations === "object")
        ? layersPayload.orientations
        : {};
      allZones.forEach((zid) => {
        const row = draft.zones[zid];
        const liveZone = zPayload[zid];
        const liveHasObjects = !!(liveZone && Array.isArray(liveZone.objects) && liveZone.objects.length > 0);
        if (!liveHasObjects && row && row.snapshot && Array.isArray(row.snapshot.objects)) {
          zPayload[zid] = row.snapshot;
        }
        if (!oPayload[zid] && row && (row.orientation === "landscape" || row.orientation === "portrait")) {
          oPayload[zid] = row.orientation;
        }
        if (row && typeof row.pngData === "string" && row.pngData.indexOf("data:image/png;base64,") === 0) {
          draftZonePngMap[zid] = row.pngData;
        }
      });
      layersPayload = {
        type: "zoned",
        active_zone: String((zoneState.currentId || (draft && draft.activeZone) || allZones[0] || "front_full")),
        zones: zPayload,
        orientations: oPayload,
        placements: (previewType === "tshirt") ? {} : (zoneState.placements || {})
      };
    } else if (layersPayload && layersPayload.type === "zoned") {
      layersPayload.active_zone = String(zoneState.currentId || layersPayload.active_zone || allZones[0] || "front_full");
    }

    const hasZoneContent = (zid) => {
      const z = layersPayload && layersPayload.zones ? layersPayload.zones[zid] : null;
      return !!(z && Array.isArray(z.objects) && z.objects.length > 0);
    };
    const selectedSet = {};
    usedBySelection.forEach((zid) => { selectedSet[zid] = true; });
    const selectedPlusContent = (usedZones.length ? usedZones : collectUsedZonesForSave()).filter((zid) => {
      // Never drop explicitly selected zones; content-check is only for non-selected carry-over.
      if (selectedSet[zid]) return true;
      return hasZoneContent(zid);
    });
    // Safety fallback: if everything is empty, keep current behavior with at least current zone.
    const finalUsed = selectedPlusContent.length ? selectedPlusContent : ((zoneState && zoneState.currentId) ? [String(zoneState.currentId)] : []);
    return { layersPayload: layersPayload, usedZones: finalUsed, allZones: allZones, zonePngMap: draftZonePngMap };
  }

  function renderSnapshotToPngData(snapshot, zoneId) {
    return new Promise((resolve) => {
      try {
        const mm = getZoneCanvasMm(zoneId);
        const w = Math.max(8, mmToPx(Number(mm.w || cfg.width_mm || 200), previewDpi));
        const h = Math.max(8, mmToPx(Number(mm.h || cfg.height_mm || 100), previewDpi));
        const tmpEl = document.createElement("canvas");
        tmpEl.width = w;
        tmpEl.height = h;
        const tmp = new fabric.StaticCanvas(tmpEl, { backgroundColor: "#fff" });
        const safe = (snapshot && Array.isArray(snapshot.objects))
          ? snapshot
          : { version: fabric.version, objects: [] };
        tmp.loadFromJSON(safe, () => {
          try {
            tmp.renderAll();
            resolve(tmp.toDataURL({ format: "png", multiplier: 1 }));
          } catch (e) {
            resolve("");
          }
          try { tmp.dispose(); } catch (e2) {}
        });
      } catch (e) {
        resolve("");
      }
    });
  }

  function collectZonePngDataFromSnapshots(layersPayload, usedZones) {
    const out = {};
    if (!layersPayload || layersPayload.type !== "zoned" || !layersPayload.zones) {
      return Promise.resolve(out);
    }
    const ids = Array.isArray(usedZones) ? usedZones.map((z) => String(z || "")).filter(Boolean) : [];
    let chain = Promise.resolve();
    ids.forEach((zid) => {
      chain = chain.then(() => renderSnapshotToPngData(layersPayload.zones[zid], zid).then((png) => {
        if (png && png.indexOf("data:image/png;base64,") === 0) out[zid] = png;
      }));
    });
    return chain.then(() => out);
  }

  function collectMockupPreviewPngData(zonePngData, usedZoneIds) {
    const out = [];
    // Keep signature for compatibility with current save pipeline.
    void usedZoneIds;
    const normalizeId = (v) => String(v || "").trim();
    const legacyId = (v) => normalizeId(v).toLowerCase().replace(/[^a-z0-9_-]/g, "");
    const resolveZonePngForArea = (area) => {
      const explicit = normalizeId(area && (area.zone_id || area.zoneId));
      const aid = normalizeId(area && area.id);
      const ids = [];
      // Deterministic mapping:
      // Prefer explicit zone_id. Use area.id only when zone_id is missing.
      if (explicit) ids.push(explicit);
      else if (aid) ids.push(aid);
      if (!ids.length) return null;
      for (let i = 0; i < ids.length; i++) {
        const zid = ids[i];
        const direct = String((zonePngData && zonePngData[zid]) || "");
        if (direct.indexOf("data:image/png;base64,") === 0) {
          return { zid: zid, png: direct };
        }
        const lz = legacyId(zid);
        if (!lz || !zonePngData || typeof zonePngData !== "object") continue;
        const hits = Object.keys(zonePngData).filter((k) => legacyId(k) === lz);
        if (hits.length !== 1) continue;
        const matchedKey = hits[0];
        const matchedPng = String(zonePngData[matchedKey] || "");
        if (matchedPng.indexOf("data:image/png;base64,") !== 0) continue;
        return { zid: zid, png: matchedPng };
      }
      return null;
    };
    const mockups = getTemplateMockups().filter((m) => !!(m && m.image_url));
    if (!mockups.length || !zonePngData || typeof zonePngData !== "object") {
      return Promise.resolve(out);
    }

    function loadImage(src) {
      return new Promise((resolve) => {
        const s = String(src || "");
        if (!s) { resolve(null); return; }
        const im = new Image();
        im.onload = function() { resolve(im); };
        im.onerror = function() { resolve(null); };
        im.src = s;
      });
    }

    let chain = Promise.resolve();
    mockups.forEach((mockup, idx) => {
      chain = chain.then(() => {
        const areas = Array.isArray(mockup.areas) ? mockup.areas : [];
        const renderAreas = [];
        const usedAuto = {};
        areas.forEach((a) => {
          const resolved = resolveZonePngForArea(a);
          if (!resolved || !resolved.zid || !resolved.png) return;
          const zid = String(resolved.zid || "");
          if (!zid || usedAuto[zid]) return;
          usedAuto[zid] = true;
          renderAreas.push({
            zid: zid,
            png: String(resolved.png || ""),
            x: Math.max(0, Math.min(1, Number((a && a.x_pct) || 0))),
            y: Math.max(0, Math.min(1, Number((a && a.y_pct) || 0))),
            w: Math.max(0.01, Math.min(1, Number((a && a.w_pct) || 0.25))),
            h: Math.max(0.01, Math.min(1, Number((a && a.h_pct) || 0.25)))
          });
        });
        if (!renderAreas.length) return Promise.resolve();

        return loadImage(mockup.image_url).then((baseImg) => {
          if (!baseImg) return;
          const bw = Math.max(1, Number(baseImg.naturalWidth || baseImg.width || 1));
          const bh = Math.max(1, Number(baseImg.naturalHeight || baseImg.height || 1));
          const targetW = Math.min(1400, Math.max(900, bw));
          const tw = targetW;
          const th = Math.max(1, Math.round((targetW * bh) / bw));
          const stage = document.createElement("canvas");
          stage.width = tw;
          stage.height = th;
          const sctx = stage.getContext("2d");
          if (!sctx) return;
          sctx.clearRect(0, 0, tw, th);
          sctx.drawImage(baseImg, 0, 0, tw, th);

          let drawChain = Promise.resolve();
          renderAreas.forEach((area) => {
            drawChain = drawChain.then(() => loadImage(area.png).then((ov) => {
              if (!ov) return;
              const rx = Math.round(area.x * tw);
              const ry = Math.round(area.y * th);
              const rw = Math.max(1, Math.round(area.w * tw));
              const rh = Math.max(1, Math.round(area.h * th));
              sctx.drawImage(ov, rx, ry, rw, rh);
            }));
          });

          return drawChain.then(() => {
            try {
              const pngData = stage.toDataURL("image/png");
              if (pngData && pngData.indexOf("data:image/png;base64,") === 0) {
                const baseKey = String((mockup && mockup.side) || "mockup").toLowerCase().replace(/[^a-z0-9_-]/g, "_") || "mockup";
                out.push({
                  key: baseKey + "_" + String(idx + 1),
                  name: String((mockup && mockup.name) || ("Mockup " + (idx + 1))),
                  pngData: pngData
                });
              }
            } catch (e) {}
          });
        });
      });
    });
    return chain.then(() => out);
  }

  function saveDesign(skipEmptyGuard, saveOpts) {
    const saveOptions = (saveOpts && typeof saveOpts === "object") ? saveOpts : {};
    const designNameForSave = normalizeDesignName(saveOptions.designName || "");
    const autoApplyAfterSave = !!saveOptions.autoApplyAfterSave;
    if (!skipEmptyGuard && zoneState && zoneState.enabled && zoneState.currentId) {
      captureCurrentZone();
      const empties = getEmptySelectedZoneIds();
      if (empties.length) {
        openEmptyZonesGuardModal({
          emptyZoneIds: empties,
          onContinue: () => saveDesign(true, saveOptions)
        });
        return;
      }
    }
    if (state.saving) {
      setStatus("Save is already in progress... (wait or retry in a moment)");
      return;
    }
    state.saving = true;
    setDesignLoadBusy(true, tUi("saving_design", "Saving design..."));
    let saveWatchdog = null;
    const finishSaving = () => {
      state.saving = false;
      setDesignLoadBusy(false);
      if (saveWatchdog) {
        try { window.clearTimeout(saveWatchdog); } catch (e) {}
        saveWatchdog = null;
      }
    };
    saveWatchdog = window.setTimeout(() => {
      if (!state.saving) return;
      finishSaving();
      setStatus("Save timeout. Please try again.");
    }, 60000);
    const saveUrl = cfg.rest.base + "/save";
    const saveRequest = (reqPayload) => {
      const controller = (typeof AbortController !== "undefined") ? new AbortController() : null;
      const reqTimeout = window.setTimeout(() => {
        if (controller) {
          try { controller.abort(); } catch (e) {}
        }
      }, 25000);
      return fetch(saveUrl, {
        method: "POST",
        headers: { "Content-Type": "application/json", "X-WP-Nonce": cfg.rest.nonce },
        body: JSON.stringify(reqPayload),
        signal: controller ? controller.signal : undefined
      }).then(async (r) => {
        window.clearTimeout(reqTimeout);
        const res = await r.json().catch(() => ({}));
        if (!r.ok) {
          const msg = res && res.message ? String(res.message) : tUi("save_failed_status_code", "Save failed (%s)", [r.status]);
          const err = new Error(msg);
          err.status = r.status;
          throw err;
        }
        return res || {};
      }).catch((err) => {
        window.clearTimeout(reqTimeout);
        if (err && err.name === "AbortError") {
          const e2 = new Error(tUi("request_timeout", "Request timeout."));
          e2.status = 408;
          throw e2;
        }
        throw err;
      });
    };

    let preparedZonePngData = {};
    let saveLayersPayload = null;
    let saveUsedZones = [];
    let saveAllZones = [];

    const commitSavedDetail = (res, fallbackMode) => {
      if (!res || !res.png) {
        setStatus("Save failed.");
        finishSaving();
        return;
      }
      const savedDetail = {
        id: res.design_uuid || res.id,
        png: res.png || "",
        design_post_id: Number(res.design_post_id || 0),
        design_title: normalizeDesignName((res && res.design_title) || designNameForSave || ""),
        zones_used: Array.isArray(res && res.zones_used) ? res.zones_used.slice() : []
      };
      if (designIdInput && savedDetail.design_post_id > 0) {
        designIdInput.value = String(savedDetail.design_post_id);
      }
      try { window.DS_LAST_SAVED = savedDetail; } catch (e) {}
      try { window.dispatchEvent(new CustomEvent("ds:designSaved", { detail: savedDetail })); } catch (e) {}
      const baseMsg = fallbackMode
        ? tUi("saved_fallback", "Saved (fallback): %s", [res.png])
        : tUi("saved_base", "Saved: %s", [res.png]);
      const baseZonesPng = (res && res.zones_png && typeof res.zones_png === "object") ? Object.keys(res.zones_png) : [];
      if (zoneState && zoneState.enabled && savedDetail.design_post_id > 0 && saveLayersPayload && saveLayersPayload.type === "zoned") {
        setStatus(
          baseMsg + " | " +
          tUi("base_zone_pngs_saving", "Base zone PNGs: %s/%s | Saving zones...", [baseZonesPng.length, saveUsedZones.length])
        );
        saveZonesSequential(savedDetail.design_post_id, saveLayersPayload, saveUsedZones, saveAllZones, preparedZonePngData)
          .then((report) => {
            const ok = Number(report && report.saved || 0);
            const pngOk = Number(report && report.savedPng || 0);
            const total = Number(report && report.total || 0);
            const active = Number(report && report.active || 0);
            const inactive = Number(report && report.inactive || 0);
            const activeZones = (report && Array.isArray(report.activeZones)) ? report.activeZones : [];
            const zonePngUrls = (report && report.zonePngUrls && typeof report.zonePngUrls === "object") ? report.zonePngUrls : {};
            const pngList = activeZones
              .map((zid) => zonePngUrls[zid] || "")
              .filter((u) => !!u);
            const zoneText = activeZones.length ? (" | " + tUi("active_list", "Active: %s", [activeZones.join(", ")])) : "";
            if (pngList.length) {
              setStatus(
                tUi("saved_zone_files", "Saved zone files: %s", [pngList.join(" | ")]) +
                " | " + tUi("saved_files_pngs", "Saved files: %s/%s | PNGs: %s/%s (inactive: %s)", [ok, total, pngOk, active, inactive]) +
                zoneText
              );
            } else {
              setStatus(baseMsg + " | " + tUi("saved_files_pngs", "Saved files: %s/%s | PNGs: %s/%s (inactive: %s)", [ok, total, pngOk, active, inactive]) + zoneText);
            }
            showDesignerToast(tUi("design_saved_successfully", "Design saved successfully."), "success");
            if (autoApplyAfterSave) {
              openApplyProductModal(true);
            } else {
              openPostSaveOptionsModal();
            }
            finishSaving();
          })
          .catch((e) => {
            setStatus(baseMsg + " | " + ((e && e.message) ? e.message : "Zone save failed."));
            finishSaving();
          });
        return;
      }
      showDesignerToast(tUi("design_saved_successfully", "Design saved successfully."), "success");
      if (autoApplyAfterSave) {
        openApplyProductModal(true);
      } else {
        openPostSaveOptionsModal();
      }
      setStatus(baseMsg);
      finishSaving();
    };

    const continueSave = () => {
      const exportDpi = Number(cfg.dpi) || 300;
      const mul = exportDpi / previewDpi;
      const baseMul = (zoneState && zoneState.enabled) ? Math.min(mul, 2) : mul;
      const pngData = withSystemGuidesHidden(() => canvas.toDataURL({ format: "png", multiplier: baseMul }));
      if (zoneState && zoneState.enabled && zoneState.currentId) {
        captureCurrentZone();
        saveZoneToLocalDraftById(zoneState.currentId, false);
      }
      const zonedLocal = buildZonedPayloadFromLocalDraft();
      const layersPayload = zonedLocal.layersPayload;
      const usedZones = zonedLocal.usedZones;
      const allZones = zonedLocal.allZones && zonedLocal.allZones.length ? zonedLocal.allZones : usedZones.slice();
      const draftZonePngMap = (zonedLocal && zonedLocal.zonePngMap && typeof zonedLocal.zonePngMap === "object") ? zonedLocal.zonePngMap : {};
      saveLayersPayload = layersPayload;
      saveUsedZones = usedZones.slice();
      saveAllZones = allZones.slice();

      setStatus(tUi("saving_zone_count", "Saving... (%s zones)", [usedZones.length]));
      setDesignLoadBusy(true, tUi("saving_zones", "Saving zones..."));
      collectZonePngDataFromSnapshots(layersPayload, usedZones).then((zonePngData) => {
      if (zoneState && zoneState.enabled) {
        usedZones.forEach((zid) => {
          if (!zonePngData[zid] && draftZonePngMap[zid]) zonePngData[zid] = draftZonePngMap[zid];
        });
      }
      preparedZonePngData = zonePngData || {};
      setDesignLoadBusy(true, tUi("building_previews", "Building previews..."));
      collectMockupPreviewPngData(preparedZonePngData, usedZones).then((previewPngData) => {
        const payload = {
          templateId: cfg.id,
          pngData: pngData,
          // Base save only; zoned files are persisted sequentially per zone.
          layers: (zoneState && zoneState.enabled) ? null : layersPayload,
          zonesUsed: usedZones,
          allZones: allZones,
          zonePngData: preparedZonePngData,
          previewPngData: Array.isArray(previewPngData) ? previewPngData : []
        };
        if (designNameForSave) payload.designName = designNameForSave;
        setDesignLoadBusy(true, tUi("uploading_design", "Uploading design..."));
        saveRequest(payload).then((res) => {
          commitSavedDetail(res, false);
        }).catch((err) => {
          const status = Number(err && err.status || 0);
          const msg = String(err && err.message || "");
          const retryable = status === 413 || status === 500 || status === 502 || status === 503 || /too large|payload|request entity|invalid json|server/i.test(msg);
          if (!retryable) {
            setStatus(msg || "Save error.");
            finishSaving();
            return;
          }
          const fallbackPayload = {
            templateId: payload.templateId,
            pngData: payload.pngData,
            layers: (zoneState && zoneState.enabled) ? null : payload.layers,
            zonesUsed: payload.zonesUsed,
            allZones: payload.allZones,
            zonePngData: {},
            previewPngData: []
          };
          if (designNameForSave) fallbackPayload.designName = designNameForSave;
          setStatus("Save retry (without mockup previews)...");
          setDesignLoadBusy(true, tUi("retrying_save", "Retrying save..."));
          saveRequest(fallbackPayload).then((res) => {
            commitSavedDetail(res, true);
          }).catch((err2) => {
            setStatus((err2 && err2.message) ? err2.message : "Save error.");
            finishSaving();
          });
        });
      }).catch(() => {
        const payload = {
          templateId: cfg.id,
          pngData: pngData,
          layers: (zoneState && zoneState.enabled) ? null : layersPayload,
          zonesUsed: usedZones,
          allZones: allZones,
          zonePngData: preparedZonePngData,
          previewPngData: []
        };
        if (designNameForSave) payload.designName = designNameForSave;
        setDesignLoadBusy(true, tUi("uploading_design", "Uploading design..."));
        saveRequest(payload).then((res) => {
          commitSavedDetail(res, false);
        }).catch((err) => {
          setStatus((err && err.message) ? err.message : "Save error.");
          finishSaving();
        });
      });
    }).catch((e) => {
      setStatus((e && e.message) ? e.message : "PNG prepare failed.");
      finishSaving();
    });
    };

    const flushBeforeSave = () => new Promise((resolve) => {
      try {
        const active = canvas && canvas.getActiveObject ? canvas.getActiveObject() : null;
        if (active && typeof active.setCoords === "function") active.setCoords();
      } catch (e) {}
      try { canvas.requestRenderAll(); } catch (e2) {}
      const raf = (typeof window !== "undefined" && typeof window.requestAnimationFrame === "function")
        ? window.requestAnimationFrame.bind(window)
        : function (cb) { return window.setTimeout(cb, 16); };
      raf(() => {
        raf(() => {
          resolve();
        });
      });
    });

    setDesignLoadBusy(true, tUi("preparing_save", "Preparing save..."));
    flushBeforeSave().then(continueSave).catch(() => continueSave());
  }

  function loadFromSnapshot(snap) {
    try { loadDesignJSON(JSON.parse(snap), () => { history.last = snap; syncUndoButtons(); }); } catch (e) { restoring = false; }
  }

  if (propsEl) {
    propsEl.querySelectorAll("[data-prop]").forEach((el) => {
      const evt = (el.type === "text" || el.type === "color") ? "input" : "change";
      el.addEventListener(evt, () => {
        if (state.syncProps) return;
        const prop = el.getAttribute("data-prop");
        if (prop === "fontFamily") {
          try { el.style.fontFamily = String(el.value || "inherit"); } catch (e) {}
        }
        const liveNoHistory = (el.type === "color" || el.type === "range");
        applyProp(prop, el.value, { skipHistory: liveNoHistory, skipUiSync: liveNoHistory });
      });
      if (el.type === "color" || el.type === "range") {
        el.addEventListener("change", () => {
          if (state.syncProps) return;
          const prop = el.getAttribute("data-prop");
          applyProp(prop, el.value);
        });
      }
    });
  }

  canvas.on("object:added", (e) => {
    if (e && e.target && !isSystemObject(e.target)) {
      tuneObjectForTouch(e.target);
      ensureUid(e.target);
      if (e.target.type !== "activeSelection") rememberLastSelectedObjectRef(e.target);
    }
    updateLayerList(); updateProps(); syncMobileEmptyStateOverlay(); pushHistory();
    scheduleAutoZoneDraftSave();
    if (typeof root.__dsSyncMobileObjectMode === "function") root.__dsSyncMobileObjectMode();
  });
  canvas.on("object:removed", () => {
    if (state.lastSelectedObject && canvas.getObjects().indexOf(state.lastSelectedObject) === -1) {
      state.lastSelectedObject = null;
    }
    updateLayerList(); updateProps(); syncMobileEmptyStateOverlay(); pushHistory();
    scheduleAutoZoneDraftSave();
    if (typeof root.__dsSyncMobileObjectMode === "function") root.__dsSyncMobileObjectMode();
  });
  canvas.on("object:modified", () => {
    const active = canvas.getActiveObject();
    if (active && !isSystemObject(active) && active.type !== "activeSelection") {
      rememberLastSelectedObjectRef(active);
      captureObjectUiValues(active);
    }
    if (root.classList.contains("ds-mobile-sheet-open")) {
      try { hydrateMobileStyleFieldsFromObject(getSingleActive() || getPreferredMobileObjectRef()); } catch (e) {}
    }
    updateLayerList(); updateProps(); pushHistory();
    scheduleAutoZoneDraftSave();
    if (typeof root.__dsSyncMobileObjectMode === "function") root.__dsSyncMobileObjectMode();
  });
  canvas.on("text:changed", () => { updateLayerList(); updateProps(); pushHistory(); scheduleAutoZoneDraftSave(); });
  canvas.on("selection:created", () => {
    clearSelectionMarqueeArtifact();
    rememberSelection(); updateLayerList(); updateProps();
    const active = canvas.getActiveObject();
    if (active) {
      if (active.type === "activeSelection" && typeof active.getObjects === "function") {
        active.getObjects().forEach((o) => tuneObjectForTouch(o));
      } else {
        tuneObjectForTouch(active);
      }
      canvas.requestRenderAll();
      syncActiveControlHandleSize();
    }
    const a = getSingleActive();
    if (a && !isSystemObject(a) && a.type !== "activeSelection") {
      rememberLastSelectedObjectRef(a);
      captureObjectUiValues(a);
    }
    if (a && isTextObject(a) && !isStickerObject(a)) state.lastSelectedText = a;
    if (root.classList.contains("ds-mobile-sheet-open")) {
      try { hydrateMobileStyleFieldsFromObject(a || getPreferredMobileObjectRef()); } catch (e) {}
    }
    if (typeof root.__dsSyncMobileObjectMode === "function") root.__dsSyncMobileObjectMode();
    syncMobileModeDirectFromSelection();
  });
  canvas.on("selection:updated", () => {
    clearSelectionMarqueeArtifact();
    rememberSelection(); updateLayerList(); updateProps();
    const active = canvas.getActiveObject();
    if (active) {
      if (active.type === "activeSelection" && typeof active.getObjects === "function") {
        active.getObjects().forEach((o) => tuneObjectForTouch(o));
      } else {
        tuneObjectForTouch(active);
      }
      canvas.requestRenderAll();
      syncActiveControlHandleSize();
    }
    const a = getSingleActive();
    if (a && !isSystemObject(a) && a.type !== "activeSelection") {
      rememberLastSelectedObjectRef(a);
      captureObjectUiValues(a);
    }
    if (a && isTextObject(a) && !isStickerObject(a)) state.lastSelectedText = a;
    if (root.classList.contains("ds-mobile-sheet-open")) {
      try { hydrateMobileStyleFieldsFromObject(a || getPreferredMobileObjectRef()); } catch (e) {}
    }
    if (typeof root.__dsSyncMobileObjectMode === "function") root.__dsSyncMobileObjectMode();
    syncMobileModeDirectFromSelection();
  });
  canvas.on("selection:cleared", () => {
    clearSelectionMarqueeArtifact();
    state.gestureLastTapTs = 0;
    state.gestureLastTapUid = "";
    state.gestureTapDown = null;
    state.gestureTapMoved = false;
    state.gestureTapTransforming = false;
    updateLayerList();
    updateProps();
    if (typeof root.__dsSyncMobileObjectMode === "function") root.__dsSyncMobileObjectMode();
    syncMobileModeDirectFromSelection();
  });
  canvas.on("object:moving", (e) => {
    state.gestureTapMoved = true;
    state.gestureTapTransforming = true;
    snapObject(e.target);
    updateObjectQuickButton();
    updateContextToolbarPosition();
    if (typeof root.__dsSyncMobileObjectMode === "function") root.__dsSyncMobileObjectMode();
  });
  canvas.on("object:scaling", () => {
    state.gestureTapMoved = true;
    state.gestureTapTransforming = true;
    updateObjectQuickButton();
    updateContextToolbarPosition();
    if (typeof root.__dsSyncMobileObjectMode === "function") root.__dsSyncMobileObjectMode();
  });
  canvas.on("object:rotating", () => {
    state.gestureTapMoved = true;
    state.gestureTapTransforming = true;
    updateObjectQuickButton();
    updateContextToolbarPosition();
    if (typeof root.__dsSyncMobileObjectMode === "function") root.__dsSyncMobileObjectMode();
  });
  canvas.on("mouse:move", (e) => {
    if (!isMobilePortraitUi()) return;
    const down = state.gestureTapDown;
    if (!down) return;
    const src = e && e.e ? e.e : null;
    const x = Number(src && src.clientX || 0);
    const y = Number(src && src.clientY || 0);
    if (Math.abs(x - down.x) > state.gestureTapMoveMax || Math.abs(y - down.y) > state.gestureTapMoveMax) {
      state.gestureTapMoved = true;
    }
  });
  canvas.on("mouse:down", (e) => {
    const blankTarget = e && e.target ? e.target : null;
    const treatAsBlank = !blankTarget || (isSystemObject(blankTarget) && !isCropFrameObject(blankTarget));
    if (treatAsBlank) {
      if (canvas.getActiveObject()) {
        canvas.discardActiveObject();
        canvas.requestRenderAll();
      }
    }
    if (!isMobilePortraitUi()) return;
    state.gestureTapMoved = false;
    state.gestureTapTransforming = !!(e && e.transform);
    const src = e && e.e ? e.e : null;
    const x = Number(src && src.clientX || 0);
    const y = Number(src && src.clientY || 0);
    const target = blankTarget;
    if (!target || (isSystemObject(target) && !isCropFrameObject(target)) || target.type === "activeSelection") {
      state.gestureTapDown = null;
      dragDbg("mouse:down(blank)", { hasActive: !!canvas.getActiveObject(), transform: !!(canvas && canvas._currentTransform) });
      return;
    }
    state.gestureTapDown = { uid: ensureUid(target), target: target, x: x, y: y, ts: Date.now() };
    dragDbg("mouse:down(target)", { uid: state.gestureTapDown.uid, type: String(target.type || ""), x: x, y: y, transform: !!(canvas && canvas._currentTransform) });
  });
  canvas.on("mouse:up", (e) => {
    clearSelectionMarqueeArtifact();
    try {
      const srcDbg = e && e.e ? e.e : null;
      dragDbg("mouse:up", {
        evtType: String(srcDbg && srcDbg.type || ""),
        pointerType: String(srcDbg && srcDbg.pointerType || ""),
        buttons: Number(srcDbg && srcDbg.buttons || 0),
        targetType: String(e && e.target && e.target.type || ""),
        transformBefore: !!(canvas && canvas._currentTransform)
      });
    } catch (eDbg) {}
    if (!isMobilePortraitUi()) return;
    const down = state.gestureTapDown;
    const target = e && e.target ? e.target : null;
    const src = e && e.e ? e.e : null;
    const x = Number(src && src.clientX || 0);
    const y = Number(src && src.clientY || 0);
    const moved = !down || state.gestureTapMoved ||
      Math.abs(x - Number(down && down.x || 0)) > state.gestureTapMoveMax ||
      Math.abs(y - Number(down && down.y || 0)) > state.gestureTapMoveMax;
    const validTarget = !!(down && target && !isSystemObject(target) && target.type !== "activeSelection");
    const sameObject = validTarget && (String(ensureUid(target)) === String(down.uid || ""));
    if (target && isTextObject(target) && moved) {
      try {
        if (target.isEditing && typeof target.exitEditing === "function") target.exitEditing();
      } catch (eTxt) {}
    }
    state.gestureTapDown = null;
    state.gestureTapMoved = false;
    state.gestureTapTransforming = false;
  });
  canvas.on("mouse:dblclick", (e) => {
    const target = e && e.target ? e.target : null;
    if (!target || !isTextObject(target)) return;
    focusTextObjectForEditing(target);
  });
  // Fallback for mobile browsers where Fabric's mouse double-click emulation is inconsistent.
  // This keeps text editing reliable on touch devices.
  if (canvas && canvas.upperCanvasEl) {
    const handleCanvasBlankPointerDown = (ev) => {
      try {
        if (!canvas || !canvas.getActiveObject || !canvas.getActiveObject()) return;
        const target = canvas.findTarget(ev, false);
        if (target && (!isSystemObject(target) || isCropFrameObject(target))) return;
        canvas.discardActiveObject();
        canvas.requestRenderAll();
        if (typeof root.__dsSyncMobileObjectMode === "function") root.__dsSyncMobileObjectMode();
        syncMobileModeDirectFromSelection();
      } catch (e) {}
    };
    const handleViewportBlankPointerDown = (ev) => {
      try {
        if (!canvas || !canvas.getActiveObject || !canvas.getActiveObject()) return;
        if (state && state.cropSession) return;
        if (ev && ev.defaultPrevented) return;
        if (ev && typeof ev.button === "number" && ev.button !== 0) return;
        const node = ev && ev.target ? ev.target : null;
        if (!node || !node.closest) return;
        if (node.closest(".canvas-container")) return;
        if (node.closest(".ds-object-quick-dock, .ds-context-toolbar")) return;
        if (node.closest(".ds-no-template-start, .ds-startup-flow-panel, .ds-mobile-sheet, .ds-zone-picker-modal, .ds-product-preview-modal")) return;
        canvas.discardActiveObject();
        canvas.requestRenderAll();
        if (typeof root.__dsSyncMobileObjectMode === "function") root.__dsSyncMobileObjectMode();
        syncMobileModeDirectFromSelection();
      } catch (e) {}
    };
    canvas.upperCanvasEl.addEventListener("pointerdown", handleCanvasBlankPointerDown, true);
    if (viewportEl) viewportEl.addEventListener("pointerdown", handleViewportBlankPointerDown, true);
    if (wrapEl && wrapEl !== viewportEl) wrapEl.addEventListener("pointerdown", handleViewportBlankPointerDown, true);
    canvas.upperCanvasEl.addEventListener("touchstart", (ev) => {
      if (!isMobileViewport()) return;
      if (!ev.touches || ev.touches.length !== 1) return;
      const t = ev.touches[0];
      state.touchStartX = Number(t && t.clientX || 0);
      state.touchStartY = Number(t && t.clientY || 0);
    }, { passive: true });
    canvas.upperCanvasEl.addEventListener("touchend", (ev) => {
      if (!isMobileViewport()) return;
      if (root.classList.contains("ds-mobile-sheet-open")) return;
      if (!ev.changedTouches || ev.changedTouches.length !== 1) return;
      if (ev.touches && ev.touches.length > 0) return;
      const t = ev.changedTouches[0];
      const x = Number(t && t.clientX || 0);
      const y = Number(t && t.clientY || 0);
      const moved = Math.abs(x - Number(state.touchStartX || 0)) > state.gestureTapMoveMax ||
        Math.abs(y - Number(state.touchStartY || 0)) > state.gestureTapMoveMax;
      if (moved) return;
      let target = null;
      try { target = canvas.findTarget(ev, false); } catch (e) {}
      if (!target || isSystemObject(target) || !isTextObject(target) || isStickerObject(target)) return;
      const uid = String(ensureUid(target));
      const now = Date.now();
      const isDoubleTap = String(state.touchLastTapUid || "") === uid && (now - Number(state.touchLastTapTs || 0)) <= 360;
      state.touchLastTapTs = now;
      state.touchLastTapUid = uid;
      if (isDoubleTap) {
        try { focusTextObjectForEditing(target); } catch (e2) {}
      }
    }, { passive: false });
  }
  // Minimal fallback release for rare missed pointerup/touchend cases.
  function releaseTransformFallback(ev) {
    try {
      if (!canvas) return;
      if (!canvas._currentTransform) return;
      const evtType = String(ev && ev.type || "").toLowerCase();
      const isCancelOrBlur = evtType.indexOf("cancel") >= 0 || evtType === "blur";
      if (!isCancelOrBlur) return;
      dragDbg("releaseTransformFallback", {
        evtType: evtType,
        pointerType: String(ev && ev.pointerType || ""),
        buttons: Number(ev && ev.buttons || 0),
        transform: true
      });
      const src = ev || {};
      if (typeof canvas.__onMouseUp === "function") {
        canvas.__onMouseUp(src);
      } else if (typeof canvas._onMouseUp === "function") {
        canvas._onMouseUp(src);
      }
      canvas._currentTransform = null;
      canvas._groupSelector = null;
      const activeObj = canvas.getActiveObject && canvas.getActiveObject();
      if (activeObj && typeof activeObj === "object") activeObj.isMoving = false;
      canvas.requestRenderAll();
      dragDbg("releaseTransformFallback:done", {
        evtType: String(ev && ev.type || ""),
        activeType: String(activeObj && activeObj.type || ""),
        transformNow: !!(canvas && canvas._currentTransform)
      });
    } catch (e) {}
  }
  window.addEventListener("pointercancel", releaseTransformFallback, { passive: true });
  window.addEventListener("touchcancel", releaseTransformFallback, { passive: true });
  window.addEventListener("blur", releaseTransformFallback, { passive: true });

  canvas.on("after:render", () => { markPreviewTexturesDirty(); updateObjectQuickButton(); updateContextToolbarPosition(); });
  window.addEventListener("pagehide", () => {
    try {
      if (!zoneState || !zoneState.enabled || !zoneState.currentId) return;
      captureCurrentZone();
      saveZoneToLocalDraftById(zoneState.currentId, false);
    } catch (e) {}
  }, { passive: true });
  window.addEventListener("beforeunload", () => {
    try {
      if (!zoneState || !zoneState.enabled || !zoneState.currentId) return;
      captureCurrentZone();
      saveZoneToLocalDraftById(zoneState.currentId, false);
    } catch (e) {}
  });

  document.addEventListener("keydown", (e) => {
    const t = e.target, tag = t && t.tagName ? t.tagName.toLowerCase() : "";
    const editable = !!(t && (t.isContentEditable || tag === "input" || tag === "textarea" || tag === "select"));
    if (editable) return;
    if (isStartupInteractionLocked()) {
      if (e.key === "Escape") return;
      return;
    }
    if (e.key === "Escape") {
      cancelCropSession();
      closePropsModal();
      closeMyDesignsModal();
      closeApplyProductModal();
      closeAssetLibraryModal();
      closePreviewModal();
      return;
    }
    if (e.key === "Delete" || e.key === "Backspace") {
      if (withActive((o) => canvas.remove(o))) pushHistory();
      e.preventDefault();
    }
  });

  bindActions();
  bindContextToolbarActions();
  window.addEventListener("resize", updateContextToolbarPosition, { passive: true });
  window.addEventListener("orientationchange", updateContextToolbarPosition, { passive: true });
  root.addEventListener("click", (e) => {
    const btn = e && e.target && e.target.closest
      ? e.target.closest('[data-action="save"], [data-action="final-save"], [data-action="apply-product"]')
      : null;
    if (!btn || !root.contains(btn)) return;
    const action = String(btn.getAttribute("data-action") || "");
    if (action === "save" || action === "final-save" || action === "apply-product") {
      try { e.stopImmediatePropagation(); } catch (errStop) {}
      try { e.stopPropagation(); } catch (errStop2) {}
    }
    if (state.saving && (action === "save" || action === "final-save" || action === "apply-product")) {
      e.preventDefault();
      setStatus("Save in progress. Please wait until all zones are saved.");
      return;
    }
    if (action === "save") {
      e.preventDefault();
      saveCurrentZoneToLocalDraft();
      return;
    }
    if (action === "final-save") {
      e.preventDefault();
      (async () => {
        const ok = await ensureProtectedActionAllowed("save", false);
        if (!ok) return;
        saveDesign();
      })();
      return;
    }
    if (action === "apply-product") {
      e.preventDefault();
      (async () => {
        const ok = await ensureProtectedActionAllowed("apply", false);
        if (!ok) return;
        openApplyProductModal();
      })();
      return;
    }
    
  }, true);
  applyUiConfig();
  populateFontFamilySelect();
  if (aiRemoveBgBtn && !aiRemoveBgEnabled) {
    aiRemoveBgBtn.style.display = "none";
  }
  loadTemplateOptions();
  if (myDesignsListEl) {
    myDesignsListEl.addEventListener("click", (e) => {
      const raw = e.target;
      const t = raw && raw.closest ? raw.closest("button[data-action]") : null;
      if (!t || !t.getAttribute) return;
      const action = t.getAttribute("data-action");
      const id = t.getAttribute("data-id");
      if (action === "my-design-load") {
        const tid = parseInt(t.getAttribute("data-template-id") || "0", 10);
        const did = parseInt(id || "0", 10);
        if (!did) {
          setStatus("Invalid design selection.");
          return;
        }
        if (tid > 0 && Number(selectedTemplateId || 0) !== tid) {
          navigateToTemplateAndDesign(tid, did);
          return;
        }
        loadDesignById(did).then((ok) => {
          if (ok) closeMyDesignsModal();
        });
      } else if (action === "my-design-start-load") {
        const tid = parseInt(t.getAttribute("data-template-id") || "0", 10);
        const did = parseInt(id || "0", 10);
        if (!tid || !did) {
          setStatus("Invalid design selection.");
          return;
        }
        if (Number(selectedTemplateId || 0) === tid) {
          loadDesignById(did).then((ok) => { if (ok) closeMyDesignsModal(); });
          return;
        }
        navigateToTemplateAndDesign(tid, did);
      } else if (action === "my-design-use") {
        useDesignOnProduct(id, t.getAttribute("data-png"), t.getAttribute("data-title"));
        closeMyDesignsModal();
      } else if (action === "my-design-visibility") {
        const next = t.getAttribute("data-next") || "private";
        setDesignVisibility(id, next);
      }
    });
  }
  if (applyProductListEl) {
    applyProductListEl.addEventListener("click", async (e) => {
      const t = e.target;
      if (!t || !t.getAttribute) return;
      const action = t.getAttribute("data-action");
      if (action !== "apply-open-product") return;
      if (!(await ensureProtectedActionAllowed("apply", true))) return;
      const baseUrl = String(t.getAttribute("data-product-url") || "");
      if (!baseUrl) return;
      const selected = currentSelectedDesign();
      if (!selected || !selected.postId) {
        setStatus("Save design first, then apply to product.");
        return;
      }
      let nextUrl = baseUrl;
      try {
        const u = new URL(baseUrl, window.location.origin);
        if (selected.uuid) u.searchParams.set("ds_design_uuid", String(selected.uuid));
        u.searchParams.set("ds_design", String(selected.postId));
        if (selected.png) u.searchParams.set("ds_design_png", String(selected.png));
        nextUrl = u.toString();
      } catch (err) {}
      window.location.href = nextUrl;
    });
  }
  if (assetLibraryListEl) {
    const bridgeInsertEmoji = (glyph) => {
      if (typeof root.__dsInsertEmoji === "function") {
        try { return !!root.__dsInsertEmoji(glyph); } catch (e) {}
      }
      return false;
    };
    const bridgeInsertIcon = (iconName) => {
      if (typeof root.__dsInsertIcon === "function") {
        try { return !!root.__dsInsertIcon(iconName); } catch (e) {}
      }
      return false;
    };
    const bridgeResolveAction = (actionName) => {
      if (typeof root.__dsResolveMobileAction === "function") {
        try { return !!root.__dsResolveMobileAction(actionName); } catch (e) {}
      }
      return false;
    };
    const clickToolbarControl = (selector) => {
      const el = root.querySelector(String(selector || ""));
      if (!el || typeof el.click !== "function") return false;
      el.click();
      return true;
    };
    const handleAssetPick = (e) => {
      const t = e.target && e.target.closest
        ? e.target.closest("button[data-action], [data-action], [data-emoji], [data-icon]")
        : null;
      if (!t || !t.getAttribute) return;
      if (!assetLibraryListEl.contains(t)) return;
      let action = t.getAttribute("data-action");
      if (!action) {
        if (t.hasAttribute("data-emoji")) action = "asset-emoji";
        else if (t.hasAttribute("data-icon")) action = "asset-icon";
      }
      if (!action) return;
      e.preventDefault();
      e.stopPropagation();
      const src = t.getAttribute("data-src") || "";
      const kind = t.getAttribute("data-kind") || "textures";
      if (action === "asset-insert") {
        insertLibraryAsset(src, kind);
      } else if (action === "asset-bg") {
        applyTextureAsBackground(src);
      } else if (action === "asset-shape") {
        const shape = String(t.getAttribute("data-shape") || "").trim().toLowerCase();
        if (!shape) return;
        const ok = clickToolbarControl('.ds-toolbar [data-shape="' + shape + '"]')
          || bridgeResolveAction("shape-" + shape);
        if (ok) setStatus("Shape inserted.");
      } else if (action === "asset-emoji") {
        const emoji = String(t.getAttribute("data-emoji") || "").trim()
          || String((t.querySelector && t.querySelector(".ds-asset-glyph") ? t.querySelector(".ds-asset-glyph").textContent : "") || "").trim();
        if (!emoji) return;
        const fallbackAction = String(t.getAttribute("data-mobile-sheet-action") || "").trim();
        const ok = bridgeInsertEmoji(emoji) || (fallbackAction ? bridgeResolveAction(fallbackAction) : false);
        if (ok) {
          setStatus("Emoticon inserted.");
          closeAssetLibraryModal();
        } else {
          setStatus("Could not insert emoticon.");
        }
      } else if (action === "asset-icon") {
        const icon = String(t.getAttribute("data-icon") || "").trim().toLowerCase().replace(/^icon-/, "");
        if (!icon) return;
        const fallbackAction = String(t.getAttribute("data-mobile-sheet-action") || "").trim();
        const ok = bridgeInsertIcon(icon) || (fallbackAction ? bridgeResolveAction(fallbackAction) : false);
        if (ok) {
          setStatus("Icon inserted.");
          closeAssetLibraryModal();
        } else {
          setStatus("Could not insert icon.");
        }
      }
    };
    assetLibraryListEl.addEventListener("click", handleAssetPick);
  }
  if (!canOutput) {
    root.querySelectorAll('[data-action="export"], [data-action="my-designs"], [data-action="ai-remove-bg"]').forEach((btn) => {
      btn.title = tUi("login_required", "Login required");
      btn.setAttribute("data-needs-login", "1");
      btn.classList.add("ds-needs-login");
    });
    if (outputAuthHintEl) {
      outputAuthHintEl.style.display = "";
      if (loginUrl) {
        outputAuthHintEl.innerHTML = '<a href="' + loginUrl + '">' + escHtml(tUi("log_in", "Log in")) + '</a> ' + escHtml(tUi("to_enable_export_my_designs", "to enable Export and My Designs."));
      } else {
        outputAuthHintEl.textContent = tUi("log_in_enable_export_my_designs", "Log in to enable Export and My Designs.");
      }
    }
    setStatus(loginUrl
      ? tUi("guest_mode_save_apply_enabled_url", "Guest mode: design, Save and Apply are enabled. Export and My Designs require login: %s", [loginUrl])
      : tUi("guest_mode_save_apply_enabled", "Guest mode: design, Save and Apply are enabled. Export and My Designs require login."));
  }
  if (mugMiniCanvasEl) {
    mugMiniCanvasEl.addEventListener("click", () => {
      if (!canOpenPreviewNow()) return;
      openPreviewModal();
    });
  }
  bindBackdropClose(mugModalEl, closePreviewModal);
  bindBackdropClose(propsModalEl, closePropsModal);
  bindBackdropClose(myDesignsModalEl, closeMyDesignsModal);
  bindBackdropClose(applyProductModalEl, closeApplyProductModal);
  bindBackdropClose(assetLibraryModalEl, closeAssetLibraryModal);
  if (cfg && Number(cfg.cart_preview_mode || 0) === 1) {
    let previewAutoTries = 0;
    const previewAutoMax = 28;
    const tryOpenCartPreview = () => {
      previewAutoTries += 1;
      try {
        if (enablePreview3D) {
          openPreviewModal();
          const opened3d = !!(mugModalEl && mugModalEl.getAttribute("aria-hidden") === "false");
          if (opened3d) return;
        } else {
          openProductPreview2DModal();
          const modal2d = productPreview2DModal && productPreview2DModal.list ? productPreview2DModal : null;
          const hasRows = !!(modal2d && modal2d.list && modal2d.list.children && modal2d.list.children.length > 0);
          const hasEmpty = !!(modal2d && modal2d.list && /ds-zone-picker-empty/.test(String(modal2d.list.innerHTML || "")));
          if (hasRows && !hasEmpty) return;
        }
      } catch (e) {}
      if (previewAutoTries < previewAutoMax) {
        window.setTimeout(tryOpenCartPreview, 220);
      }
    };
    window.setTimeout(tryOpenCartPreview, 320);
  }
  if (zoneState && zoneState.enabled) {
    const localDraft = loadLocalDraft();
    const fromTemplatePick = !!forceInitialZoneSelectionAfterTemplatePick;
    if (!fromTemplatePick && initialDesignId <= 0 && localDraft) applyLocalDraftToState(localDraft);
    let loadedStartupSelection = false;
    if (startupFlowEnabled && startupApproved) {
      if (startupStep !== "zones") {
        try {
          const fromParam = String(startupParams.get("ds_zsel") || "").trim();
          if (fromParam) {
            const idsParam = fromParam.split(",").map((v) => String(v || "").trim()).filter(Boolean);
            if (idsParam.length) {
              applyLoadedZoneSelection(idsParam);
              loadedStartupSelection = true;
            }
          }
        } catch (e) {}
        try {
          const key = startupSelectionKeyForTemplate(selectedTemplateId);
          const raw = window.sessionStorage.getItem(key);
          if (raw) {
            const parsed = JSON.parse(raw || "{}");
            const ids = Array.isArray(parsed && parsed.selectedIds) ? parsed.selectedIds : [];
            if (ids.length) {
              applyLoadedZoneSelection(ids);
              loadedStartupSelection = true;
            }
            window.sessionStorage.removeItem(key);
          }
        } catch (e) {}
        if (!loadedStartupSelection) {
          try {
            const readyIds = loadStartupZoneReadyForTemplate(selectedTemplateId);
            if (readyIds.length) {
              applyLoadedZoneSelection(readyIds);
              loadedStartupSelection = true;
            }
          } catch (e) {}
        }
      } else {
        try {
          const key = startupSelectionKeyForTemplate(selectedTemplateId);
          window.sessionStorage.removeItem(key);
        } catch (e) {}
      }
    }
    if ((fromTemplatePick || (startupFlowEnabled && !startupApproved)) && Array.isArray(zoneState.zones) && zoneState.zones.length > 1 && !loadedStartupSelection) {
      zoneState.userSelectionReady = false;
      zoneState.selectedIds = [];
    }
    const hasTemplateSelected = Number(selectedTemplateId || 0) > 0;
    const hasSelectedZones = getSelectedZoneIds().length > 0;
    const mustOpenZoneToolsOnEditorOpen = !!(hasTemplateSelected && zoneState && zoneState.enabled && !hasSelectedZones && !pendingInitialDesignLoad);
    if ((!pendingInitialDesignLoad && requiresInitialZoneSelection()) || mustOpenZoneToolsOnEditorOpen) {
      setZoneSelectionGate(true);
      if (startupFlowEnabled && (startupStep === "zones" || forceInitialZoneSelectionAfterTemplatePick)) {
        triggerZoneSelectionRequiredStatus();
      }
    } else {
      setZoneSelectionGate(false);
      zoneState.internalSwitch = true;
      switchToZone(zoneState.currentId);
      zoneState.internalSwitch = false;
    }
  }
  dockToolbarBlocksToSides();
  applyUnifiedUiIcons(root);
  placePreviewControl();
  applyViewportModeFlags();
  try {
    const params = new URLSearchParams(window.location.search || "");
    const restoreByParam = params.get("ds_fullscreen") === "1";
    const restoreBySession = window.sessionStorage.getItem("ds_editor_fullscreen") === "1";
    if (restoreByParam || restoreBySession) {
      syncFullscreenUiState(true);
    }
  } catch (e) {}
  redrawSystem();
  fitCanvas(true);
  updateLayerList();
  updateProps();
  syncMobileEmptyStateOverlay();
  resetHistory();
  bindMobileQuickShellActions();
  startMobileModeWatchdog();
  startupFlowStarted = true;
  setStartupFlowGate(false);
  if (startupFlowEnabled && startupApproved && startupStep === "zones") {
    const isZoneModalOpen = () => {
      try {
        const modal = root.querySelector('[data-role="zone-picker-modal"]');
        return !!(modal && modal.classList.contains("is-open") && modal.getAttribute("aria-hidden") === "false");
      } catch (e) {
        return false;
      }
    };
    const forceOpenZoneTools = () => {
      if (isZoneModalOpen()) return true;
      try {
        if (requiresInitialZoneSelection()) setZoneSelectionGate(true);
      } catch (e0) {}
      try {
        clickRealControl('.ds-toolbar [data-action="zone-picker-open"]');
      } catch (e1) {}
      try {
        clickRealControl('.ds-toolbar [data-action="toggle-zones"]');
      } catch (e2) {}
      try {
        openZonePickerModal();
      } catch (e3) {}
      return isZoneModalOpen();
    };
    let tries = 0;
    const timer = window.setInterval(() => {
      tries += 1;
      const ok = forceOpenZoneTools();
      if (ok || tries >= 18) {
        window.clearInterval(timer);
      }
    }, 120);
    window.setTimeout(() => {
      if (!isZoneModalOpen()) forceOpenZoneTools();
    }, 0);
    window.setTimeout(() => {
      if (!isZoneModalOpen()) forceOpenZoneTools();
    }, 260);
    const reattemptOpen = () => {
      if (isZoneModalOpen()) return;
      forceOpenZoneTools();
    };
    window.addEventListener("load", reattemptOpen, { once: true });
    const firstInteractionRetry = () => {
      if (isZoneModalOpen()) return;
      forceOpenZoneTools();
      if (isZoneModalOpen()) {
        document.removeEventListener("pointerdown", firstInteractionRetry, true);
        document.removeEventListener("keydown", firstInteractionRetry, true);
      }
    };
    document.addEventListener("pointerdown", firstInteractionRetry, true);
    document.addEventListener("keydown", firstInteractionRetry, true);
  }
  if (startupFlowEnabled && startupApproved) {
    try {
      const u = new URL(window.location.href);
      u.searchParams.delete("ds_zsel");
      u.searchParams.delete("ds_start");
      u.searchParams.delete("ds_step");
      u.searchParams.delete("ds_open_zones");
      window.history.replaceState(null, "", u.toString());
    } catch (e) {}
  }
  if (startupFlowEnabled && !startupApproved && startupOpenZonesRequested) {
    try {
      const u = new URL(window.location.href);
      u.searchParams.delete("ds_open_zones");
      window.history.replaceState(null, "", u.toString());
    } catch (e) {}
  }
  setStartupLoadingOverlay(false);
  const shouldAutoOpenZonesPostInit = (() => {
    if (!zoneState || !zoneState.enabled) return false;
    if (Number(selectedTemplateId || 0) <= 0) return false;
    if (pendingInitialDesignLoad) return false;
    return startupAutoOpenZonesAfterEditorInit || requiresInitialZoneSelection() || getSelectedZoneIds().length === 0;
  })();
  if (shouldAutoOpenZonesPostInit) {
    const tryOpenZonesPostInit = () => {
      try {
        if (!zoneState || !zoneState.enabled) return false;
        if (Number(selectedTemplateId || 0) <= 0) return false;
        if (!(requiresInitialZoneSelection() || getSelectedZoneIds().length === 0)) return false;
        setZoneSelectionGate(true);
        openZonePickerModal();
        return true;
      } catch (e) {
        return false;
      }
    };
    const zoneModalOpenNow = () => {
      try {
        const modal = root.querySelector('[data-role="zone-picker-modal"]');
        return !!(modal && modal.classList.contains("is-open") && modal.getAttribute("aria-hidden") === "false");
      } catch (e) {
        return false;
      }
    };
    const removeInteractionArms = () => {
      try { document.removeEventListener("pointerdown", onFirstInteraction, true); } catch (e) {}
      try { document.removeEventListener("touchstart", onFirstInteraction, true); } catch (e) {}
      try { document.removeEventListener("click", onFirstInteraction, true); } catch (e) {}
      try { document.removeEventListener("mousemove", onFirstInteraction, true); } catch (e) {}
    };
    const onFirstInteraction = () => {
      if (zoneModalOpenNow()) { removeInteractionArms(); return; }
      const opened = tryOpenZonesPostInit();
      if (opened || zoneModalOpenNow()) removeInteractionArms();
    };
    window.setTimeout(() => {
      if (tryOpenZonesPostInit() || zoneModalOpenNow()) return;
      document.addEventListener("pointerdown", onFirstInteraction, true);
      document.addEventListener("touchstart", onFirstInteraction, true);
      document.addEventListener("click", onFirstInteraction, true);
      document.addEventListener("mousemove", onFirstInteraction, true);
    }, 0);
  }
  if (!pendingInitialDesignLoad && requiresInitialZoneSelection()) setStatus(tUi("choose_print_areas_start_designing", "Choose print areas to start designing."));
  else setStatus(tUi("editor_ready", "Editor ready."));
  try {
    const r = root.getBoundingClientRect();
    // Some themes/blocks keep shortcode content above viewport after hydration.
    if (r.bottom < 120 || r.top < -160) {
      root.scrollIntoView({ behavior: "auto", block: "start" });
    }
  } catch (e) {}

  if (Number(initialDesignId || 0) > 0) {
    const pre = Number(initialDesignId);
    if (designIdInput) designIdInput.value = String(pre);
    loadDesignById(pre).then((ok) => {
      pendingInitialDesignLoad = false;
      if (!ok) return;
      try { window.sessionStorage.removeItem("ds_pending_design_id"); } catch (e) {}
      if (initialAutoApplyToProduct && typeof openApplyProductModal === "function") {
        window.setTimeout(() => {
          try { openApplyProductModal(); } catch (e2) {}
        }, 80);
      }
      try { window.sessionStorage.removeItem("ds_pending_auto_apply_product"); } catch (e3) {}
    }).catch(() => {
      pendingInitialDesignLoad = false;
    });
  }

  let rs = null;
  function scheduleViewportRelayout(forceFit) {
    if (rs) window.clearTimeout(rs);
    rs = window.setTimeout(() => {
      refreshLayoutUi(!!forceFit);
    }, 140);
  }
  window.addEventListener("resize", () => scheduleViewportRelayout(false));
  window.addEventListener("orientationchange", () => scheduleViewportRelayout(true));
  if (typeof window !== "undefined" && window.visualViewport && window.visualViewport.addEventListener) {
    window.visualViewport.addEventListener("resize", () => scheduleViewportRelayout(false));
  }
})();
