<script>
import { v4 as uuidv4 } from "uuid";
import { onMount } from "svelte";
import { get } from "svelte/store";
import BlockList from "./components/BlockList.svelte";
import { blocks, settings, theme, pugCode } from "./store";

  const CUSTOM_PRESETS_KEY = "vip_letter_editor_custom_presets_v1";
  const NOTES_KEY = "vip_letter_editor_notes_v1";
  const NOVINKI_FORM_KEY = "vip_letter_editor_preset_novinki_v1";
  const AKCIA_FORM_KEY = "vip_letter_editor_preset_akcia_v1";
  const NEWCOLL_FORM_KEY = "vip_letter_editor_preset_newcoll_v1";
  const HTML_PUG_FORM_KEY = "vip_letter_editor_html_pug_v1";

  let activeTab = "builder";
  let copyStatus = "";
  let currentVersion = "female";
  const defaultNovinkiForm = {
    femaleIds: "",
    maleIds: "",
    showPrices: false,
    heading: "Новинки недели",
    subheading: "Узнайте о новинках первыми и выбирайте лучшее <br>в подборках от наших стилистов!",
    btnLeftText: "ДЛЯ НЕЕ",
    btnLeftHref: "https://vipavenue.ru/new/womens/",
    btnRightText: "ДЛЯ НЕГО",
    btnRightHref: "https://vipavenue.ru/new/mens/",
    btnMoreFemaleText: "Больше новинок для нее",
    btnMoreFemaleHref: "https://vipavenue.ru/new/womens/",
    btnMoreMaleText: "Больше новинок для него",
    btnMoreMaleHref: "https://vipavenue.ru/new/mens/",
    sizeFemaleSizes: "40,42,44,46,48,50,52,54",
    sizeFemaleLinks:
      "https://vipavenue.ru/new/womens/clothes/?sizes=977130,https://vipavenue.ru/new/womens/clothes/?sizes=977142,https://vipavenue.ru/new/womens/clothes/?sizes=977154,https://vipavenue.ru/new/womens/clothes/?sizes=977166,https://vipavenue.ru/new/womens/clothes/?sizes=977178,https://vipavenue.ru/new/womens/clothes/?sizes=977189,https://vipavenue.ru/new/womens/clothes/?sizes=977200,https://vipavenue.ru/new/womens/clothes/?sizes=977409",
    sizeMaleSizes: "46,48,50,52,54,56,58,60",
    sizeMaleLinks:
      "https://vipavenue.ru/new/mens/clothes/?sizes=977052,https://vipavenue.ru/new/mens/clothes/?sizes=977061,https://vipavenue.ru/new/mens/clothes/?sizes=977070,https://vipavenue.ru/new/mens/clothes/?sizes=977079,https://vipavenue.ru/new/mens/clothes/?sizes=977088,https://vipavenue.ru/new/mens/clothes/?sizes=977097,https://vipavenue.ru/new/mens/clothes/?sizes=977105,https://vipavenue.ru/new/mens/clothes/?sizes=978803",
    dividerWidth: 300,
    dividerHeight: 1
  };
  let novinkiForm = { ...defaultNovinkiForm };

  const defaultAkciaForm = {
    bannerImageUrl: "https://email-files.vipavenue.ru/newsletter_2025/banner_sale/BF.jpg",
    btnFemaleText: "Подборка для нее",
    btnFemaleHref: "https://vipavenue.ru/sale/womens/",
    btnMaleText: "Подборка для него",
    btnMaleHref: "https://vipavenue.ru/sale/mens/",
    showPrices: true,
    femaleIds: "",
    maleIds: ""
  };
  let akciaForm = { ...defaultAkciaForm };

  const defaultNewCollForm = {
    bannerImageUrl: "https://email-files.vipavenue.ru/newsletter_2025/banner_newk/4.jpg",
    btnFemaleText: "Подборка для нее",
    btnFemaleHref: "https://vipavenue.ru/promo/womens/1346414/",
    btnMaleText: "Подборка для него",
    btnMaleHref: "https://vipavenue.ru/promo/mens/1346415/",
    showPrices: true,
    femaleIds: "",
    maleIds: ""
  };
  let newCollForm = { ...defaultNewCollForm };

  const HTML_PUG_SNIPPET = "<vk-snippet-end/>";
  const defaultHtmlPugForm = {
    gender: "female",
    preheaderText: "Премиальные шубы и дубленки",
    contentPath: "let/let",
    includeSnippet: true
  };
  let htmlPugForm = { ...defaultHtmlPugForm };
  let htmlCopyStatus = "";
  let saveStatus = "";
  let saveInFlight = false;
  let savePending = null;
  let autoSaveTimer = null;
  let saveStatusTimer = null;
  let autoSaveIndicator = false;
  let autoSaveIndicatorTimer = null;
  let htmlIndexCopyStatus = "";
  let htmlIndexCopyTimer = null;

  let savedFemaleOrder = null;

  const ID_QUEUE_INPUT_KEY = "vip_letter_editor_id_pool_input_v1";
  const ID_QUEUE_STATE_KEY = "vip_letter_editor_id_pool_state_v1";

  let idQueueInput = "";
  let idQueue = [];

  const cloneBlocks = (list) =>
    list.map((b) => ({
      ...b,
      data: b.data ? { ...b.data } : b.data
    }));

  const blockGroups = [
    {
      label: "Заголовки и текст",
      options: [
        { value: "titleActual", label: "АКТУАЛЬНЫЙ заголовок" },
        { value: "paragraph", label: "Текстовый блок" }
      ]
    },
    {
      label: "Кнопки",
      options: [
        { value: "buttonSingle", label: "Кнопка по центру" },
        { value: "buttonDouble", label: "Две кнопки" },
        { value: "buttonTriple", label: "Три кнопки" }
      ]
    },
    {
      label: "Баннеры",
      options: [
        { value: "banner", label: "Баннер с ссылкой" },
        { value: "bannerNoLink", label: "Баннер без ссылки (mixin)" },
        { value: "twoBannersWithText", label: "Два баннера с текстом" },
        { value: "twoBannersNoText", label: "Два баннера без текста" },
        { value: "threeBannersNoText", label: "Три баннера без текста" }
      ]
    },
    {
      label: "Товары",
      options: [
        { value: "products4Row", label: "4 товара в ряд" },
        { value: "products3Row", label: "3 товара в ряд" },
        { value: "productsImageLeft", label: "Товары + картинка слева" },
        { value: "productsImageRight", label: "Товары + картинка справа" },
        { value: "productsImageLeft3", label: "3 товара + картинка слева" },
        { value: "productsImageRight3", label: "3 товара + картинка справа" }
      ]
    },
    {
      label: "Текст + картинка",
      options: [
        { value: "textImageLeft", label: "Текст справа, картинка слева" },
        { value: "textImageRight", label: "Текст слева, картинка справа" }
      ]
    },
    {
      label: "Сервисные блоки",
      options: [
        { value: "sizeGrid", label: "Размерная сетка" },
        { value: "promocode", label: "Промокод" },
        { value: "spacer", label: "Отступ" },
        { value: "dividerVA", label: "Разделитель" }
      ]
    }
  ];

  const PRODUCT_ID_BLOCKS = {
    products4Row: { field: "productIds", count: 4 },
    products3Row: { field: "productIds", count: 3 },
    productsImageLeft: { field: "productIds", count: 4 },
    productsImageRight: { field: "productIds", count: 4 },
    productsImageLeft3: { field: "productIds", count: 3 },
    productsImageRight3: { field: "productIds", count: 3 }
  };
  $: setBodyTheme($theme);

  const presetBlocks = (type, data) => {
    const base = createDefaultBlock(type);
    return { ...base, data: { ...base.data, ...data } };
  };

  const presets = [
    {
      key: "novinki",
      title: "Новинки",
      description: "Две версии: женская и мужская. Вводишь ID (16 жен, 16 муж), опционально цены."
    },
    {
      key: "akcia",
      title: "Акция",
      description: "Баннер + 2 блока товаров по 16 ID, разделитель и кнопки. Женская и мужская версии."
    },
    {
      key: "newCollections",
      title: "Новые коллекции",
      description: "Баннер + 2 блока товаров по 16 ID, разделитель и кнопки. Женская и мужская версии."
    }
  ];

  function setBodyTheme(value) {
    if (typeof document === "undefined") return;
    document.body.classList.toggle("theme-dark", value === "dark");
    document.body.classList.toggle("theme-light", value === "light");
  }

  function createDefaultBlock(type) {
    const id = uuidv4();
    switch (type) {
      case "spacer":
        return { id, type, segment: "common", data: { height: 40 } };
      case "titleActual":
        return { id, type, segment: "common", data: { text: "", bottomSpacing: 20, removeBottomSpacing: false } };
      case "paragraph":
        return { id, type, segment: "common", data: { text: "", bottomSpacing: 40, removeBottomSpacing: false } };
      case "buttonSingle":
        return {
          id,
          type,
          segment: "common",
          data: {
            text: "женская коллекция",
            href: "",
            width: 340,
            height: 45,
            bgColor: "#242424",
            fontSize: 16,
            textColor: "#ffffff",
            bottomSpacing: 40,
            removeBottomSpacing: false
          }
        };
      case "buttonDouble":
        return {
          id,
          type,
          segment: "common",
          data: {
            leftText: "подарки для нее",
            leftHref: "",
            rightText: "подарки для него",
            rightHref: "",
            width: 275,
            height: 45,
            bgColor: "#242424",
            fontSize: 16,
            textColor: "#ffffff",
            gap: 20,
            bottomSpacing: 40,
            removeBottomSpacing: false
          }
        };
      case "buttonTriple":
        return {
          id,
          type,
          segment: "common",
          data: {
            leftText: "ЖЕНСКОЕ",
            leftHref: "",
            centerText: "МУЖСКОЕ",
            centerHref: "",
            rightText: "АКСЕССУАРЫ",
            rightHref: "",
            width: 174,
            height: 45,
            bgColor: "#242424",
            fontSize: 16,
            textColor: "#ffffff",
            gap: 24,
            bottomSpacing: 40,
            removeBottomSpacing: false
          }
        };
      case "banner":
        return {
          id,
          type,
          segment: "common",
          data: {
            href: "",
            imageBaseName: "",
            imageExtension: ".png",
            width: 570,
            bottomSpacing: 40,
            removeBottomSpacing: false
          }
        };
      case "bannerNoLink":
        return {
          id,
          type,
          segment: "common",
          data: {
            imageBaseName: "",
            imageExtension: ".png",
            imageUrl: "",
            height: 293,
            topSpacing: 40,
            bottomSpacing: 0,
            removeBottomSpacing: false
          }
        };
      case "twoBannersWithText":
        return {
          id,
          type,
          segment: "common",
          data: {
            leftHref: "",
            leftImage: "",
            leftImageBaseName: "",
            leftImageExtension: ".png",
            leftText: "Куртки для неё",
            rightHref: "",
            rightImage: "",
            rightImageBaseName: "",
            rightImageExtension: ".png",
            rightText: "Куртки для него",
            width: 270,
            gap: 30,
            bottomSpacing: 40,
            removeBottomSpacing: false
          }
        };
      case "twoBannersNoText":
        return {
          id,
          type,
          segment: "common",
          data: {
            leftHref: "",
            leftImage: "",
            leftImageBaseName: "",
            leftImageExtension: ".png",
            rightHref: "",
            rightImage: "",
            rightImageBaseName: "",
            rightImageExtension: ".png",
            width: 270,
            gap: 30,
            bottomSpacing: 40,
            removeBottomSpacing: false
          }
        };
      case "threeBannersNoText":
        return {
          id,
          type,
          segment: "common",
          data: {
            href1: "",
            img1: "",
            imgBaseName1: "",
            imgExtension1: ".png",
            href2: "",
            img2: "",
            imgBaseName2: "",
            imgExtension2: ".png",
            href3: "",
            img3: "",
            imgBaseName3: "",
            imgExtension3: ".png",
            width: 170,
            gap: 30,
            bottomSpacing: 40,
            removeBottomSpacing: false
          }
        };
      case "products4Row":
        return {
          id,
          type,
          segment: "common",
          data: {
            productIds: "",
            showPrices: true,
            bottomSpacing: 40,
            removeBottomSpacing: false
          }
        };
      case "products3Row":
        return {
          id,
          type,
          segment: "common",
          data: {
            productIds: "",
            showPrices: true,
            bottomSpacing: 40,
            removeBottomSpacing: false
          }
        };
      case "productsImageLeft":
        return {
          id,
          type,
          segment: "common",
          data: {
            productIds: "",
            link: "",
            imageUrl: "",
            imageBaseName: "",
            imageExtension: ".png",
            imgWidth: 275,
            showPrices: true,
            bottomSpacing: 40,
            removeBottomSpacing: false
          }
        };
      case "productsImageRight":
        return {
          id,
          type,
          segment: "common",
          data: {
            productIds: "",
            link: "",
            imageUrl: "",
            imageBaseName: "",
            imageExtension: ".png",
            imgWidth: 275,
            showPrices: true,
            bottomSpacing: 40,
            removeBottomSpacing: false
          }
        };
      case "productsImageLeft3":
        return {
          id,
          type,
          segment: "common",
          data: {
            productIds: "",
            link: "",
            imageUrl: "",
            imageBaseName: "",
            imageExtension: ".png",
            imgWidth: 275,
            showPrices: true,
            bottomSpacing: 40,
            removeBottomSpacing: false
          }
        };
      case "productsImageRight3":
        return {
          id,
          type,
          segment: "common",
          data: {
            productIds: "",
            link: "",
            imageUrl: "",
            imageBaseName: "",
            imageExtension: ".png",
            imgWidth: 275,
            showPrices: true,
            bottomSpacing: 40,
            removeBottomSpacing: false
          }
        };
      case "textImageLeft":
        return {
          id,
          type,
          segment: "common",
          data: {
            link: "",
            imageUrl: "",
            imageBaseName: "",
            imageExtension: ".png",
            imgWidth: 264,
            imgHeight: 330,
            header: "LOREM IPSUM DOLOR",
            text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit",
            buttonText: "ДЛЯ НЕГО",
            buttonHref: "",
            bottomSpacing: 20,
            removeBottomSpacing: false
          }
        };
      case "textImageRight":
        return {
          id,
          type,
          segment: "common",
          data: {
            link: "",
            imageUrl: "",
            imageBaseName: "",
            imageExtension: ".png",
            imgWidth: 264,
            imgHeight: 330,
            header: "LOREM IPSUM DOLOR",
            text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit",
            buttonText: "ДЛЯ НЕГО",
            buttonHref: "",
            bottomSpacing: 20,
            removeBottomSpacing: false
          }
        };
      case "sizeGrid":
        return {
          id,
          type,
          segment: "common",
          data: {
            sizes: "46,48,50,52,54,56,58,60",
            links: "",
            bottomSpacing: 20,
            removeBottomSpacing: false
          }
        };
      case "promocode":
        return {
          id,
          type,
          segment: "common",
          data: {
            code: "VIPAVENUE",
            bottomSpacing: 40,
            removeBottomSpacing: false
          }
        };
      case "dividerVA":
        return {
          id,
          type,
          segment: "common",
          data: {
            width: 300,
            height: 1,
            swapCenter: false,
            bottomSpacing: 40,
            removeBottomSpacing: false
          }
        };
      default:
        return { id, type, segment: "common", data: {} };
    }
  }

  const IMAGE_FIELDS = {
    banner: [{ base: "imageBaseName", direct: "imageUrl" }],
    bannerNoLink: [{ base: "imageBaseName", direct: "imageUrl" }],
    twoBannersWithText: [
      { base: "leftImageBaseName", direct: "leftImage" },
      { base: "rightImageBaseName", direct: "rightImage" }
    ],
    twoBannersNoText: [
      { base: "leftImageBaseName", direct: "leftImage" },
      { base: "rightImageBaseName", direct: "rightImage" }
    ],
    threeBannersNoText: [
      { base: "imgBaseName1", direct: "img1" },
      { base: "imgBaseName2", direct: "img2" },
      { base: "imgBaseName3", direct: "img3" }
    ],
    productsImageLeft: [{ base: "imageBaseName", direct: "imageUrl" }],
    productsImageRight: [{ base: "imageBaseName", direct: "imageUrl" }],
    productsImageLeft3: [{ base: "imageBaseName", direct: "imageUrl" }],
    productsImageRight3: [{ base: "imageBaseName", direct: "imageUrl" }],
    textImageLeft: [{ base: "imageBaseName", direct: "imageUrl" }],
    textImageRight: [{ base: "imageBaseName", direct: "imageUrl" }]
  };

  function applyAutoImageNames(list) {
    let counter = 1;
    let changed = false;

    const next = list.map((block) => {
      const slots = IMAGE_FIELDS[block.type];
      if (!slots?.length) {
        return block;
      }

      const data = { ...block.data };
      let localChanged = false;

      const hasValue = (value) =>
        value !== undefined && value !== null && String(value).trim() !== "";

      for (const config of slots) {
        const fieldConfig = typeof config === "string" ? { base: config } : config;
        const baseField = fieldConfig.base;
        const directField = fieldConfig.direct;
        const baseValue = data[baseField];
        const directValue = directField ? data[directField] : undefined;

        if (!hasValue(baseValue) && !hasValue(directValue)) {
          data[baseField] = String(counter);
          localChanged = true;
        }
        counter += 1;
      }

      if (localChanged) {
        changed = true;
        return { ...block, data };
      }
      return block;
    });

    return changed ? next : list;
  }

  const setBlocksAuto = (list) => blocks.set(applyAutoImageNames(list));
  const updateBlocksAuto = (fn) =>
    blocks.update((arr) => {
      const next = fn(arr);
      return applyAutoImageNames(next);
    });

  const parseIdQueueInput = (value) =>
    value
      .split(/[\n\r,\s]+/)
      .map((s) => s.trim())
      .filter(Boolean);

  const persistIdQueueInput = (value) => {
    if (typeof localStorage === "undefined") return;
    try {
      localStorage.setItem(ID_QUEUE_INPUT_KEY, value);
    } catch (e) {
      console.warn("Failed to save id queue input", e);
    }
  };

  const persistIdQueueState = (queue) => {
    if (typeof localStorage === "undefined") return;
    try {
      localStorage.setItem(ID_QUEUE_STATE_KEY, JSON.stringify(queue));
    } catch (e) {
      console.warn("Failed to save id queue state", e);
    }
  };

  function setIdQueueFromList(list) {
    idQueue = list.map((value) => ({ value, usedBy: null }));
    persistIdQueueState(idQueue);
  }

  function applyIdQueueFromInput() {
    const parsed = parseIdQueueInput(idQueueInput);
    setIdQueueFromList(parsed);
  }

  function ensureIdQueueReady() {
    if (idQueue.length === 0 && idQueueInput.trim()) {
      applyIdQueueFromInput();
    }
  }

  function clearIdQueueAll() {
    idQueueInput = "";
    idQueue = [];
    persistIdQueueInput("");
    persistIdQueueState(idQueue);
  }

  function onIdQueueInputChange(value) {
    idQueueInput = value;
    persistIdQueueInput(value);
  }

  function updateIdEntry(index, value) {
    const trimmed = value.trim();
    const next = [...idQueue];
    if (!trimmed) {
      next.splice(index, 1);
    } else {
      const prev = next[index] || { usedBy: null };
      next[index] = { ...prev, value: trimmed };
    }
    idQueue = next;
    persistIdQueueState(idQueue);
    idQueueInput = next.map((item) => item.value).join(", ");
    persistIdQueueInput(idQueueInput);
  }

  function resetIdQueueUsage() {
    if (idQueue.length === 0) return;
    let changed = false;
    const next = idQueue.map((item) => {
      if (item.usedBy) {
        changed = true;
        return { ...item, usedBy: null };
      }
      return item;
    });
    if (changed) {
      idQueue = next;
      persistIdQueueState(idQueue);
    }
  }

  function releaseIdsForBlock(blockId) {
    if (!blockId || idQueue.length === 0) return;
    let changed = false;
    const next = idQueue.map((item) => {
      if (item.usedBy === blockId) {
        changed = true;
        return { ...item, usedBy: null };
      }
      return item;
    });
    if (changed) {
      idQueue = next;
      persistIdQueueState(idQueue);
    }
  }

  const getIdsForBlock = (blockId) =>
    idQueue.filter((item) => item.usedBy === blockId).map((item) => item.value);

  function takeIdsForBlock(blockId, count) {
    if (!blockId || count <= 0 || idQueue.length === 0) return [];
    const next = idQueue.slice();
    const values = [];
    for (let i = 0; i < next.length && values.length < count; i++) {
      if (!next[i].usedBy) {
        next[i] = { ...next[i], usedBy: blockId };
        values.push(next[i].value);
      }
    }
    if (values.length > 0) {
      idQueue = next;
      persistIdQueueState(idQueue);
    }
    return values;
  }

  function addBlock(type) {
    if (!type) return;
    pushHistory();
    const newBlock = createDefaultBlock(type);
    updateBlocksAuto((arr) => [...arr, newBlock]);
  }

  function updateBlock(id, patch) {
    pushHistory();
    updateBlocksAuto((arr) =>
      arr.map((b) => {
        if (b.id !== id) return b;
        if (!patch) return b;

        const { data, segment, ...rest } = patch;
        const next = { ...b };

        if (segment !== undefined) next.segment = segment;

        const dataPatch = data !== undefined ? data : rest;
        if (dataPatch && Object.keys(dataPatch).length > 0) {
          next.data = { ...b.data, ...dataPatch };
        }

        return next;
      })
    );
  }

  function removeBlock(id) {
    pushHistory();
    updateBlocksAuto((arr) => arr.filter((b) => b.id !== id));
  }

  function assembleVersion(target) {
    pushHistory();
    const arr = get(blocks);

    const buildVersion = (list, tgt) => {
      const dividerIndex = list.findIndex((b) => b.type === "dividerVA" && b.data?.swapCenter);
      if (dividerIndex !== -1) {
        const before = list.slice(0, dividerIndex);
        const divider = list[dividerIndex];
        const after = list.slice(dividerIndex + 1);

        const beforeCommons = [];
        const beforeSeg = [];
        before.forEach((b) => {
          const seg = b.segment || "common";
          if (seg === "common") beforeCommons.push(b);
          else beforeSeg.push(b);
        });

        const afterCommons = [];
        const afterSeg = [];
        after.forEach((b) => {
          const seg = b.segment || "common";
          if (seg === "common") afterCommons.push(b);
          else afterSeg.push(b);
        });

        if (tgt === "male") {
          return [...beforeCommons, ...afterSeg, divider, ...afterCommons, ...beforeSeg];
        }
        return [...beforeCommons, ...beforeSeg, divider, ...afterCommons, ...afterSeg];
      }

      const commonsPrefix = [];
      const commonsMid = [];
      const commonsSuffix = [];
      const segmentA = [];
      const segmentB = [];
      let firstSegType = null;
      let secondSegType = null;

      for (const b of list) {
        const seg = b.segment || "common";
        if (seg === "common") {
          if (!firstSegType) commonsPrefix.push(b);
          else if (!secondSegType) commonsMid.push(b);
          else commonsSuffix.push(b);
        } else {
          if (!firstSegType) {
            firstSegType = seg;
            segmentA.push(b);
          } else if (seg === firstSegType && !secondSegType) {
            segmentA.push(b);
          } else {
            if (!secondSegType) secondSegType = seg;
            segmentB.push(b);
          }
        }
      }

      if (!secondSegType) {
        return [...commonsPrefix, ...segmentA, ...commonsMid, ...segmentB, ...commonsSuffix];
      }

      const firstIsTarget = tgt === firstSegType;
      return firstIsTarget
        ? [...commonsPrefix, ...segmentA, ...commonsMid, ...segmentB, ...commonsSuffix]
        : [...commonsPrefix, ...segmentB, ...commonsMid, ...segmentA, ...commonsSuffix];
    };

    if (target === "male") {
      savedFemaleOrder = cloneBlocks(arr);
      setBlocksAuto(buildVersion(arr, "male"));
    } else {
      if (savedFemaleOrder) {
        setBlocksAuto(savedFemaleOrder);
        savedFemaleOrder = null;
      } else {
        setBlocksAuto(buildVersion(arr, "female"));
      }
    }

    currentVersion = target;
  }

  function reorderBlocks(newOrder) {
    // dndzone отдаёт новый отсортированный массив (с теми же объектами)
    pushHistory();
    setBlocksAuto(newOrder);
  }

  function applyBlocks(list, options = {}) {
    pushHistory();
    setBlocksAuto(list);
    if (options.showPrices !== undefined) {
      updateSettingsShowPrices(options.showPrices);
    }
  }

  const chunkToRows = (idsString) => {
    const ids = idsString
      .split(",")
      .map((s) => s.trim())
      .filter(Boolean)
      .slice(0, 16);
    const rows = [];
    for (let i = 0; i < ids.length; i += 4) {
      rows.push(ids.slice(i, i + 4).join(","));
    }
    return rows;
  };

  const extractImageMeta = (value) => {
    if (!value) return { baseName: "", extension: ".png" };
    const trimmed = value.trim();
    if (!trimmed) return { baseName: "", extension: ".png" };
    const clean = trimmed.split(/[?#]/)[0];
    const filename = clean.split("/").pop() || clean;
    const dotIndex = filename.lastIndexOf(".");
    if (dotIndex === -1) {
      return { baseName: filename, extension: ".png" };
    }
    const baseName = filename.slice(0, dotIndex);
    const extension = filename.slice(dotIndex) || ".png";
    return { baseName, extension: extension.startsWith(".") ? extension : `.${extension}` };
  };

  const isDirectUrl = (value) => /^https?:\/\//i.test(value || "");

  function buildSizeGridBlock(sizesStr, linksStr) {
    return {
      id: uuidv4(),
      type: "sizeGrid",
      data: {
        sizes: sizesStr,
        links: linksStr,
        bottomSpacing: 40,
        removeBottomSpacing: false
      }
    };
  }

  function buildProductsRows(idsString, showPrices) {
    return chunkToRows(idsString).map((row) => ({
      id: uuidv4(),
      type: "products4Row",
      data: {
        productIds: row,
        showPrices: !!showPrices,
        bottomSpacing: 0,
        removeBottomSpacing: true
      }
    }));
  }

  function buildAkciaBlocks(order = "female") {
    const spacerBlock = (h) => ({ id: uuidv4(), type: "spacer", data: { height: h } });

    const bannerUrl = (akciaForm.bannerImageUrl || "").trim();
    const bannerMeta = extractImageMeta(bannerUrl);
    const isDirectBanner = isDirectUrl(bannerUrl);
    const banner = (href) => ({
      id: uuidv4(),
      type: "banner",
      data: {
        href,
        imageBaseName: isDirectBanner ? "" : bannerMeta.baseName,
        imageExtension: isDirectBanner ? bannerMeta.extension : bannerMeta.extension,
        imageUrl: isDirectBanner ? bannerUrl : "",
        bottomSpacing: 40,
        removeBottomSpacing: false
      }
    });

    const btn = (text, href) => ({
      id: uuidv4(),
      type: "buttonSingle",
      data: {
        text,
        href,
        width: 340,
        height: 45,
        bottomSpacing: 0,
        removeBottomSpacing: true
      }
    });

    const divider = {
      id: uuidv4(),
      type: "dividerVA",
      data: { width: 300, height: 1, bottomSpacing: 40, removeBottomSpacing: false }
    };

    const productsFemale = buildProductsRows(akciaForm.femaleIds, akciaForm.showPrices);
    const productsMale = buildProductsRows(akciaForm.maleIds, akciaForm.showPrices);

    const btnFemale = btn(akciaForm.btnFemaleText, akciaForm.btnFemaleHref);
    const btnMale = btn(akciaForm.btnMaleText, akciaForm.btnMaleHref);

    if (order === "female") {
      return [
        banner(akciaForm.btnFemaleHref),
        ...productsFemale,
        spacerBlock(40),
        btnFemale,
        divider,
        ...productsMale,
        spacerBlock(40),
        btnMale
      ];
    }

    return [
      banner(akciaForm.btnMaleHref),
      ...productsMale,
      spacerBlock(40),
      btnMale,
      divider,
      ...productsFemale,
      spacerBlock(40),
      btnFemale
    ];
  }

  function buildNewCollBlocks(order = "female") {
    const spacerBlock = (h) => ({ id: uuidv4(), type: "spacer", data: { height: h } });

    const bannerUrl = (newCollForm.bannerImageUrl || "").trim();
    const bannerMeta = extractImageMeta(bannerUrl);
    const isDirectBanner = isDirectUrl(bannerUrl);
    const banner = (href) => ({
      id: uuidv4(),
      type: "banner",
      data: {
        href,
        imageBaseName: isDirectBanner ? "" : bannerMeta.baseName,
        imageExtension: isDirectBanner ? bannerMeta.extension : bannerMeta.extension,
        imageUrl: isDirectBanner ? bannerUrl : "",
        bottomSpacing: 40,
        removeBottomSpacing: false
      }
    });

    const btn = (text, href) => ({
      id: uuidv4(),
      type: "buttonSingle",
      data: {
        text,
        href,
        width: 340,
        height: 45,
        bottomSpacing: 0,
        removeBottomSpacing: true
      }
    });

    const divider = {
      id: uuidv4(),
      type: "dividerVA",
      data: { width: 300, height: 1, bottomSpacing: 40, removeBottomSpacing: false }
    };

    const productsFemale = buildProductsRows(newCollForm.femaleIds, newCollForm.showPrices);
    const productsMale = buildProductsRows(newCollForm.maleIds, newCollForm.showPrices);

    const btnFemale = btn(newCollForm.btnFemaleText, newCollForm.btnFemaleHref);
    const btnMale = btn(newCollForm.btnMaleText, newCollForm.btnMaleHref);

    if (order === "female") {
      return [
        banner(newCollForm.btnFemaleHref),
        ...productsFemale,
        spacerBlock(40),
        btnFemale,
        divider,
        ...productsMale,
        spacerBlock(40),
        btnMale
      ];
    }

    return [
      banner(newCollForm.btnMaleHref),
      ...productsMale,
      spacerBlock(40),
      btnMale,
      divider,
      ...productsFemale,
      spacerBlock(40),
      btnFemale
    ];
  }

  function buildNovinkiBlocks(order = "female") {
    const spacer = (h) => ({
      id: uuidv4(),
      type: "spacer",
      data: { height: h }
    });

    const headerTitle = {
      id: uuidv4(),
      type: "titleActual",
      data: { text: novinkiForm.heading, bottomSpacing: 30, removeBottomSpacing: false }
    };

    const headerText = {
      id: uuidv4(),
      type: "paragraph",
      data: { text: novinkiForm.subheading, bottomSpacing: 30, removeBottomSpacing: false }
    };

    const buttonsTop = {
      id: uuidv4(),
      type: "buttonDouble",
      data: {
        leftText: novinkiForm.btnLeftText,
        leftHref: novinkiForm.btnLeftHref,
        rightText: novinkiForm.btnRightText,
        rightHref: novinkiForm.btnRightHref,
        width: 160,
        height: 45,
        gap: 25,
        bottomSpacing: 40,
        removeBottomSpacing: false
      }
    };

    const productsFemale = buildProductsRows(novinkiForm.femaleIds, novinkiForm.showPrices);
    const productsMale = buildProductsRows(novinkiForm.maleIds, novinkiForm.showPrices);

    const sizeFemale = buildSizeGridBlock(novinkiForm.sizeFemaleSizes, novinkiForm.sizeFemaleLinks);
    const sizeMale = buildSizeGridBlock(novinkiForm.sizeMaleSizes, novinkiForm.sizeMaleLinks);

    const btnMoreFemale = {
      id: uuidv4(),
      type: "buttonSingle",
      data: {
        text: novinkiForm.btnMoreFemaleText,
        href: novinkiForm.btnMoreFemaleHref,
        width: 300,
        height: 45,
        bottomSpacing: 0,
        removeBottomSpacing: true
      }
    };

    const btnMoreMale = {
      id: uuidv4(),
      type: "buttonSingle",
      data: {
        text: novinkiForm.btnMoreMaleText,
        href: novinkiForm.btnMoreMaleHref,
        width: 300,
        height: 45,
        bottomSpacing: 0,
        removeBottomSpacing: true
      }
    };

    const divider = {
      id: uuidv4(),
      type: "dividerVA",
      data: {
        width: novinkiForm.dividerWidth ?? 300,
        height: novinkiForm.dividerHeight ?? 1,
        bottomSpacing: 40,
        removeBottomSpacing: false
      }
    };

    if (order === "female") {
      return [
        headerTitle,
        headerText,
        buttonsTop,
        ...productsFemale,
        spacer(40),
        sizeFemale,
        btnMoreFemale,
        spacer(40),
        divider,
        ...productsMale,
        spacer(40),
        sizeMale,
        btnMoreMale
      ];
    }

    // order === "male": сначала мужские, потом женские
    return [
      headerTitle,
      headerText,
      buttonsTop,
      ...productsMale,
      spacer(40),
      sizeMale,
      btnMoreMale,
      spacer(40),
      divider,
      ...productsFemale,
      spacer(40),
      sizeFemale,
      btnMoreFemale
    ];
  }


  function updateSettingsImageBase(value) {
    settings.update((s) => ({ ...s, imageBaseUrl: value }));
  }

  function updateSettingsShowPrices(value) {
    settings.update((s) => ({ ...s, showPrices: value }));
  }

  function updateSettingsProjectName(value) {
    settings.update((s) => ({ ...s, projectName: value }));
  }

  function updateSettingsTemplateName(value) {
    settings.update((s) => ({ ...s, templateName: value }));
  }

  const sanitizePreheader = (value = "") =>
    value
      .replace(/<vk-snippet-end\s*\/>/gi, "")
      .replace(/\s+/g, " ")
      .trim();

  const normalizeContentPath = (value = "") =>
    value
      .replace(/^(\.\/)?letters\//i, "")
      .replace(/^\/+/, "")
      .trim();

  const escapePugString = (value = "") => value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');

  const buildHtmlPug = () => {
    const gender = htmlPugForm.gender === "male" ? "male" : "female";
    const headerInclude = gender === "female" ? "header-woman" : "header-man";
    const footerInclude = gender === "female" ? "footer-woman" : "footer-man";
    const preheaderText = sanitizePreheader(htmlPugForm.preheaderText);
    const contentPath = normalizeContentPath(htmlPugForm.contentPath) || defaultHtmlPugForm.contentPath;
    const includeSnippet = htmlPugForm.includeSnippet !== false;
    const snippetSuffix = includeSnippet ? ` ${HTML_PUG_SNIPPET}` : "";
    const preheaderValue = `${escapePugString(preheaderText)}${preheaderText ? snippetSuffix : ""}`;

    return [
      "extends layout/layout.pug",
      "//- Для задания класса основной таблицы в которой находится весь контент письма",
      "//- В данном случае в комментарии установлен класс для темного письма",
      "",
      "//- prepend wrapper ",
      "//-     -",
      '//-         var wrapperClass = "blackMainBackground"; ',
      "",
      "block header",
      `    include ./parts/header/${headerInclude}`,
      "block preheader",
      `    +preheader("${preheaderValue}")`,
      "block content",
      `    include ./letters/${contentPath}`,
      "block footer",
      `    include ./parts/footer/${footerInclude}`
    ].join("\n");
  };

  const formatPugCode = (code) =>
    code
      .split("\n")
      .map((line) => line.replace(/\t/g, "  ").replace(/\s+$/g, ""))
      .join("\n")
      .trim();

  const projectName = "aspekter";
  const templateName = "let.pug";
  const API_BASE = (import.meta.env?.VITE_API_BASE || "").replace(/\/+$/, "");
  $: displayedPug = formatEnabled ? formatPugCode($pugCode || "") : $pugCode;
  $: htmlPugReady = !!sanitizePreheader(htmlPugForm.preheaderText) && !!normalizeContentPath(htmlPugForm.contentPath);
  $: canSave = !!($pugCode || "").trim() && htmlPugReady;
  $: if (hasMounted) {
    htmlPugForm.preheaderText;
    htmlPugForm.contentPath;
    htmlPugForm.gender;
    htmlPugForm.includeSnippet;
    queueAutoSave();
  }
  let pugOutputEl;
  const resizePugOutput = (content = "") => {
    if (!pugOutputEl) return;
    pugOutputEl.style.height = "auto";
    const viewportHeight = window?.innerHeight || 900;
    const offset = 120;
    const minHeight = Math.max(260, viewportHeight - offset);
    const maxHeight = viewportHeight - offset;
    const nextHeight = Math.min(Math.max(pugOutputEl.scrollHeight, minHeight), maxHeight);
    pugOutputEl.style.height = `${nextHeight}px`;
  };
  $: resizePugOutput(displayedPug);

  const logAction = (message) => {
    const stamp = new Date();
    changeLog = [...changeLog.slice(-(LOG_LIMIT - 1)), { id: stamp.getTime(), message, time: stamp }];
  };

  function applyNoteTemplate(template) {
    const trimmed = notes.trim();
    notes = trimmed ? `${trimmed}\n${template} ` : `${template} `;
  }

  function copyPug() {
    if (!$pugCode) return;
    if (navigator.clipboard?.writeText) {
      navigator.clipboard.writeText($pugCode);
    } else {
      const textarea = document.createElement("textarea");
      textarea.value = $pugCode;
      document.body.appendChild(textarea);
      textarea.select();
      document.execCommand("copy");
      document.body.removeChild(textarea);
    }
    copyStatus = "Скопировано ✅";
    setTimeout(() => (copyStatus = ""), 1500);
    logAction("Скопирован код");
  }

  function copyHtmlPug() {
    const preheaderText = sanitizePreheader(htmlPugForm.preheaderText);
    const contentPath = normalizeContentPath(htmlPugForm.contentPath);
    if (!preheaderText || !contentPath) return;
    const htmlPugCode = buildHtmlPug();
    if (navigator.clipboard?.writeText) {
      navigator.clipboard.writeText(htmlPugCode);
    } else {
      const textarea = document.createElement("textarea");
      textarea.value = htmlPugCode;
      document.body.appendChild(textarea);
      textarea.select();
      document.execCommand("copy");
      document.body.removeChild(textarea);
    }
    htmlCopyStatus = "Скопировано ✅";
    setTimeout(() => (htmlCopyStatus = ""), 1500);
    logAction("Скопирован html.pug");
  }

  const setSaveStatus = (message) => {
    saveStatus = message;
    if (saveStatusTimer) clearTimeout(saveStatusTimer);
    saveStatusTimer = setTimeout(() => (saveStatus = ""), 2000);
  };

  const setHtmlIndexCopyStatus = (message) => {
    htmlIndexCopyStatus = message;
    if (htmlIndexCopyTimer) clearTimeout(htmlIndexCopyTimer);
    htmlIndexCopyTimer = setTimeout(() => (htmlIndexCopyStatus = ""), 2000);
  };

  const setAutoSaveIndicator = () => {
    autoSaveIndicator = true;
    if (autoSaveIndicatorTimer) clearTimeout(autoSaveIndicatorTimer);
    autoSaveIndicatorTimer = setTimeout(() => (autoSaveIndicator = false), 1500);
  };

  const buildApiUrl = (path) => (API_BASE ? `${API_BASE}${path}` : path);

  async function saveHtmlPugOnly({ silent = false } = {}) {
    if (saveInFlight) {
      savePending = "html";
      return;
    }
    const preheaderText = sanitizePreheader(htmlPugForm.preheaderText);
    const contentPath = normalizeContentPath(htmlPugForm.contentPath);
    if (!preheaderText || !contentPath) {
      if (!silent) setSaveStatus("Заполните прехедер и путь к письму.");
      return;
    }

    saveInFlight = true;
    try {
      const res = await fetch(buildApiUrl("/api/save-html"), {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          htmlPug: buildHtmlPug()
        })
      });
      if (!res.ok) {
        const text = await res.text();
        throw new Error(text || `HTTP ${res.status}`);
      }
      if (silent) {
        setAutoSaveIndicator();
      } else {
        setSaveStatus("html.pug сохранён ✅");
      }
      logAction("Сохранен html.pug");
    } catch (e) {
      console.warn("Failed to save html.pug", e);
      if (!silent) setSaveStatus("Ошибка сохранения ❌");
    } finally {
      saveInFlight = false;
      if (savePending) {
        const next = savePending;
        savePending = null;
        if (next === "all") {
          saveToEmailGen({ silent: true });
        } else {
          saveHtmlPugOnly({ silent: true });
        }
      }
    }
  }

  async function saveToEmailGen({ silent = false } = {}) {
    if (saveInFlight) {
      savePending = "all";
      return;
    }
    if (!($pugCode || "").trim()) {
      if (!silent) setSaveStatus("Нет Pug-кода для сохранения.");
      return;
    }
    const preheaderText = sanitizePreheader(htmlPugForm.preheaderText);
    const contentPath = normalizeContentPath(htmlPugForm.contentPath);
    if (!preheaderText || !contentPath) {
      if (!silent) setSaveStatus("Заполните прехедер и путь к письму.");
      return;
    }

    saveInFlight = true;
    try {
      const res = await fetch(buildApiUrl("/api/save"), {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          pugCode: $pugCode,
          htmlPug: buildHtmlPug(),
          contentPath
        })
      });
      if (!res.ok) {
        const text = await res.text();
        throw new Error(text || `HTTP ${res.status}`);
      }
      setSaveStatus(silent ? "Автосохранено ✅" : "Сохранено ✅");
      logAction("Сохранено в email-gen");
    } catch (e) {
      console.warn("Failed to save to email-gen", e);
      if (!silent) setSaveStatus("Ошибка сохранения ❌");
    } finally {
      saveInFlight = false;
      if (savePending) {
        const next = savePending;
        savePending = null;
        if (next === "all") {
          saveToEmailGen({ silent: true });
        } else {
          saveHtmlPugOnly({ silent: true });
        }
      }
    }
  }

  async function copyIndexHtml() {
    try {
      const res = await fetch(buildApiUrl("/api/html"));
      if (!res.ok) {
        const text = await res.text();
        throw new Error(text || `HTTP ${res.status}`);
      }
      const html = await res.text();
      if (navigator.clipboard?.writeText) {
        await navigator.clipboard.writeText(html);
      } else {
        const textarea = document.createElement("textarea");
        textarea.value = html;
        document.body.appendChild(textarea);
        textarea.select();
        document.execCommand("copy");
        document.body.removeChild(textarea);
      }
      setHtmlIndexCopyStatus("HTML скопирован ✅");
      logAction("Скопирован HTML");
    } catch (e) {
      console.warn("Failed to copy HTML", e);
      setHtmlIndexCopyStatus("Не удалось получить HTML");
    }
  }

  const queueAutoSave = () => {
    if (!hasMounted) return;
    if (autoSaveTimer) clearTimeout(autoSaveTimer);
    autoSaveTimer = setTimeout(() => {
      saveHtmlPugOnly({ silent: true });
    }, 800);
  };

  function exportPug() {
    if (!$pugCode.trim()) {
      alert("Нет Pug-кода для сохранения.");
      return;
    }
    const blob = new Blob([$pugCode], { type: "text/plain;charset=utf-8" });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    a.download = "letter.pug";
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    URL.revokeObjectURL(url);
    logAction("Экспортирован .pug");
  }

  function resetAll() {
    const ok = confirm("Точно сбросить все блоки и очистить код?");
    if (!ok) return;
    setBlocksAuto([]);
    copyStatus = "";
    logAction("Конструктор сброшен");
  }

  function distributeIdsAcrossBlocks() {
    distributingIds = true;
    ensureIdQueueReady();
    if (idQueue.length === 0) {
      distributingIds = false;
      alert("Добавьте ID в список слева, чтобы распределить их по блокам.");
      return;
    }
    const arr = get(blocks);
    if (!arr || arr.length === 0) {
      distributingIds = false;
      return;
    }

    let changed = false;

    const next = arr.map((block) => {
      const config = PRODUCT_ID_BLOCKS[block.type];
      if (!config) {
        releaseIdsForBlock(block.id);
        return block;
      }

      const assigned = getIdsForBlock(block.id);
      if (assigned.length === config.count) {
        const joined = assigned.join(",");
        const existing = (block.data?.[config.field] || "").trim();
        if (!existing) {
          changed = true;
          return {
            ...block,
            data: {
              ...block.data,
              [config.field]: joined
            }
          };
        }
        return block;
      }

      if (assigned.length > 0 && assigned.length !== config.count) {
        releaseIdsForBlock(block.id);
      }

      const fresh = takeIdsForBlock(block.id, config.count);
      if (!fresh.length) {
        return block;
      }

      changed = true;
      return {
        ...block,
        data: {
          ...block.data,
          [config.field]: fresh.join(",")
        }
      };
    });

  if (changed) {
    setBlocksAuto(next);
    logAction("Распределены ID по блокам");
  } else {
    idQueue = [...idQueue];
    persistIdQueueState(idQueue);
  }
  distributingIds = false;
}

  function saveCurrentAsPreset() {
    const name = prompt("Введите имя пресета");
    if (!name || !name.trim()) return;
    const preset = {
      id: uuidv4(),
      name: name.trim(),
      blocks: $blocks || [],
      settings: $settings || {}
    };
    customPresets = [...customPresets, preset];
    saveCustomPresets(customPresets);
    alert("Сохранено");
  }

  function loadCustomPreset(id) {
    const preset = customPresets.find((p) => p.id === id);
    if (!preset) return;
    applyBlocks(preset.blocks || []);
    if (preset.settings) {
      settings.set({ ...$settings, ...preset.settings });
    }
    activeTab = "builder";
  }

  function deleteCustomPreset(id) {
    const preset = customPresets.find((p) => p.id === id);
    if (!preset) return;
    const ok = confirm(`Удалить пресет "${preset.name}"?`);
    if (!ok) return;
    customPresets = customPresets.filter((p) => p.id !== id);
    saveCustomPresets(customPresets);
  }

  function resetNovinkiForm() {
    novinkiForm = { ...defaultNovinkiForm };
  }

  function resetAkciaForm() {
    akciaForm = { ...defaultAkciaForm };
  }

  function resetNewCollForm() {
    newCollForm = { ...defaultNewCollForm };
  }

  const loadCustomPresets = () => {
    try {
      const raw = localStorage.getItem(CUSTOM_PRESETS_KEY);
      if (!raw) return [];
      const parsed = JSON.parse(raw);
      return Array.isArray(parsed) ? parsed : [];
    } catch (e) {
      console.warn("Failed to load custom presets", e);
      return [];
    }
  };

  const saveCustomPresets = (list) => {
    try {
      localStorage.setItem(CUSTOM_PRESETS_KEY, JSON.stringify(list));
    } catch (e) {
      console.warn("Failed to save custom presets", e);
    }
  };

  let openPreset = "";
  let customPresets = loadCustomPresets();
  let collapseAll = null;
  let notes = "";
  let distributingIds = false;
  let changeLog = [];
  let formatEnabled = false;
  let colorizeTitles = false;
  let history = [];
  const HISTORY_LIMIT = 25;
  const COLOR_TITLES_KEY = "vip_letter_editor_color_titles";
  const LOG_LIMIT = 5;
  let hasMounted = false;

  onMount(() => {
    if (typeof localStorage !== "undefined") {
      const saved = localStorage.getItem(NOTES_KEY);
      if (saved !== null) {
        notes = saved;
      }
      const savedColors = localStorage.getItem(COLOR_TITLES_KEY);
      if (savedColors !== null) {
        colorizeTitles = savedColors === "1";
      }
      const savedIdInput = localStorage.getItem(ID_QUEUE_INPUT_KEY);
      if (savedIdInput !== null) {
        idQueueInput = savedIdInput;
      }
      const savedQueue = localStorage.getItem(ID_QUEUE_STATE_KEY);
      if (savedQueue) {
        try {
          const parsed = JSON.parse(savedQueue);
          if (Array.isArray(parsed)) {
            idQueue = parsed
              .map((item) =>
                typeof item?.value === "string"
                  ? { value: item.value, usedBy: item.usedBy || null }
                  : null
              )
              .filter(Boolean);
          }
        } catch (e) {
          console.warn("Failed to parse saved id queue", e);
        }
      }
      const savedNovinki = localStorage.getItem(NOVINKI_FORM_KEY);
      if (savedNovinki) {
        try {
          const parsedNovinki = JSON.parse(savedNovinki);
          if (parsedNovinki && typeof parsedNovinki === "object") {
            novinkiForm = { ...novinkiForm, ...parsedNovinki };
          }
        } catch (e) {
          console.warn("Failed to parse saved novinki preset", e);
        }
      }
      const savedAkcia = localStorage.getItem(AKCIA_FORM_KEY);
      if (savedAkcia) {
        try {
          const parsedAkcia = JSON.parse(savedAkcia);
          if (parsedAkcia && typeof parsedAkcia === "object") {
            akciaForm = { ...akciaForm, ...parsedAkcia };
          }
        } catch (e) {
          console.warn("Failed to parse saved akcia preset", e);
        }
      }
      const savedNewColl = localStorage.getItem(NEWCOLL_FORM_KEY);
      if (savedNewColl) {
        try {
          const parsedNewColl = JSON.parse(savedNewColl);
          if (parsedNewColl && typeof parsedNewColl === "object") {
            newCollForm = { ...newCollForm, ...parsedNewColl };
          }
        } catch (e) {
          console.warn("Failed to parse saved newColl preset", e);
        }
      }
      const savedHtmlPug = localStorage.getItem(HTML_PUG_FORM_KEY);
      if (savedHtmlPug) {
        try {
          const parsedHtmlPug = JSON.parse(savedHtmlPug);
          if (parsedHtmlPug && typeof parsedHtmlPug === "object") {
            htmlPugForm = { ...htmlPugForm, ...parsedHtmlPug };
          }
        } catch (e) {
          console.warn("Failed to parse saved html.pug form", e);
        }
      }
    }
    hasMounted = true;
    updateBlocksAuto((arr) => arr);
  });

  $: if (idQueue.length > 0) {
    const blockIds = new Set(($blocks || []).map((b) => b.id));
    let changed = false;
    const next = idQueue.map((item) => {
      if (item.usedBy && !blockIds.has(item.usedBy)) {
        changed = true;
        return { ...item, usedBy: null };
      }
      return item;
    });
    if (changed) {
      idQueue = next;
      persistIdQueueState(idQueue);
    }
  }

  $: idQueueUsedCount = idQueue.reduce((acc, item) => (item.usedBy ? acc + 1 : acc), 0);

  function onPresetToggle(key, event) {
    if (event?.currentTarget?.open) {
      openPreset = key;
    } else {
      openPreset = "";
    }
  }

  const openPresetDirect = (key) => {
    activeTab = "presets";
    openPreset = key;
  };

  const openPresetList = () => {
    activeTab = "presets";
    openPreset = "";
  };

  function toggleCollapseAll() {
    collapseAll = collapseAll === true ? false : true;
  }

  function pushHistory() {
    const snapshot = cloneBlocks(get(blocks));
    history = [...history.slice(-(HISTORY_LIMIT - 1)), snapshot];
  }

  function undoLast() {
    if (!history.length) return;
    const prev = history[history.length - 1];
    history = history.slice(0, -1);
    setBlocksAuto(cloneBlocks(prev));
  }

  $: if (typeof localStorage !== "undefined") {
    localStorage.setItem(NOTES_KEY, notes);
    localStorage.setItem(COLOR_TITLES_KEY, colorizeTitles ? "1" : "0");
  }

  $: if (hasMounted && typeof localStorage !== "undefined") {
    localStorage.setItem(NOVINKI_FORM_KEY, JSON.stringify(novinkiForm));
  }

  $: if (hasMounted && typeof localStorage !== "undefined") {
    localStorage.setItem(AKCIA_FORM_KEY, JSON.stringify(akciaForm));
  }

  $: if (hasMounted && typeof localStorage !== "undefined") {
    localStorage.setItem(NEWCOLL_FORM_KEY, JSON.stringify(newCollForm));
  }

  $: if (hasMounted && typeof localStorage !== "undefined") {
    localStorage.setItem(HTML_PUG_FORM_KEY, JSON.stringify(htmlPugForm));
  }

</script>

<div class="page">
  <aside class="panel panel-sidebar control-panel">
    <div class="control-panel-title">aspekter</div>
    <div class="meta-panel"></div>
    {#if activeTab === "builder"}
      <div class="preset-links preset-links-sidebar">
        <button class="text-link" on:click={() => openPresetDirect("novinki")}>Новинки</button>
        <button class="text-link" on:click={() => openPresetDirect("newCollections")}>Новые коллекции</button>
        <button class="text-link" on:click={() => openPresetDirect("akcia")}>Акции</button>
        <button class="text-link" on:click={openPresetList}>Все пресеты</button>
      </div>
    {/if}
    {#if activeTab === "builder"}
      <div class="sidebar-tabs">
      </div>
      <div class="sidebar-separator"></div>
      <div class="id-manager-panel">
        <div class="id-manager-header">
          <div>
            <div class="id-manager-title">Пул ID</div>
            <div class="id-manager-count">
              Использовано {idQueueUsedCount}/{idQueue.length}
            </div>
          </div>
          <div class="id-manager-actions">
            <button
              class="btn btn-secondary"
              on:click={applyIdQueueFromInput}
              title="Перечитать текст из поля и сбросить прогресс"
            >
              Распознать
            </button>
            <button
              class="btn"
              on:click={distributeIdsAcrossBlocks}
              disabled={idQueue.length === 0 || distributingIds}
              title="Заполнить новые товарные блоки из списка"
            >
              {#if distributingIds}
                <span class="dot-loader"></span> Работаем…
              {:else}
                Распределить
              {/if}
            </button>
            <button class="btn icon-square danger" title="Очистить весь список" on:click={clearIdQueueAll}>
              <svg class="icon" viewBox="0 0 16 16" aria-hidden="true">
                <path d="M5 5 11 11M11 5 5 11" />
              </svg>
            </button>
          </div>
        </div>
        <textarea
          class="id-manager-input"
          rows="3"
          placeholder="Вставьте ID через запятую: id1,id2,id3..."
          value={idQueueInput}
          on:input={(e) => onIdQueueInputChange(e.target.value)}
        ></textarea>
        {#if idQueue.length}
          <div class="id-manager-preview">
            {#each idQueue as entry, index}
              <input
                class="id-chip"
                class:used={!!entry.usedBy}
                class:free={!entry.usedBy}
                value={entry.value}
                size={Math.max(1, entry.value.length)}
                title={entry.usedBy ? "Занято" : "Свободно"}
                on:input={(e) => updateIdEntry(index, e.target.value)}
              />
            {/each}
          </div>
        {/if}
      </div>
      <details class="sidebar-group" open>
        <summary class="sidebar-title">html.pug</summary>
        <div class="preset-fields html-pug-fields">
          <label class="html-pug-label" for="html-pug-preheader">Прехедер</label>
          <div class="html-pug-row">
            <input
              id="html-pug-preheader"
              type="text"
              class="field-control html-pug-grow"
              placeholder="Премиальные шубы и дубленки"
              bind:value={htmlPugForm.preheaderText}
            />
            <button
              type="button"
              class="btn-inline vk-btn"
              class:active={htmlPugForm.includeSnippet}
              on:click={() => (htmlPugForm.includeSnippet = !htmlPugForm.includeSnippet)}
            >
              VK
            </button>
          </div>
          <label class="html-pug-label html-pug-path" for="html-pug-path">путь к письму</label>
          <input
            id="html-pug-path"
            type="text"
            class="field-control html-pug-path-input"
            placeholder="let/let"
            bind:value={htmlPugForm.contentPath}
          />
        </div>
        <div class="sidebar-row html-pug-actions">
          <div class="btn-inline-row html-pug-gender">
            <button
              type="button"
              class="btn-inline html-pug-gender-btn female"
              class:active={htmlPugForm.gender === "female"}
              on:click={() => (htmlPugForm.gender = "female")}
            >
              Жен
            </button>
            <button
              type="button"
              class="btn-inline html-pug-gender-btn male"
              class:active={htmlPugForm.gender === "male"}
              on:click={() => (htmlPugForm.gender = "male")}
            >
              Муж
            </button>
            <span class="autosave-dot" class:active={autoSaveIndicator} title="html.pug обновлён"></span>
          </div>
          <button
            class="btn icon-square btn-textual html-pug-copy"
            on:click={copyHtmlPug}
            disabled={!htmlPugReady}
            title="Скопировать html.pug"
            aria-label="Скопировать html.pug"
          >
            <svg class="icon" viewBox="0 0 16 16" aria-hidden="true">
              <rect x="5.1" y="3.2" width="7.2" height="9" rx="1.2" />
              <rect x="3.7" y="5" width="7.2" height="9" rx="1.2" />
            </svg>
            <span>html.pug</span>
          </button>
        </div>
        {#if htmlCopyStatus}
          <div class="copy-status">{htmlCopyStatus}</div>
        {/if}
        {#if saveStatus}
          <div class="save-status">{saveStatus}</div>
        {/if}
      </details>
      <div class="sidebar-separator"></div>
      <details class="sidebar-group">
        <summary class="sidebar-title">Заметки</summary>
        <textarea
          class="notes-area"
          placeholder="Черновики, идеи..."
          bind:value={notes}
          on:input={(e) => {
            const el = e.target;
            el.style.height = "auto";
            el.style.height = `${el.scrollHeight}px`;
          }}
        ></textarea>
      </details>
      <details class="sidebar-group change-log" open={changeLog.length > 0}>
        <summary class="sidebar-title">Последние действия</summary>
        {#if changeLog.length === 0}
          <div class="change-log-empty">Действия появятся во время работы.</div>
        {:else}
          <ul>
            {#each [...changeLog].reverse() as entry}
              <li>
                <span>{entry.message}</span>
                <time>{entry.time.toLocaleTimeString()}</time>
              </li>
            {/each}
          </ul>
        {/if}
      </details>
    {:else}
      <div class="sidebar-group">
        <button class="btn btn-primary" on:click={() => (activeTab = "builder")}>
          <svg class="icon" viewBox="0 0 16 16" aria-hidden="true">
            <path d="M10 3.5 5 8l5 4.5" />
            <path d="M5 8h7" />
          </svg>
          Вернуться в конструктор
        </button>
      </div>
    {/if}
  </aside>

  <div class="workspace">
    <header class="workspace-header">
      <div class="workspace-header-left">
        <div class="workspace-header-actions">
          <button
            class="btn icon-square triangle"
            on:click={toggleCollapseAll}
            title={collapseAll ? "Развернуть все" : "Свернуть все"}
          >
            <svg class="icon" viewBox="0 0 16 16" aria-hidden="true" style={`transform: rotate(${collapseAll ? 0 : 90}deg);`}>
              <path d="M6.2 4.5 10.2 8 6.2 11.5Z" />
            </svg>
          </button>
          <button
            class="btn icon-square"
            on:click={() => assembleVersion(currentVersion === "female" ? "male" : "female")}
            title={currentVersion === "female" ? "Собрать мужское" : "Собрать женское"}
          >
            <svg class="icon" viewBox="0 0 16 16" aria-hidden="true">
              <path d="M4.5 4h7" />
              <path d="M11.5 4v2.8" />
              <path d="M11.5 4 9.2 6.2" />
              <path d="M11.5 12h-7" />
              <path d="M4.5 12V9.2" />
              <path d="M4.5 12 6.8 9.8" />
            </svg>
          </button>
          <button
            class="btn icon-square"
            on:click={saveCurrentAsPreset}
            title="Сохранить как пресет"
          >
            <svg class="icon" viewBox="0 0 16 16" aria-hidden="true">
              <path d="M3 3.2h7.8l2.2 2.2v7.6H3Z" />
              <path d="M4.4 3.2h4.4v3H4.4Z" />
              <path d="M6 9.8h4" />
              <path d="M6 12h4" />
            </svg>
          </button>
          <button
            class="btn icon-square"
            aria-pressed={$settings.showPrices ?? true}
            on:click={() => updateSettingsShowPrices(!($settings.showPrices ?? true))}
            title="Показывать цены"
          >
            <svg class="icon" viewBox="0 0 16 16" aria-hidden="true">
              <path d="M5.5 3h4a2.5 2.5 0 0 1 0 5h-4" />
              <path d="M5.5 3v10" />
              <path d="M4 6.5h6" />
              <path d="M4 9h4" />
            </svg>
          </button>
          <button
            class="btn icon-square"
            aria-pressed={colorizeTitles}
            class:toggled={colorizeTitles}
            on:click={() => (colorizeTitles = !colorizeTitles)}
            title="Раскрасить названия блоков"
          >
            <svg class="icon" viewBox="0 0 16 16" aria-hidden="true">
              <path d="M7.2 9.8 11 3.2" />
              <path d="M6.2 9.8c-.9.6-1.4 1.6-1.4 2.6v1l1.1-.6c1.1-.6 1.7-1.6 1.7-2.7 0-.8-.9-1.3-1.4-1.3Z" />
            </svg>
          </button>
          <button
            class="btn icon-square"
            on:click={undoLast}
            title="Отменить последнее действие"
            disabled={!history.length}
          >
            <svg class="icon" viewBox="0 0 16 16" aria-hidden="true">
              <path d="M6.2 4 3 7.2l3.2 3.2" />
              <path d="M3 7.2h6.6a3.2 3.2 0 1 1 0 6.4H8" />
            </svg>
          </button>
        </div>
        <div class="image-base-field">
          <input
            type="text"
            class="image-base-input"
            placeholder="https://email-files.vipavenue.ru/newsletter_2025/..."
            value={$settings.imageBaseUrl || ""}
            on:input={(e) => updateSettingsImageBase(e.target.value)}
          />
        </div>
      </div>
      <div class="workspace-header-tools">
        <div class="pug-actions">
          <button
            id="copy-btn-top"
            class="btn icon-square btn-textual"
            on:click={copyPug}
          title="Скопировать код"
          aria-label="Скопировать код"
        >
          <svg class="icon" viewBox="0 0 16 16" aria-hidden="true">
            <rect x="5.1" y="3.2" width="7.2" height="9" rx="1.2" />
            <rect x="3.7" y="5" width="7.2" height="9" rx="1.2" />
          </svg>
          <span>Скопировать</span>
        </button>
          <button
            id="export-btn-top"
            class="btn icon-square btn-textual"
            on:click={exportPug}
            title="Экспорт .pug"
            aria-label="Экспорт .pug"
          >
            <svg class="icon" viewBox="0 0 16 16" aria-hidden="true">
              <path d="M8 2.5v7" />
              <path d="M5.5 7.8 8 10.3l2.5-2.5" />
              <path d="M4 12.5h8" />
            </svg>
            <span>Экспорт</span>
          </button>
          <button
            class="btn icon-square btn-textual"
            on:click={() => saveToEmailGen()}
            title="Сохранить в email-gen"
            aria-label="Сохранить в email-gen"
            disabled={!canSave || saveInFlight}
          >
            <svg class="icon" viewBox="0 0 16 16" aria-hidden="true">
              <path d="M3 3h8l2 2v8H3Z" />
              <path d="M5 3v4h5V3" />
              <path d="M5 10h6" />
            </svg>
            <span>Сохранить</span>
          </button>
          <button
            class="btn icon-square btn-textual"
            on:click={copyIndexHtml}
            title="Скопировать HTML"
            aria-label="Скопировать HTML"
          >
            <svg class="icon" viewBox="0 0 16 16" aria-hidden="true">
              <rect x="5.1" y="3.2" width="7.2" height="9" rx="1.2" />
              <rect x="3.7" y="5" width="7.2" height="9" rx="1.2" />
            </svg>
            <span>HTML</span>
          </button>
          <button
            class="btn icon-square btn-textual danger"
            on:click={resetAll}
            title="Сбросить конструктор"
            aria-label="Сбросить конструктор"
          >
            <svg class="icon" viewBox="0 0 16 16" aria-hidden="true">
              <path d="M5 5 11 11M11 5 5 11" />
            </svg>
            <span>Сброс</span>
          </button>
        </div>
        {#if htmlIndexCopyStatus}
          <div class="header-status">{htmlIndexCopyStatus}</div>
        {/if}
        <div class="theme-toggle">
          <div class="theme-toggle-inner">
            <label class="theme-switch" aria-label="Переключить тему">
              <input type="checkbox" checked={$theme === "dark"} on:change={(e) => theme.set(e.target.checked ? "dark" : "light")} />
              <span class="theme-slider"></span>
            </label>
          </div>
        </div>
      </div>
    </header>

    <main class="workspace-grid">
      <section class="panel panel-left">
        {#if activeTab === "builder"}
          <BlockList
            blocks={$blocks}
            onChange={updateBlock}
            onRemove={removeBlock}
            onReorder={reorderBlocks}
            collapseAll={collapseAll}
            onAdd={addBlock}
            blockGroups={blockGroups}
            colorizeTitles={colorizeTitles}
          />
        {:else}
          <h1>Пресеты</h1>
          <div class="preset-list">
            {#each presets as preset}
              {#if preset.key === "novinki"}
                <details
                  class="preset-card"
                  open={openPreset === "novinki"}
                  on:toggle={(e) => onPresetToggle("novinki", e)}
                >
                  <summary>
                    <div class="preset-title">Новинки</div>
                    <div class="preset-desc">Введите 16 женских и 16 мужских ID (4 ряда по 4). Выберите, показывать ли цены.</div>
                  </summary>

                  <div class="preset-fields">
                    <label class="inline">
                      <input type="checkbox" bind:checked={novinkiForm.showPrices} />
                      Показывать цены у всех товаров
                    </label>
                  </div>

                  <div class="preset-fields preset-row">
                    <label>Женские ID (16 шт, через запятую)
                      <textarea bind:value={novinkiForm.femaleIds} placeholder="id1,id2,id3,..." />
                    </label>
                    <label>Мужские ID (16 шт, через запятую)
                      <textarea bind:value={novinkiForm.maleIds} placeholder="id1,id2,id3,..." />
                    </label>
                  </div>

                  <details class="preset-advanced">
                    <summary>Редко используемые настройки</summary>
                    <div class="preset-fields">
                      <label>Заголовок
                        <input type="text" class="field-control" bind:value={novinkiForm.heading} />
                      </label>
                      <label>Подзаголовок
                        <textarea bind:value={novinkiForm.subheading}></textarea>
                      </label>
                      <label>Кнопка сверху — левый текст
                        <input type="text" class="field-control" bind:value={novinkiForm.btnLeftText} />
                      </label>
                      <label>Кнопка сверху — левый href
                        <input type="text" class="field-control" bind:value={novinkiForm.btnLeftHref} />
                      </label>
                      <label>Кнопка сверху — правый текст
                        <input type="text" class="field-control" bind:value={novinkiForm.btnRightText} />
                      </label>
                      <label>Кнопка сверху — правый href
                        <input type="text" class="field-control" bind:value={novinkiForm.btnRightHref} />
                      </label>

                      <label>Размерная сетка жен — размеры
                        <input type="text" class="field-control" bind:value={novinkiForm.sizeFemaleSizes} />
                      </label>
                      <label>Размерная сетка жен — ссылки
                        <textarea bind:value={novinkiForm.sizeFemaleLinks}></textarea>
                      </label>

                      <label>Размерная сетка муж — размеры
                        <input type="text" class="field-control" bind:value={novinkiForm.sizeMaleSizes} />
                      </label>
                      <label>Размерная сетка муж — ссылки
                        <textarea bind:value={novinkiForm.sizeMaleLinks}></textarea>
                      </label>

                      <label>Кнопка «Больше новинок для нее» — текст
                        <input type="text" class="field-control" bind:value={novinkiForm.btnMoreFemaleText} />
                      </label>
                      <label>Кнопка «Больше новинок для нее» — href
                        <input type="text" class="field-control" bind:value={novinkiForm.btnMoreFemaleHref} />
                      </label>
                      <label>Кнопка «Больше новинок для него» — текст
                        <input type="text" class="field-control" bind:value={novinkiForm.btnMoreMaleText} />
                      </label>
                      <label>Кнопка «Больше новинок для него» — href
                        <input type="text" class="field-control" bind:value={novinkiForm.btnMoreMaleHref} />
                      </label>

                      <label>Divider ширина (px)
                        <input type="number" class="field-control" bind:value={novinkiForm.dividerWidth} />
                      </label>
                      <label>Divider высота (px)
                        <input type="number" class="field-control" bind:value={novinkiForm.dividerHeight} />
                      </label>
                    </div>
                  </details>

                  <div class="toolbar">
                    <button class="btn" on:click={() => applyBlocks(buildNovinkiBlocks("female"), { showPrices: novinkiForm.showPrices })}>Женская версия</button>
                    <button class="btn" on:click={() => applyBlocks(buildNovinkiBlocks("male"), { showPrices: novinkiForm.showPrices })}>Мужская версия</button>
                    <button class="btn btn-secondary reset-btn" on:click={resetNovinkiForm}>Сбросить пресет</button>
                  </div>
                </details>
              {:else if preset.key === "akcia"}
                <details
                  class="preset-card"
                  open={openPreset === "akcia"}
                  on:toggle={(e) => onPresetToggle("akcia", e)}
                >
                  <summary>
                    <div class="preset-title">Акция</div>
                    <div class="preset-desc">Баннер, 2 блока товаров по 16 ID, разделитель и кнопки. Женская/мужская версии.</div>
                  </summary>

                  <div class="preset-fields">
                    <label>URL баннера
                      <input type="text" class="field-control" bind:value={akciaForm.bannerImageUrl} />
                    </label>
                    <label class="inline">
                      <input type="checkbox" bind:checked={akciaForm.showPrices} />
                      Показывать цены у всех товаров
                    </label>
                  </div>

                  <div class="preset-fields preset-row">
                    <label>Женские ID (16 шт, через запятую)
                      <textarea bind:value={akciaForm.femaleIds} placeholder="id1,id2,..." />
                    </label>
                    <label>Мужские ID (16 шт, через запятую)
                      <textarea bind:value={akciaForm.maleIds} placeholder="id1,id2,..." />
                    </label>
                  </div>

                  <details class="preset-advanced">
                    <summary>Редко используемые настройки</summary>
                    <div class="preset-fields">
                      <label>Кнопка жен — текст
                        <input type="text" class="field-control" bind:value={akciaForm.btnFemaleText} />
                      </label>
                      <label>Кнопка жен — href
                        <input type="text" class="field-control" bind:value={akciaForm.btnFemaleHref} />
                      </label>
                      <label>Кнопка муж — текст
                        <input type="text" class="field-control" bind:value={akciaForm.btnMaleText} />
                      </label>
                      <label>Кнопка муж — href
                        <input type="text" class="field-control" bind:value={akciaForm.btnMaleHref} />
                      </label>
                    </div>
                  </details>

                  <div class="toolbar">
                    <button class="btn" on:click={() => applyBlocks(buildAkciaBlocks("female"), { showPrices: akciaForm.showPrices })}>Женская версия</button>
                    <button class="btn" on:click={() => applyBlocks(buildAkciaBlocks("male"), { showPrices: akciaForm.showPrices })}>Мужская версия</button>
                    <button class="btn btn-secondary reset-btn" on:click={resetAkciaForm}>Сбросить пресет</button>
                  </div>
                </details>
              {:else if preset.key === "newCollections"}
                <details
                  class="preset-card"
                  open={openPreset === "newCollections"}
                  on:toggle={(e) => onPresetToggle("newCollections", e)}
                >
                  <summary>
                    <div class="preset-title">Новые коллекции</div>
                    <div class="preset-desc">Баннер, 2 блока товаров по 16 ID, разделитель и кнопки. Женская/мужская версии.</div>
                  </summary>

                  <div class="preset-fields">
                    <label>URL баннера
                      <input type="text" class="field-control" bind:value={newCollForm.bannerImageUrl} />
                    </label>
                    <label class="inline">
                      <input type="checkbox" bind:checked={newCollForm.showPrices} />
                      Показывать цены у всех товаров
                    </label>
                  </div>

                  <div class="preset-fields preset-row">
                    <label>Женские ID (16 шт, через запятую)
                      <textarea bind:value={newCollForm.femaleIds} placeholder="id1,id2,..." />
                    </label>
                    <label>Мужские ID (16 шт, через запятую)
                      <textarea bind:value={newCollForm.maleIds} placeholder="id1,id2,..." />
                    </label>
                  </div>

                  <details class="preset-advanced">
                    <summary>Редко используемые настройки</summary>
                    <div class="preset-fields">
                      <label>Кнопка жен — текст
                        <input type="text" class="field-control" bind:value={newCollForm.btnFemaleText} />
                      </label>
                      <label>Кнопка жен — href
                        <input type="text" class="field-control" bind:value={newCollForm.btnFemaleHref} />
                      </label>
                      <label>Кнопка муж — текст
                        <input type="text" class="field-control" bind:value={newCollForm.btnMaleText} />
                      </label>
                      <label>Кнопка муж — href
                        <input type="text" class="field-control" bind:value={newCollForm.btnMaleHref} />
                      </label>
                    </div>
                  </details>

                  <div class="toolbar">
                    <button class="btn" on:click={() => applyBlocks(buildNewCollBlocks("female"), { showPrices: newCollForm.showPrices })}>Женская версия</button>
                    <button class="btn" on:click={() => applyBlocks(buildNewCollBlocks("male"), { showPrices: newCollForm.showPrices })}>Мужская версия</button>
                    <button class="btn btn-secondary reset-btn" on:click={resetNewCollForm}>Сбросить пресет</button>
                  </div>
                </details>
              {/if}
            {/each}

            <details
              class="preset-card"
              open={openPreset === "custom"}
              on:toggle={(e) => onPresetToggle("custom", e)}
            >
              <summary>
                <div class="preset-title">Мои пресеты</div>
                <div class="preset-desc">Сохранённые состояния конструктора</div>
              </summary>
              {#if customPresets.length === 0}
                <div class="preset-desc">Сохраните пресет в конструкторе, чтобы он появился здесь.</div>
              {:else}
                <div class="custom-presets">
                  {#each customPresets as p}
                    <div class="custom-preset-row">
                      <div class="custom-preset-name">{p.name}</div>
                      <div class="custom-preset-actions">
                        <button class="btn" on:click={() => loadCustomPreset(p.id)}>Загрузить</button>
                        <button class="btn btn-secondary" on:click={() => deleteCustomPreset(p.id)}>Удалить</button>
                      </div>
                    </div>
                  {/each}
                </div>
              {/if}
            </details>
          </div>
        {/if}
      </section>

      <section class="panel panel-right">
        <textarea
          id="pug-output"
          readonly
          bind:this={pugOutputEl}
          value={displayedPug}
        ></textarea>
        {#if copyStatus}
          <div class="copy-status">{copyStatus}</div>
        {/if}
      </section>
    </main>

  </div>
</div>
