Beta-2.1 - The clearer, the faster #22
@@ -1,17 +1,20 @@
 | 
				
			|||||||
#-----------------------------------#
 | 
					 | 
				
			||||||
# Modern theme for Lumeex           #
 | 
					 | 
				
			||||||
# https://git.djeex.fr/Djeex/lumeex #
 | 
					 | 
				
			||||||
#-----------------------------------#
 | 
					 | 
				
			||||||
colors:
 | 
					colors:
 | 
				
			||||||
  primary: '#0065a1'
 | 
					  primary: '#0065A1'
 | 
				
			||||||
  primary_dark: '#005384'
 | 
					  primary_dark: '#005384'
 | 
				
			||||||
  secondary: '#00b0f0'
 | 
					  secondary: '#00B0F0'
 | 
				
			||||||
  accent: '#ffc700'
 | 
					  accent: '#FFC700'
 | 
				
			||||||
  text_dark: '#616161'
 | 
					  text_dark: '#616161'
 | 
				
			||||||
  background: '#ffffff'
 | 
					  background: '#FFFFFF'
 | 
				
			||||||
  browser_color: '#ffffff'
 | 
					  browser_color: '#FFFFFF'
 | 
				
			||||||
favicon:
 | 
					favicon:
 | 
				
			||||||
  path: favicon.png
 | 
					  path: favicon.png
 | 
				
			||||||
 | 
					fonts:
 | 
				
			||||||
 | 
					  primary:
 | 
				
			||||||
 | 
					    fallback: sans-serif
 | 
				
			||||||
 | 
					    name: Lato
 | 
				
			||||||
 | 
					  secondary:
 | 
				
			||||||
 | 
					    fallback: serif
 | 
				
			||||||
 | 
					    name: Montserrat
 | 
				
			||||||
google_fonts:
 | 
					google_fonts:
 | 
				
			||||||
- family: Lato
 | 
					- family: Lato
 | 
				
			||||||
  weights:
 | 
					  weights:
 | 
				
			||||||
@@ -23,10 +26,3 @@ google_fonts:
 | 
				
			|||||||
  - '200'
 | 
					  - '200'
 | 
				
			||||||
  - '400'
 | 
					  - '400'
 | 
				
			||||||
  - '700'
 | 
					  - '700'
 | 
				
			||||||
fonts:
 | 
					 | 
				
			||||||
  primary:
 | 
					 | 
				
			||||||
    name: Lato
 | 
					 | 
				
			||||||
    fallback: sans-serif
 | 
					 | 
				
			||||||
  secondary:
 | 
					 | 
				
			||||||
    name: Montserrat
 | 
					 | 
				
			||||||
    fallback: serif
 | 
					 | 
				
			||||||
@@ -1,21 +1,17 @@
 | 
				
			|||||||
#-----------------------------------#
 | 
					 | 
				
			||||||
# Typewriter theme for Lumeex       #
 | 
					 | 
				
			||||||
# https://git.djeex.fr/Djeex/lumeex #
 | 
					 | 
				
			||||||
#-----------------------------------#
 | 
					 | 
				
			||||||
colors:
 | 
					colors:
 | 
				
			||||||
  primary: '#0065a1'
 | 
					  accent: '#FFC700'
 | 
				
			||||||
 | 
					  background: '#FFFFFF'
 | 
				
			||||||
 | 
					  browser_color: '#FFFFFF'
 | 
				
			||||||
 | 
					  primary: '#0065A1'
 | 
				
			||||||
  primary_dark: '#005384'
 | 
					  primary_dark: '#005384'
 | 
				
			||||||
  secondary: '#00b0f0'
 | 
					  secondary: '#00B0F0'
 | 
				
			||||||
  accent: '#ffc700'
 | 
					 | 
				
			||||||
  text_dark: '#616161'
 | 
					  text_dark: '#616161'
 | 
				
			||||||
  background: '#ffffff'
 | 
					 | 
				
			||||||
  browser_color: '#ffffff'
 | 
					 | 
				
			||||||
favicon:
 | 
					favicon:
 | 
				
			||||||
  path: favicon.png
 | 
					  path: favicon.png
 | 
				
			||||||
fonts:
 | 
					fonts:
 | 
				
			||||||
  primary:
 | 
					  primary:
 | 
				
			||||||
    name: Trixie
 | 
					    name: trixie.woff
 | 
				
			||||||
    fallback: sans-serif
 | 
					    fallback: sans-serif
 | 
				
			||||||
  secondary:
 | 
					  secondary:
 | 
				
			||||||
    name: Trixie
 | 
					    name: trixie.woff
 | 
				
			||||||
    fallback: serif
 | 
					    fallback: serif
 | 
				
			||||||
@@ -26,14 +26,19 @@ function hideLoader() {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
document.addEventListener("DOMContentLoaded", () => {
 | 
					document.addEventListener("DOMContentLoaded", () => {
 | 
				
			||||||
  // Form and menu logic
 | 
					  // --- Section Forms ---
 | 
				
			||||||
  const form = document.getElementById("site-info-form");
 | 
					  const infoForm = document.getElementById("info-form");
 | 
				
			||||||
 | 
					  const socialForm = document.getElementById("social-form");
 | 
				
			||||||
 | 
					  const menuForm = document.getElementById("menu-form");
 | 
				
			||||||
 | 
					  const footerForm = document.getElementById("footer-form");
 | 
				
			||||||
 | 
					  const legalsForm = document.getElementById("legals-form");
 | 
				
			||||||
 | 
					  const buildForm = document.getElementById("build-form");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // --- Menu logic ---
 | 
				
			||||||
  const menuList = document.getElementById("menu-items-list");
 | 
					  const menuList = document.getElementById("menu-items-list");
 | 
				
			||||||
  const addMenuBtn = document.getElementById("add-menu-item");
 | 
					  const addMenuBtn = document.getElementById("add-menu-item");
 | 
				
			||||||
 | 
					 | 
				
			||||||
  let menuItems = [];
 | 
					  let menuItems = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Render menu items
 | 
					 | 
				
			||||||
  function renderMenuItems() {
 | 
					  function renderMenuItems() {
 | 
				
			||||||
    menuList.innerHTML = "";
 | 
					    menuList.innerHTML = "";
 | 
				
			||||||
    menuItems.forEach((item, idx) => {
 | 
					    menuItems.forEach((item, idx) => {
 | 
				
			||||||
@@ -50,7 +55,6 @@ document.addEventListener("DOMContentLoaded", () => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Update menu items from inputs
 | 
					 | 
				
			||||||
  function updateMenuItemsFromInputs() {
 | 
					  function updateMenuItemsFromInputs() {
 | 
				
			||||||
    const inputs = menuList.querySelectorAll("input");
 | 
					    const inputs = menuList.querySelectorAll("input");
 | 
				
			||||||
    const items = [];
 | 
					    const items = [];
 | 
				
			||||||
@@ -62,12 +66,11 @@ document.addEventListener("DOMContentLoaded", () => {
 | 
				
			|||||||
    menuItems = items;
 | 
					    menuItems = items;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Intellectual property paragraphs logic
 | 
					  // --- Intellectual property paragraphs logic ---
 | 
				
			||||||
  const ipList = document.getElementById("ip-list");
 | 
					  const ipList = document.getElementById("ip-list");
 | 
				
			||||||
  const addIpBtn = document.getElementById("add-ip-paragraph");
 | 
					  const addIpBtn = document.getElementById("add-ip-paragraph");
 | 
				
			||||||
  let ipParagraphs = [];
 | 
					  let ipParagraphs = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Render IP paragraphs
 | 
					 | 
				
			||||||
  function renderIpParagraphs() {
 | 
					  function renderIpParagraphs() {
 | 
				
			||||||
    ipList.innerHTML = "";
 | 
					    ipList.innerHTML = "";
 | 
				
			||||||
    ipParagraphs.forEach((item, idx) => {
 | 
					    ipParagraphs.forEach((item, idx) => {
 | 
				
			||||||
@@ -83,7 +86,6 @@ document.addEventListener("DOMContentLoaded", () => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Update IP paragraphs from textareas
 | 
					 | 
				
			||||||
  function updateIpParagraphsFromInputs() {
 | 
					  function updateIpParagraphsFromInputs() {
 | 
				
			||||||
    const textareas = ipList.querySelectorAll("textarea");
 | 
					    const textareas = ipList.querySelectorAll("textarea");
 | 
				
			||||||
    ipParagraphs = Array.from(textareas).map(textarea => ({
 | 
					    ipParagraphs = Array.from(textareas).map(textarea => ({
 | 
				
			||||||
@@ -91,27 +93,27 @@ document.addEventListener("DOMContentLoaded", () => {
 | 
				
			|||||||
    })).filter(item => item.paragraph !== "");
 | 
					    })).filter(item => item.paragraph !== "");
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Build options
 | 
					  // --- Build options ---
 | 
				
			||||||
  const convertImagesCheckbox = document.getElementById("convert-images-checkbox");
 | 
					  const convertImagesCheckbox = document.getElementById("convert-images-checkbox");
 | 
				
			||||||
  const resizeImagesCheckbox = document.getElementById("resize-images-checkbox");
 | 
					  const resizeImagesCheckbox = document.getElementById("resize-images-checkbox");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Theme select
 | 
					  // --- Theme select ---
 | 
				
			||||||
  const themeSelect = document.getElementById("theme-select");
 | 
					  const themeSelect = document.getElementById("theme-select");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Thumbnail upload and modal logic
 | 
					  // --- Thumbnail upload and modal logic ---
 | 
				
			||||||
  const thumbnailInput = form?.elements["social.thumbnail"];
 | 
					  const thumbnailInput = document.getElementById("social-thumbnail");
 | 
				
			||||||
  const thumbnailUpload = document.getElementById("thumbnail-upload");
 | 
					  const thumbnailUpload = document.getElementById("thumbnail-upload");
 | 
				
			||||||
  const chooseThumbnailBtn = document.getElementById("choose-thumbnail-btn");
 | 
					  const chooseThumbnailBtn = document.getElementById("choose-thumbnail-btn");
 | 
				
			||||||
  const thumbnailPreview = document.getElementById("thumbnail-preview");
 | 
					  const thumbnailPreview = document.getElementById("thumbnail-preview");
 | 
				
			||||||
  const removeThumbnailBtn = document.getElementById("remove-thumbnail-btn");
 | 
					  const removeThumbnailBtn = document.getElementById("remove-thumbnail-btn");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Modal elements for delete confirmation
 | 
					  // --- Modal elements for delete confirmation ---
 | 
				
			||||||
  const deleteModal = document.getElementById("delete-modal");
 | 
					  const deleteModal = document.getElementById("delete-modal");
 | 
				
			||||||
  const deleteModalClose = document.getElementById("delete-modal-close");
 | 
					  const deleteModalClose = document.getElementById("delete-modal-close");
 | 
				
			||||||
  const deleteModalConfirm = document.getElementById("delete-modal-confirm");
 | 
					  const deleteModalConfirm = document.getElementById("delete-modal-confirm");
 | 
				
			||||||
  const deleteModalCancel = document.getElementById("delete-modal-cancel");
 | 
					  const deleteModalCancel = document.getElementById("delete-modal-cancel");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Modal elements for theme deletion
 | 
					  // --- Modal elements for theme deletion ---
 | 
				
			||||||
  const deleteThemeModal = document.getElementById("delete-theme-modal");
 | 
					  const deleteThemeModal = document.getElementById("delete-theme-modal");
 | 
				
			||||||
  const deleteThemeModalClose = document.getElementById("delete-theme-modal-close");
 | 
					  const deleteThemeModalClose = document.getElementById("delete-theme-modal-close");
 | 
				
			||||||
  const deleteThemeModalConfirm = document.getElementById("delete-theme-modal-confirm");
 | 
					  const deleteThemeModalConfirm = document.getElementById("delete-theme-modal-confirm");
 | 
				
			||||||
@@ -119,7 +121,7 @@ document.addEventListener("DOMContentLoaded", () => {
 | 
				
			|||||||
  const deleteThemeModalText = document.getElementById("delete-theme-modal-text");
 | 
					  const deleteThemeModalText = document.getElementById("delete-theme-modal-text");
 | 
				
			||||||
  let themeToDelete = null;
 | 
					  let themeToDelete = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Show/hide thumbnail preview, remove button, and choose button
 | 
					  // --- Show/hide thumbnail preview, remove button, and choose button ---
 | 
				
			||||||
  function updateThumbnailPreview(src) {
 | 
					  function updateThumbnailPreview(src) {
 | 
				
			||||||
    if (thumbnailPreview) {
 | 
					    if (thumbnailPreview) {
 | 
				
			||||||
      thumbnailPreview.src = src || "";
 | 
					      thumbnailPreview.src = src || "";
 | 
				
			||||||
@@ -133,12 +135,12 @@ document.addEventListener("DOMContentLoaded", () => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Choose thumbnail button triggers file input
 | 
					  // --- Choose thumbnail button triggers file input ---
 | 
				
			||||||
  if (chooseThumbnailBtn && thumbnailUpload) {
 | 
					  if (chooseThumbnailBtn && thumbnailUpload) {
 | 
				
			||||||
    chooseThumbnailBtn.addEventListener("click", () => thumbnailUpload.click());
 | 
					    chooseThumbnailBtn.addEventListener("click", () => thumbnailUpload.click());
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Handle thumbnail upload and refresh preview (with cache busting)
 | 
					  // --- Handle thumbnail upload and refresh preview (with cache busting) ---
 | 
				
			||||||
  if (thumbnailUpload) {
 | 
					  if (thumbnailUpload) {
 | 
				
			||||||
    thumbnailUpload.addEventListener("change", async (e) => {
 | 
					    thumbnailUpload.addEventListener("change", async (e) => {
 | 
				
			||||||
      const file = e.target.files[0];
 | 
					      const file = e.target.files[0];
 | 
				
			||||||
@@ -156,17 +158,18 @@ document.addEventListener("DOMContentLoaded", () => {
 | 
				
			|||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        showToast("❌ Error uploading thumbnail", "error");
 | 
					        showToast("❌ Error uploading thumbnail", "error");
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					      updateSectionStatus("social");
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Remove thumbnail button triggers modal
 | 
					  // --- Remove thumbnail button triggers modal ---
 | 
				
			||||||
  if (removeThumbnailBtn) {
 | 
					  if (removeThumbnailBtn) {
 | 
				
			||||||
    removeThumbnailBtn.addEventListener("click", () => {
 | 
					    removeThumbnailBtn.addEventListener("click", () => {
 | 
				
			||||||
      deleteModal.style.display = "flex";
 | 
					      deleteModal.style.display = "flex";
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Modal logic for thumbnail deletion
 | 
					  // --- Modal logic for thumbnail deletion ---
 | 
				
			||||||
  if (deleteModal && deleteModalClose && deleteModalConfirm && deleteModalCancel) {
 | 
					  if (deleteModal && deleteModalClose && deleteModalConfirm && deleteModalCancel) {
 | 
				
			||||||
    deleteModalClose.onclick = deleteModalCancel.onclick = () => {
 | 
					    deleteModalClose.onclick = deleteModalCancel.onclick = () => {
 | 
				
			||||||
      deleteModal.style.display = "none";
 | 
					      deleteModal.style.display = "none";
 | 
				
			||||||
@@ -187,10 +190,11 @@ document.addEventListener("DOMContentLoaded", () => {
 | 
				
			|||||||
        showToast("❌ Error removing thumbnail", "error");
 | 
					        showToast("❌ Error removing thumbnail", "error");
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      deleteModal.style.display = "none";
 | 
					      deleteModal.style.display = "none";
 | 
				
			||||||
 | 
					      updateSectionStatus("social");
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Theme upload logic (custom theme folder)
 | 
					  // --- Theme upload logic (custom theme folder) ---
 | 
				
			||||||
  const themeUpload = document.getElementById("theme-upload");
 | 
					  const themeUpload = document.getElementById("theme-upload");
 | 
				
			||||||
  const chooseThemeBtn = document.getElementById("choose-theme-btn");
 | 
					  const chooseThemeBtn = document.getElementById("choose-theme-btn");
 | 
				
			||||||
  if (chooseThemeBtn && themeUpload) {
 | 
					  if (chooseThemeBtn && themeUpload) {
 | 
				
			||||||
@@ -223,10 +227,11 @@ document.addEventListener("DOMContentLoaded", () => {
 | 
				
			|||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        showToast("❌ Error uploading theme", "error");
 | 
					        showToast("❌ Error uploading theme", "error");
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					      updateSectionStatus("build");
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Remove theme button triggers modal
 | 
					  // --- Remove theme button triggers modal ---
 | 
				
			||||||
  const removeThemeBtn = document.getElementById("remove-theme-btn");
 | 
					  const removeThemeBtn = document.getElementById("remove-theme-btn");
 | 
				
			||||||
  if (removeThemeBtn && themeSelect) {
 | 
					  if (removeThemeBtn && themeSelect) {
 | 
				
			||||||
    removeThemeBtn.addEventListener("click", () => {
 | 
					    removeThemeBtn.addEventListener("click", () => {
 | 
				
			||||||
@@ -242,7 +247,7 @@ document.addEventListener("DOMContentLoaded", () => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Modal logic for theme deletion
 | 
					  // --- Modal logic for theme deletion ---
 | 
				
			||||||
  if (deleteThemeModal && deleteThemeModalClose && deleteThemeModalConfirm && deleteThemeModalCancel) {
 | 
					  if (deleteThemeModal && deleteThemeModalClose && deleteThemeModalConfirm && deleteThemeModalCancel) {
 | 
				
			||||||
    deleteThemeModalClose.onclick = deleteThemeModalCancel.onclick = () => {
 | 
					    deleteThemeModalClose.onclick = deleteThemeModalCancel.onclick = () => {
 | 
				
			||||||
      deleteThemeModal.style.display = "none";
 | 
					      deleteThemeModal.style.display = "none";
 | 
				
			||||||
@@ -283,10 +288,11 @@ document.addEventListener("DOMContentLoaded", () => {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
      deleteThemeModal.style.display = "none";
 | 
					      deleteThemeModal.style.display = "none";
 | 
				
			||||||
      themeToDelete = null;
 | 
					      themeToDelete = null;
 | 
				
			||||||
 | 
					      updateSectionStatus("build");
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Fetch theme list and populate select
 | 
					  // --- Fetch theme list and populate select ---
 | 
				
			||||||
  if (themeSelect) {
 | 
					  if (themeSelect) {
 | 
				
			||||||
    fetch("/api/themes")
 | 
					    fetch("/api/themes")
 | 
				
			||||||
      .then(res => res.json())
 | 
					      .then(res => res.json())
 | 
				
			||||||
@@ -307,139 +313,307 @@ document.addEventListener("DOMContentLoaded", () => {
 | 
				
			|||||||
      });
 | 
					      });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Load config from server and populate form
 | 
					  // --- Load config from server and populate forms ---
 | 
				
			||||||
  if (form) {
 | 
					  let loadedConfig = {};
 | 
				
			||||||
  fetch("/api/site-info")
 | 
					  fetch("/api/site-info")
 | 
				
			||||||
    .then(res => res.json())
 | 
					    .then(res => res.json())
 | 
				
			||||||
    .then(data => {
 | 
					    .then(data => {
 | 
				
			||||||
 | 
					      loadedConfig = data;
 | 
				
			||||||
 | 
					      // Info
 | 
				
			||||||
 | 
					      if (infoForm) {
 | 
				
			||||||
 | 
					        infoForm.elements["info.title"].value = data.info?.title || "";
 | 
				
			||||||
 | 
					        infoForm.elements["info.subtitle"].value = data.info?.subtitle || "";
 | 
				
			||||||
 | 
					        infoForm.elements["info.description"].value = data.info?.description || "";
 | 
				
			||||||
 | 
					        infoForm.elements["info.canonical"].value = data.info?.canonical || "";
 | 
				
			||||||
 | 
					        infoForm.elements["info.keywords"].value = Array.isArray(data.info?.keywords) ? data.info.keywords.join(", ") : (data.info?.keywords || "");
 | 
				
			||||||
 | 
					        infoForm.elements["info.author"].value = data.info?.author || "";
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      // Social
 | 
				
			||||||
 | 
					      if (socialForm) {
 | 
				
			||||||
 | 
					        socialForm.elements["social.instagram_url"].value = data.social?.instagram_url || "";
 | 
				
			||||||
 | 
					        if (thumbnailInput) thumbnailInput.value = data.social?.thumbnail || "";
 | 
				
			||||||
 | 
					        updateThumbnailPreview(data.social?.thumbnail ? `/photos/${data.social.thumbnail}?t=${Date.now()}` : "");
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      // Menu
 | 
				
			||||||
 | 
					      menuItems = Array.isArray(data.menu?.items) ? data.menu.items : [];
 | 
				
			||||||
 | 
					      renderMenuItems();
 | 
				
			||||||
 | 
					      // Footer
 | 
				
			||||||
 | 
					      if (footerForm) {
 | 
				
			||||||
 | 
					        footerForm.elements["footer.copyright"].value = data.footer?.copyright || "";
 | 
				
			||||||
 | 
					        footerForm.elements["footer.legal_label"].value = data.footer?.legal_label || "";
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      // Legals
 | 
				
			||||||
      ipParagraphs = Array.isArray(data.legals?.intellectual_property)
 | 
					      ipParagraphs = Array.isArray(data.legals?.intellectual_property)
 | 
				
			||||||
        ? data.legals.intellectual_property
 | 
					        ? data.legals.intellectual_property
 | 
				
			||||||
        : [];
 | 
					        : [];
 | 
				
			||||||
      renderIpParagraphs();
 | 
					      renderIpParagraphs();
 | 
				
			||||||
        menuItems = Array.isArray(data.menu?.items) ? data.menu.items : [];
 | 
					      if (legalsForm) {
 | 
				
			||||||
        renderMenuItems();
 | 
					        legalsForm.elements["legals.hoster_name"].value = data.legals?.hoster_name || "";
 | 
				
			||||||
        form.elements["info.title"].value = data.info?.title || "";
 | 
					        legalsForm.elements["legals.hoster_address"].value = data.legals?.hoster_address || "";
 | 
				
			||||||
        form.elements["info.subtitle"].value = data.info?.subtitle || "";
 | 
					        legalsForm.elements["legals.hoster_contact"].value = data.legals?.hoster_contact || "";
 | 
				
			||||||
        form.elements["info.description"].value = data.info?.description || "";
 | 
					      }
 | 
				
			||||||
        form.elements["info.canonical"].value = data.info?.canonical || "";
 | 
					      // Build
 | 
				
			||||||
        form.elements["info.keywords"].value = Array.isArray(data.info?.keywords) ? data.info.keywords.join(", ") : (data.info?.keywords || "");
 | 
					 | 
				
			||||||
        form.elements["info.author"].value = data.info?.author || "";
 | 
					 | 
				
			||||||
        form.elements["social.instagram_url"].value = data.social?.instagram_url || "";
 | 
					 | 
				
			||||||
        if (thumbnailInput) thumbnailInput.value = data.social?.thumbnail || "";
 | 
					 | 
				
			||||||
        updateThumbnailPreview(data.social?.thumbnail ? `/photos/${data.social.thumbnail}?t=${Date.now()}` : "");
 | 
					 | 
				
			||||||
        form.elements["footer.copyright"].value = data.footer?.copyright || "";
 | 
					 | 
				
			||||||
        form.elements["footer.legal_label"].value = data.footer?.legal_label || "";
 | 
					 | 
				
			||||||
      if (themeSelect) {
 | 
					      if (themeSelect) {
 | 
				
			||||||
        themeSelect.value = data.build?.theme || "";
 | 
					        themeSelect.value = data.build?.theme || "";
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
        form.elements["legals.hoster_name"].value = data.legals?.hoster_name || "";
 | 
					 | 
				
			||||||
        form.elements["legals.hoster_address"].value = data.legals?.hoster_address || "";
 | 
					 | 
				
			||||||
        form.elements["legals.hoster_contact"].value = data.legals?.hoster_contact || "";
 | 
					 | 
				
			||||||
        // Build checkboxes
 | 
					 | 
				
			||||||
      if (convertImagesCheckbox) {
 | 
					      if (convertImagesCheckbox) {
 | 
				
			||||||
        convertImagesCheckbox.checked = !!data.build?.convert_images;
 | 
					        convertImagesCheckbox.checked = !!data.build?.convert_images;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      if (resizeImagesCheckbox) {
 | 
					      if (resizeImagesCheckbox) {
 | 
				
			||||||
        resizeImagesCheckbox.checked = !!data.build?.resize_images;
 | 
					        resizeImagesCheckbox.checked = !!data.build?.resize_images;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					      // Initial status update
 | 
				
			||||||
 | 
					      ["info", "social", "menu", "footer", "legals", "build"].forEach(updateSectionStatus);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Add menu item
 | 
					  // --- Add menu item ---
 | 
				
			||||||
  if (addMenuBtn) {
 | 
					  if (addMenuBtn) {
 | 
				
			||||||
    addMenuBtn.addEventListener("click", () => {
 | 
					    addMenuBtn.addEventListener("click", () => {
 | 
				
			||||||
      menuItems.push({ label: "", href: "" });
 | 
					      menuItems.push({ label: "", href: "" });
 | 
				
			||||||
      renderMenuItems();
 | 
					      renderMenuItems();
 | 
				
			||||||
 | 
					      updateSectionStatus("menu");
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Remove menu item
 | 
					  // --- Remove menu item ---
 | 
				
			||||||
  menuList.addEventListener("click", (e) => {
 | 
					  menuList.addEventListener("click", (e) => {
 | 
				
			||||||
    if (e.target.classList.contains("remove-menu-item")) {
 | 
					    if (e.target.classList.contains("remove-menu-item")) {
 | 
				
			||||||
      const idx = parseInt(e.target.getAttribute("data-idx"));
 | 
					      const idx = parseInt(e.target.getAttribute("data-idx"));
 | 
				
			||||||
      menuItems.splice(idx, 1);
 | 
					      menuItems.splice(idx, 1);
 | 
				
			||||||
      renderMenuItems();
 | 
					      renderMenuItems();
 | 
				
			||||||
 | 
					      updateSectionStatus("menu");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Update menuItems on input change
 | 
					  // --- Update menuItems on input change ---
 | 
				
			||||||
  menuList.addEventListener("input", () => {
 | 
					  menuList.addEventListener("input", () => {
 | 
				
			||||||
    updateMenuItemsFromInputs();
 | 
					    updateMenuItemsFromInputs();
 | 
				
			||||||
 | 
					    updateSectionStatus("menu");
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Add paragraph
 | 
					  // --- Add paragraph ---
 | 
				
			||||||
  if (addIpBtn) {
 | 
					  if (addIpBtn) {
 | 
				
			||||||
    addIpBtn.addEventListener("click", () => {
 | 
					    addIpBtn.addEventListener("click", () => {
 | 
				
			||||||
      ipParagraphs.push({ paragraph: "" });
 | 
					      ipParagraphs.push({ paragraph: "" });
 | 
				
			||||||
      renderIpParagraphs();
 | 
					      renderIpParagraphs();
 | 
				
			||||||
 | 
					      updateSectionStatus("legals");
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Remove paragraph
 | 
					  // --- Remove paragraph ---
 | 
				
			||||||
  ipList.addEventListener("click", (e) => {
 | 
					  ipList.addEventListener("click", (e) => {
 | 
				
			||||||
    if (e.target.classList.contains("remove-ip-paragraph")) {
 | 
					    if (e.target.classList.contains("remove-ip-paragraph")) {
 | 
				
			||||||
      const idx = parseInt(e.target.getAttribute("data-idx"));
 | 
					      const idx = parseInt(e.target.getAttribute("data-idx"));
 | 
				
			||||||
      ipParagraphs.splice(idx, 1);
 | 
					      ipParagraphs.splice(idx, 1);
 | 
				
			||||||
      renderIpParagraphs();
 | 
					      renderIpParagraphs();
 | 
				
			||||||
 | 
					      updateSectionStatus("legals");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Update ipParagraphs on input change
 | 
					  // --- Update ipParagraphs on input change ---
 | 
				
			||||||
  ipList.addEventListener("input", () => {
 | 
					  ipList.addEventListener("input", () => {
 | 
				
			||||||
    updateIpParagraphsFromInputs();
 | 
					    updateIpParagraphsFromInputs();
 | 
				
			||||||
 | 
					    updateSectionStatus("legals");
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Save config to server
 | 
					  // --- Section value helpers ---
 | 
				
			||||||
  if (form) {
 | 
					  function getSectionValues(section) {
 | 
				
			||||||
    form.addEventListener("submit", async (e) => {
 | 
					    switch (section) {
 | 
				
			||||||
      e.preventDefault();
 | 
					      case "info":
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					          title: infoForm.elements["info.title"].value,
 | 
				
			||||||
 | 
					          subtitle: infoForm.elements["info.subtitle"].value,
 | 
				
			||||||
 | 
					          description: infoForm.elements["info.description"].value,
 | 
				
			||||||
 | 
					          canonical: infoForm.elements["info.canonical"].value,
 | 
				
			||||||
 | 
					          keywords: infoForm.elements["info.keywords"].value.split(",").map(i => i.trim()).filter(Boolean),
 | 
				
			||||||
 | 
					          author: infoForm.elements["info.author"].value
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					      case "social":
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					          instagram_url: socialForm.elements["social.instagram_url"].value,
 | 
				
			||||||
 | 
					          thumbnail: thumbnailInput ? thumbnailInput.value : ""
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					      case "menu":
 | 
				
			||||||
        updateMenuItemsFromInputs();
 | 
					        updateMenuItemsFromInputs();
 | 
				
			||||||
 | 
					        return { items: menuItems };
 | 
				
			||||||
 | 
					      case "footer":
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					          copyright: footerForm.elements["footer.copyright"].value,
 | 
				
			||||||
 | 
					          legal_label: footerForm.elements["footer.legal_label"].value
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					      case "legals":
 | 
				
			||||||
        updateIpParagraphsFromInputs();
 | 
					        updateIpParagraphsFromInputs();
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
      // Check if thumbnail is set before saving (uploaded or present in input)
 | 
					          hoster_name: legalsForm.elements["legals.hoster_name"].value,
 | 
				
			||||||
      if (!thumbnailInput || !thumbnailInput.value) {
 | 
					          hoster_address: legalsForm.elements["legals.hoster_address"].value,
 | 
				
			||||||
        showLoader("Saving...");
 | 
					          hoster_contact: legalsForm.elements["legals.hoster_contact"].value,
 | 
				
			||||||
        showToast("❌ Thumbnail is required.", "error");
 | 
					          intellectual_property: ipParagraphs
 | 
				
			||||||
        hideLoader();
 | 
					        };
 | 
				
			||||||
        return;
 | 
					      case "build":
 | 
				
			||||||
      }
 | 
					        return {
 | 
				
			||||||
 | 
					 | 
				
			||||||
      const build = {
 | 
					 | 
				
			||||||
          theme: themeSelect ? themeSelect.value : "",
 | 
					          theme: themeSelect ? themeSelect.value : "",
 | 
				
			||||||
          convert_images: !!(convertImagesCheckbox && convertImagesCheckbox.checked),
 | 
					          convert_images: !!(convertImagesCheckbox && convertImagesCheckbox.checked),
 | 
				
			||||||
          resize_images: !!(resizeImagesCheckbox && resizeImagesCheckbox.checked)
 | 
					          resize_images: !!(resizeImagesCheckbox && resizeImagesCheckbox.checked)
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					      default:
 | 
				
			||||||
      const payload = {
 | 
					        return {};
 | 
				
			||||||
        info: {
 | 
					 | 
				
			||||||
          title: form.elements["info.title"].value,
 | 
					 | 
				
			||||||
          subtitle: form.elements["info.subtitle"].value,
 | 
					 | 
				
			||||||
          description: form.elements["info.description"].value,
 | 
					 | 
				
			||||||
          canonical: form.elements["info.canonical"].value,
 | 
					 | 
				
			||||||
          keywords: form.elements["info.keywords"].value.split(",").map(i => i.trim()).filter(Boolean),
 | 
					 | 
				
			||||||
          author: form.elements["info.author"].value
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        social: {
 | 
					 | 
				
			||||||
          instagram_url: form.elements["social.instagram_url"].value,
 | 
					 | 
				
			||||||
          thumbnail: thumbnailInput ? thumbnailInput.value : ""
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        menu: {
 | 
					 | 
				
			||||||
          items: menuItems
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        footer: {
 | 
					 | 
				
			||||||
          copyright: form.elements["footer.copyright"].value,
 | 
					 | 
				
			||||||
          legal_label: form.elements["footer.legal_label"].value
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        build,
 | 
					 | 
				
			||||||
        legals: {
 | 
					 | 
				
			||||||
          hoster_name: form.elements["legals.hoster_name"].value,
 | 
					 | 
				
			||||||
          hoster_address: form.elements["legals.hoster_address"].value,
 | 
					 | 
				
			||||||
          hoster_contact: form.elements["legals.hoster_contact"].value,
 | 
					 | 
				
			||||||
          intellectual_property: ipParagraphs
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
      };
 | 
					  }
 | 
				
			||||||
      // --- REMOVE loader for save ---
 | 
					
 | 
				
			||||||
      // showLoader("Saving...");
 | 
					  function isSectionSaved(section) {
 | 
				
			||||||
 | 
					    const values = getSectionValues(section);
 | 
				
			||||||
 | 
					    const config = loadedConfig[section] || {};
 | 
				
			||||||
 | 
					    switch (section) {
 | 
				
			||||||
 | 
					      case "info":
 | 
				
			||||||
 | 
					        return Object.keys(values).every(
 | 
				
			||||||
 | 
					          key => values[key] && (
 | 
				
			||||||
 | 
					            key === "keywords"
 | 
				
			||||||
 | 
					              ? Array.isArray(config.keywords) && values.keywords.join(",") === config.keywords.join(",")
 | 
				
			||||||
 | 
					              : values[key] === (config[key] || "")
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					      case "social":
 | 
				
			||||||
 | 
					        return values.instagram_url && values.thumbnail &&
 | 
				
			||||||
 | 
					          values.instagram_url === (config.instagram_url || "") &&
 | 
				
			||||||
 | 
					          values.thumbnail === (config.thumbnail || "");
 | 
				
			||||||
 | 
					      case "menu":
 | 
				
			||||||
 | 
					        return JSON.stringify(values.items) === JSON.stringify(config.items || []);
 | 
				
			||||||
 | 
					      case "footer":
 | 
				
			||||||
 | 
					        return values.copyright && values.legal_label &&
 | 
				
			||||||
 | 
					          values.copyright === (config.copyright || "") &&
 | 
				
			||||||
 | 
					          values.legal_label === (config.legal_label || "");
 | 
				
			||||||
 | 
					      case "legals":
 | 
				
			||||||
 | 
					        return values.hoster_name && values.hoster_address && values.hoster_contact &&
 | 
				
			||||||
 | 
					          values.hoster_name === (config.hoster_name || "") &&
 | 
				
			||||||
 | 
					          values.hoster_address === (config.hoster_address || "") &&
 | 
				
			||||||
 | 
					          values.hoster_contact === (config.hoster_contact || "") &&
 | 
				
			||||||
 | 
					          JSON.stringify(values.intellectual_property) === JSON.stringify(config.intellectual_property || []);
 | 
				
			||||||
 | 
					      case "build":
 | 
				
			||||||
 | 
					        return values.theme === (config.theme || "") &&
 | 
				
			||||||
 | 
					          !!values.convert_images === !!config.convert_images &&
 | 
				
			||||||
 | 
					          !!values.resize_images === !!config.resize_images;
 | 
				
			||||||
 | 
					      default:
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function isSectionComplete(section) {
 | 
				
			||||||
 | 
					    const values = getSectionValues(section);
 | 
				
			||||||
 | 
					    switch (section) {
 | 
				
			||||||
 | 
					      case "info":
 | 
				
			||||||
 | 
					        return (
 | 
				
			||||||
 | 
					          values.title &&
 | 
				
			||||||
 | 
					          values.subtitle &&
 | 
				
			||||||
 | 
					          values.description &&
 | 
				
			||||||
 | 
					          values.canonical &&
 | 
				
			||||||
 | 
					          values.keywords.length > 0 &&
 | 
				
			||||||
 | 
					          values.author
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					      case "social":
 | 
				
			||||||
 | 
					        return values.instagram_url && values.thumbnail;
 | 
				
			||||||
 | 
					      case "menu":
 | 
				
			||||||
 | 
					        return Array.isArray(values.items) && values.items.every(item => item.label && item.href);
 | 
				
			||||||
 | 
					      case "footer":
 | 
				
			||||||
 | 
					        return values.copyright && values.legal_label;
 | 
				
			||||||
 | 
					      case "legals":
 | 
				
			||||||
 | 
					        return (
 | 
				
			||||||
 | 
					          values.hoster_name &&
 | 
				
			||||||
 | 
					          values.hoster_address &&
 | 
				
			||||||
 | 
					          values.hoster_contact &&
 | 
				
			||||||
 | 
					          Array.isArray(values.intellectual_property) &&
 | 
				
			||||||
 | 
					          values.intellectual_property.length > 0 &&
 | 
				
			||||||
 | 
					          values.intellectual_property.every(ip => ip.paragraph)
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					      case "build":
 | 
				
			||||||
 | 
					        return values.theme;
 | 
				
			||||||
 | 
					      default:
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function updateSectionStatus(section) {
 | 
				
			||||||
 | 
					    const statusEl = document.querySelector(`#${section}-section .section-status`);
 | 
				
			||||||
 | 
					    if (!statusEl) return;
 | 
				
			||||||
 | 
					  if (!isSectionComplete(section)) {
 | 
				
			||||||
 | 
					    statusEl.innerHTML = "⚠️ Section not yet saved. Please fill required fields";
 | 
				
			||||||
 | 
					    statusEl.style.color = "#ffc700";
 | 
				
			||||||
 | 
					    statusEl.style.display = "";
 | 
				
			||||||
 | 
					    statusEl.style.fontStyle = "normal";
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (isSectionSaved(section)) {
 | 
				
			||||||
 | 
					    statusEl.innerHTML = "";
 | 
				
			||||||
 | 
					    statusEl.style.display = "none";
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    statusEl.innerHTML = "⚠️ Section not yet saved";
 | 
				
			||||||
 | 
					    statusEl.style.color = "#ffc700";
 | 
				
			||||||
 | 
					    statusEl.style.display = "";
 | 
				
			||||||
 | 
					    statusEl.style.fontStyle = "normal";
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // --- Listen for changes in each section ---
 | 
				
			||||||
 | 
					  [
 | 
				
			||||||
 | 
					    { form: infoForm, section: "info" },
 | 
				
			||||||
 | 
					    { form: socialForm, section: "social" },
 | 
				
			||||||
 | 
					    { form: menuForm, section: "menu" },
 | 
				
			||||||
 | 
					    { form: footerForm, section: "footer" },
 | 
				
			||||||
 | 
					    { form: legalsForm, section: "legals" },
 | 
				
			||||||
 | 
					    { form: buildForm, section: "build" }
 | 
				
			||||||
 | 
					  ].forEach(({ form, section }) => {
 | 
				
			||||||
 | 
					    if (!form) return;
 | 
				
			||||||
 | 
					    form.addEventListener("input", () => updateSectionStatus(section));
 | 
				
			||||||
 | 
					    form.addEventListener("change", () => updateSectionStatus(section));
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // --- Save section handler (form submit) ---
 | 
				
			||||||
 | 
					  [
 | 
				
			||||||
 | 
					    { form: infoForm, section: "info" },
 | 
				
			||||||
 | 
					    { form: socialForm, section: "social" },
 | 
				
			||||||
 | 
					    { form: menuForm, section: "menu" },
 | 
				
			||||||
 | 
					    { form: footerForm, section: "footer" },
 | 
				
			||||||
 | 
					    { form: legalsForm, section: "legals" },
 | 
				
			||||||
 | 
					    { form: buildForm, section: "build" }
 | 
				
			||||||
 | 
					  ].forEach(({ form, section }) => {
 | 
				
			||||||
 | 
					    if (!form) return;
 | 
				
			||||||
 | 
					    form.addEventListener("submit", async (e) => {
 | 
				
			||||||
 | 
					      e.preventDefault();
 | 
				
			||||||
 | 
					      // Native browser validation
 | 
				
			||||||
 | 
					      if (!form.reportValidity()) {
 | 
				
			||||||
 | 
					        showToast("❌ Please fill all required fields before saving.", "error");
 | 
				
			||||||
 | 
					        updateSectionStatus(section);
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      // Social section: check thumbnail
 | 
				
			||||||
 | 
					      if (section === "social") {
 | 
				
			||||||
 | 
					        if (!thumbnailInput || !thumbnailInput.value) {
 | 
				
			||||||
 | 
					          showToast("❌ Thumbnail is required.", "error");
 | 
				
			||||||
 | 
					          updateSectionStatus(section);
 | 
				
			||||||
 | 
					          return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      // Menu section: check all menu items
 | 
				
			||||||
 | 
					      if (section === "menu") {
 | 
				
			||||||
 | 
					        updateMenuItemsFromInputs();
 | 
				
			||||||
 | 
					        if (!menuItems.length || !menuItems.every(item => item.label && item.href)) {
 | 
				
			||||||
 | 
					          showToast("❌ Please fill all menu item fields.", "error");
 | 
				
			||||||
 | 
					          updateSectionStatus(section);
 | 
				
			||||||
 | 
					          return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      // Legals section: check all paragraphs
 | 
				
			||||||
 | 
					      if (section === "legals") {
 | 
				
			||||||
 | 
					        updateIpParagraphsFromInputs();
 | 
				
			||||||
 | 
					        if (!ipParagraphs.length || !ipParagraphs.every(ip => ip.paragraph)) {
 | 
				
			||||||
 | 
					          showToast("❌ Please fill all intellectual property paragraphs.", "error");
 | 
				
			||||||
 | 
					          updateSectionStatus(section);
 | 
				
			||||||
 | 
					          return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      // Build payload for this section only
 | 
				
			||||||
 | 
					      let payload = {};
 | 
				
			||||||
 | 
					      payload[section] = getSectionValues(section);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const res = await fetch("/api/site-info", {
 | 
					      const res = await fetch("/api/site-info", {
 | 
				
			||||||
        method: "POST",
 | 
					        method: "POST",
 | 
				
			||||||
        headers: { "Content-Type": "application/json" },
 | 
					        headers: { "Content-Type": "application/json" },
 | 
				
			||||||
@@ -447,10 +621,18 @@ document.addEventListener("DOMContentLoaded", () => {
 | 
				
			|||||||
      });
 | 
					      });
 | 
				
			||||||
      const result = await res.json();
 | 
					      const result = await res.json();
 | 
				
			||||||
      if (result.status === "ok") {
 | 
					      if (result.status === "ok") {
 | 
				
			||||||
        showToast("✅ Site info saved!", "success");
 | 
					        showToast("✅ Section saved!", "success");
 | 
				
			||||||
 | 
					        // Reload config for this section
 | 
				
			||||||
 | 
					        fetch("/api/site-info")
 | 
				
			||||||
 | 
					          .then(res => res.json())
 | 
				
			||||||
 | 
					          .then(data => {
 | 
				
			||||||
 | 
					            loadedConfig = data;
 | 
				
			||||||
 | 
					            updateSectionStatus(section);
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        showToast("❌ Error saving site info", "error");
 | 
					        showToast("❌ Error saving section", "error");
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
@@ -116,54 +116,173 @@ function renderLocalFonts(fonts) {
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// --- Section helpers ---
 | 
				
			||||||
 | 
					function getSectionValues(section) {
 | 
				
			||||||
 | 
					  switch (section) {
 | 
				
			||||||
 | 
					    case "colors":
 | 
				
			||||||
 | 
					      return {
 | 
				
			||||||
 | 
					        primary: document.getElementById("color-primary-text").value,
 | 
				
			||||||
 | 
					        primary_dark: document.getElementById("color-primary-dark-text").value,
 | 
				
			||||||
 | 
					        secondary: document.getElementById("color-secondary-text").value,
 | 
				
			||||||
 | 
					        accent: document.getElementById("color-accent-text").value,
 | 
				
			||||||
 | 
					        text_dark: document.getElementById("color-text-dark-text").value,
 | 
				
			||||||
 | 
					        background: document.getElementById("color-background-text").value,
 | 
				
			||||||
 | 
					        browser_color: document.getElementById("color-browser-color-text").value
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					    case "google-fonts":
 | 
				
			||||||
 | 
					      const googleFontsFields = document.getElementById("google-fonts-fields");
 | 
				
			||||||
 | 
					      const fonts = [];
 | 
				
			||||||
 | 
					      if (googleFontsFields) {
 | 
				
			||||||
 | 
					        googleFontsFields.querySelectorAll(".input-field").forEach(field => {
 | 
				
			||||||
 | 
					          const family = field.querySelector('input[name^="google_fonts"][name$="[family]"]').value.trim();
 | 
				
			||||||
 | 
					          const weights = field.querySelector('input[name^="google_fonts"][name$="[weights]"]').value
 | 
				
			||||||
 | 
					            .split(",").map(w => w.trim()).filter(Boolean);
 | 
				
			||||||
 | 
					          if (family) fonts.push({ family, weights });
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return fonts;
 | 
				
			||||||
 | 
					    case "fonts":
 | 
				
			||||||
 | 
					      return {
 | 
				
			||||||
 | 
					        primary: {
 | 
				
			||||||
 | 
					          name: document.getElementById("font-primary").value,
 | 
				
			||||||
 | 
					          fallback: document.getElementById("font-primary-fallback").value
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        secondary: {
 | 
				
			||||||
 | 
					          name: document.getElementById("font-secondary").value,
 | 
				
			||||||
 | 
					          fallback: document.getElementById("font-secondary-fallback").value
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					    case "favicon":
 | 
				
			||||||
 | 
					      return {
 | 
				
			||||||
 | 
					        path: document.getElementById("favicon-path").value
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      return {};
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function isSectionComplete(section) {
 | 
				
			||||||
 | 
					  switch (section) {
 | 
				
			||||||
 | 
					    case "colors":
 | 
				
			||||||
 | 
					      const v = getSectionValues("colors");
 | 
				
			||||||
 | 
					      return (
 | 
				
			||||||
 | 
					        v.primary &&
 | 
				
			||||||
 | 
					        v.primary_dark &&
 | 
				
			||||||
 | 
					        v.secondary &&
 | 
				
			||||||
 | 
					        v.accent &&
 | 
				
			||||||
 | 
					        v.text_dark &&
 | 
				
			||||||
 | 
					        v.background &&
 | 
				
			||||||
 | 
					        v.browser_color
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    case "google-fonts":
 | 
				
			||||||
 | 
					      const fonts = getSectionValues("google-fonts");
 | 
				
			||||||
 | 
					      return fonts.every(f => f.family);
 | 
				
			||||||
 | 
					    case "fonts":
 | 
				
			||||||
 | 
					      const f = getSectionValues("fonts");
 | 
				
			||||||
 | 
					      return f.primary.name && f.primary.fallback && f.secondary.name && f.secondary.fallback;
 | 
				
			||||||
 | 
					    case "favicon":
 | 
				
			||||||
 | 
					      const fav = getSectionValues("favicon");
 | 
				
			||||||
 | 
					      return !!fav.path;
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function isSectionSaved(section, loadedConfig) {
 | 
				
			||||||
 | 
					  switch (section) {
 | 
				
			||||||
 | 
					    case "colors":
 | 
				
			||||||
 | 
					      const v = getSectionValues("colors");
 | 
				
			||||||
 | 
					      const c = loadedConfig.colors || {};
 | 
				
			||||||
 | 
					      return (
 | 
				
			||||||
 | 
					        v.primary === c.primary &&
 | 
				
			||||||
 | 
					        v.primary_dark === c.primary_dark &&
 | 
				
			||||||
 | 
					        v.secondary === c.secondary &&
 | 
				
			||||||
 | 
					        v.accent === c.accent &&
 | 
				
			||||||
 | 
					        v.text_dark === c.text_dark &&
 | 
				
			||||||
 | 
					        v.background === c.background &&
 | 
				
			||||||
 | 
					        v.browser_color === c.browser_color
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    case "google-fonts":
 | 
				
			||||||
 | 
					      const fonts = getSectionValues("google-fonts");
 | 
				
			||||||
 | 
					      const cf = loadedConfig.google_fonts || [];
 | 
				
			||||||
 | 
					      return JSON.stringify(fonts) === JSON.stringify(cf);
 | 
				
			||||||
 | 
					    case "fonts":
 | 
				
			||||||
 | 
					      const f = getSectionValues("fonts");
 | 
				
			||||||
 | 
					      const cfnt = loadedConfig.fonts || {};
 | 
				
			||||||
 | 
					      return (
 | 
				
			||||||
 | 
					        f.primary.name === (cfnt.primary?.name || "") &&
 | 
				
			||||||
 | 
					        f.primary.fallback === (cfnt.primary?.fallback || "") &&
 | 
				
			||||||
 | 
					        f.secondary.name === (cfnt.secondary?.name || "") &&
 | 
				
			||||||
 | 
					        f.secondary.fallback === (cfnt.secondary?.fallback || "")
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    case "favicon":
 | 
				
			||||||
 | 
					      const fav = getSectionValues("favicon");
 | 
				
			||||||
 | 
					      const cfav = loadedConfig.favicon || {};
 | 
				
			||||||
 | 
					      return fav.path === (cfav.path || "");
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function updateSectionStatus(section, loadedConfig) {
 | 
				
			||||||
 | 
					  const statusEl = document.querySelector(`#${section}-form .section-status`);
 | 
				
			||||||
 | 
					  if (!statusEl) return;
 | 
				
			||||||
 | 
					  if (!isSectionComplete(section)) {
 | 
				
			||||||
 | 
					    statusEl.innerHTML = "⚠️ Section not yet saved. Please fill required fields";
 | 
				
			||||||
 | 
					    statusEl.style.color = "#ffc700";
 | 
				
			||||||
 | 
					    statusEl.style.display = "";
 | 
				
			||||||
 | 
					    statusEl.style.fontStyle = "normal";
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (isSectionSaved(section, loadedConfig)) {
 | 
				
			||||||
 | 
					    statusEl.innerHTML = "";
 | 
				
			||||||
 | 
					    statusEl.style.display = "none";
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    statusEl.innerHTML = "⚠️ Section not yet saved";
 | 
				
			||||||
 | 
					    statusEl.style.color = "#ffc700";
 | 
				
			||||||
 | 
					    statusEl.style.display = "";
 | 
				
			||||||
 | 
					    statusEl.style.fontStyle = "normal";
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
document.addEventListener("DOMContentLoaded", async () => {
 | 
					document.addEventListener("DOMContentLoaded", async () => {
 | 
				
			||||||
  const themeInfo = await fetchThemeInfo();
 | 
					  const themeInfo = await fetchThemeInfo();
 | 
				
			||||||
  const themeNameSpan = document.getElementById("current-theme");
 | 
					  const themeNameSpan = document.getElementById("current-theme");
 | 
				
			||||||
  if (themeNameSpan) themeNameSpan.textContent = themeInfo.theme_name;
 | 
					  if (themeNameSpan) themeNameSpan.textContent = themeInfo.theme_name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const themeYaml = themeInfo.theme_yaml;
 | 
					  let loadedConfig = themeInfo.theme_yaml;
 | 
				
			||||||
  const googleFonts = themeYaml.google_fonts ? JSON.parse(JSON.stringify(themeYaml.google_fonts)) : [];
 | 
					  let googleFonts = loadedConfig.google_fonts ? JSON.parse(JSON.stringify(loadedConfig.google_fonts)) : [];
 | 
				
			||||||
  let localFonts = await fetchLocalFonts(themeInfo.theme_name);
 | 
					  let localFonts = await fetchLocalFonts(themeInfo.theme_name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Colors
 | 
					  // Colors
 | 
				
			||||||
  if (themeYaml.colors) {
 | 
					  if (loadedConfig.colors) {
 | 
				
			||||||
    setupColorPicker("color-primary", "color-primary-btn", "color-primary-text", themeYaml.colors.primary || "#0065a1");
 | 
					    setupColorPicker("color-primary", "color-primary-btn", "color-primary-text", loadedConfig.colors.primary || "#0065a1");
 | 
				
			||||||
    setupColorPicker("color-primary-dark", "color-primary-dark-btn", "color-primary-dark-text", themeYaml.colors.primary_dark || "#005384");
 | 
					    setupColorPicker("color-primary-dark", "color-primary-dark-btn", "color-primary-dark-text", loadedConfig.colors.primary_dark || "#005384");
 | 
				
			||||||
    setupColorPicker("color-secondary", "color-secondary-btn", "color-secondary-text", themeYaml.colors.secondary || "#00b0f0");
 | 
					    setupColorPicker("color-secondary", "color-secondary-btn", "color-secondary-text", loadedConfig.colors.secondary || "#00b0f0");
 | 
				
			||||||
    setupColorPicker("color-accent", "color-accent-btn", "color-accent-text", themeYaml.colors.accent || "#ffc700");
 | 
					    setupColorPicker("color-accent", "color-accent-btn", "color-accent-text", loadedConfig.colors.accent || "#ffc700");
 | 
				
			||||||
    setupColorPicker("color-text-dark", "color-text-dark-btn", "color-text-dark-text", themeYaml.colors.text_dark || "#616161");
 | 
					    setupColorPicker("color-text-dark", "color-text-dark-btn", "color-text-dark-text", loadedConfig.colors.text_dark || "#616161");
 | 
				
			||||||
    setupColorPicker("color-background", "color-background-btn", "color-background-text", themeYaml.colors.background || "#fff");
 | 
					    setupColorPicker("color-background", "color-background-btn", "color-background-text", loadedConfig.colors.background || "#fff");
 | 
				
			||||||
    setupColorPicker("color-browser-color", "color-browser-color-btn", "color-browser-color-text", themeYaml.colors.browser_color || "#fff");
 | 
					    setupColorPicker("color-browser-color", "color-browser-color-btn", "color-browser-color-text", loadedConfig.colors.browser_color || "#fff");
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Fonts
 | 
					  // Fonts
 | 
				
			||||||
  function refreshFontDropdowns() {
 | 
					  function refreshFontDropdowns() {
 | 
				
			||||||
    setFontDropdown("font-primary", document.getElementById("font-primary").value, [
 | 
					    setFontDropdown("font-primary", loadedConfig.fonts?.primary?.name || "Lato", [
 | 
				
			||||||
      ...googleFonts.map(f => f.family),
 | 
					      ...googleFonts.map(f => f.family),
 | 
				
			||||||
      ...localFonts
 | 
					      ...localFonts
 | 
				
			||||||
    ]);
 | 
					    ]);
 | 
				
			||||||
    setFontDropdown("font-secondary", document.getElementById("font-secondary").value, [
 | 
					    setFontDropdown("font-secondary", loadedConfig.fonts?.secondary?.name || "Montserrat", [
 | 
				
			||||||
      ...googleFonts.map(f => f.family),
 | 
					      ...googleFonts.map(f => f.family),
 | 
				
			||||||
      ...localFonts
 | 
					      ...localFonts
 | 
				
			||||||
    ]);
 | 
					    ]);
 | 
				
			||||||
 | 
					    setFallbackDropdown("font-primary-fallback", loadedConfig.fonts?.primary?.fallback || "sans-serif");
 | 
				
			||||||
 | 
					    setFallbackDropdown("font-secondary-fallback", loadedConfig.fonts?.secondary?.fallback || "serif");
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (themeYaml.fonts) {
 | 
					  refreshFontDropdowns();
 | 
				
			||||||
    setFontDropdown("font-primary", themeYaml.fonts.primary?.name || "Lato", [
 | 
					 | 
				
			||||||
      ...googleFonts.map(f => f.family),
 | 
					 | 
				
			||||||
      ...localFonts
 | 
					 | 
				
			||||||
    ]);
 | 
					 | 
				
			||||||
    setFallbackDropdown("font-primary-fallback", themeYaml.fonts.primary?.fallback || "sans-serif");
 | 
					 | 
				
			||||||
    setFontDropdown("font-secondary", themeYaml.fonts.secondary?.name || "Montserrat", [
 | 
					 | 
				
			||||||
      ...googleFonts.map(f => f.family),
 | 
					 | 
				
			||||||
      ...localFonts
 | 
					 | 
				
			||||||
    ]);
 | 
					 | 
				
			||||||
    setFallbackDropdown("font-secondary-fallback", themeYaml.fonts.secondary?.fallback || "serif");
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Font upload logic
 | 
					  // Font upload logic
 | 
				
			||||||
  const fontUploadInput = document.getElementById("font-upload");
 | 
					  const fontUploadInput = document.getElementById("font-upload");
 | 
				
			||||||
  const chooseFontBtn = document.getElementById("choose-font-btn");
 | 
					  const chooseFontBtn = document.getElementById("choose-font-btn");
 | 
				
			||||||
  const fontUploadStatus = document.getElementById("font-upload-status");
 | 
					 | 
				
			||||||
  const localFontsList = document.getElementById("local-fonts-list");
 | 
					  const localFontsList = document.getElementById("local-fonts-list");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Modal logic for font deletion
 | 
					  // Modal logic for font deletion
 | 
				
			||||||
@@ -341,9 +460,9 @@ document.addEventListener("DOMContentLoaded", async () => {
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (themeYaml.favicon && themeYaml.favicon.path) {
 | 
					  if (loadedConfig.favicon && loadedConfig.favicon.path) {
 | 
				
			||||||
    faviconInput.value = themeYaml.favicon.path;
 | 
					    faviconInput.value = loadedConfig.favicon.path;
 | 
				
			||||||
    updateFaviconPreview(`/themes/${themeInfo.theme_name}/${themeYaml.favicon.path}?t=${Date.now()}`);
 | 
					    updateFaviconPreview(`/themes/${themeInfo.theme_name}/${loadedConfig.favicon.path}?t=${Date.now()}`);
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    updateFaviconPreview("");
 | 
					    updateFaviconPreview("");
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -367,6 +486,7 @@ document.addEventListener("DOMContentLoaded", async () => {
 | 
				
			|||||||
      googleFonts.push(...updatedGoogleFonts);
 | 
					      googleFonts.push(...updatedGoogleFonts);
 | 
				
			||||||
      renderGoogleFonts(googleFonts);
 | 
					      renderGoogleFonts(googleFonts);
 | 
				
			||||||
      refreshFontDropdowns();
 | 
					      refreshFontDropdowns();
 | 
				
			||||||
 | 
					      updateSectionStatus("google-fonts", loadedConfig);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -396,6 +516,7 @@ document.addEventListener("DOMContentLoaded", async () => {
 | 
				
			|||||||
        googleFonts.push(...updatedGoogleFonts);
 | 
					        googleFonts.push(...updatedGoogleFonts);
 | 
				
			||||||
        renderGoogleFonts(googleFonts);
 | 
					        renderGoogleFonts(googleFonts);
 | 
				
			||||||
        refreshFontDropdowns();
 | 
					        refreshFontDropdowns();
 | 
				
			||||||
 | 
					        updateSectionStatus("google-fonts", loadedConfig);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }, true);
 | 
					    }, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -414,54 +535,72 @@ document.addEventListener("DOMContentLoaded", async () => {
 | 
				
			|||||||
        googleFonts.push(...updatedGoogleFonts);
 | 
					        googleFonts.push(...updatedGoogleFonts);
 | 
				
			||||||
        renderGoogleFonts(googleFonts);
 | 
					        renderGoogleFonts(googleFonts);
 | 
				
			||||||
        refreshFontDropdowns();
 | 
					        refreshFontDropdowns();
 | 
				
			||||||
 | 
					        updateSectionStatus("google-fonts", loadedConfig);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  document.getElementById("theme-editor-form").addEventListener("submit", async (e) => {
 | 
					  // --- Section status listeners ---
 | 
				
			||||||
 | 
					  [
 | 
				
			||||||
 | 
					    { form: document.getElementById("colors-form"), section: "colors" },
 | 
				
			||||||
 | 
					    { form: document.getElementById("google-fonts-form"), section: "google-fonts" },
 | 
				
			||||||
 | 
					    { form: document.getElementById("fonts-form"), section: "fonts" },
 | 
				
			||||||
 | 
					    { form: document.getElementById("favicon-form"), section: "favicon" }
 | 
				
			||||||
 | 
					  ].forEach(({ form, section }) => {
 | 
				
			||||||
 | 
					    if (!form) return;
 | 
				
			||||||
 | 
					    form.addEventListener("input", () => updateSectionStatus(section, loadedConfig));
 | 
				
			||||||
 | 
					    form.addEventListener("change", () => updateSectionStatus(section, loadedConfig));
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // --- Section save handlers ---
 | 
				
			||||||
 | 
					  [
 | 
				
			||||||
 | 
					    { form: document.getElementById("colors-form"), section: "colors" },
 | 
				
			||||||
 | 
					    { form: document.getElementById("google-fonts-form"), section: "google-fonts" },
 | 
				
			||||||
 | 
					    { form: document.getElementById("fonts-form"), section: "fonts" },
 | 
				
			||||||
 | 
					    { form: document.getElementById("favicon-form"), section: "favicon" }
 | 
				
			||||||
 | 
					  ].forEach(({ form, section }) => {
 | 
				
			||||||
 | 
					    if (!form) return;
 | 
				
			||||||
 | 
					    form.addEventListener("submit", async (e) => {
 | 
				
			||||||
      e.preventDefault();
 | 
					      e.preventDefault();
 | 
				
			||||||
    showLoader("Saving theme...");
 | 
					      if (!form.reportValidity() || !isSectionComplete(section)) {
 | 
				
			||||||
    const data = {};
 | 
					        showToast("❌ Please fill all required fields before saving.", "error");
 | 
				
			||||||
    data.colors = {
 | 
					        updateSectionStatus(section, loadedConfig);
 | 
				
			||||||
      primary: document.getElementById("color-primary-text").value,
 | 
					        return;
 | 
				
			||||||
      primary_dark: document.getElementById("color-primary-dark-text").value,
 | 
					 | 
				
			||||||
      secondary: document.getElementById("color-secondary-text").value,
 | 
					 | 
				
			||||||
      accent: document.getElementById("color-accent-text").value,
 | 
					 | 
				
			||||||
      text_dark: document.getElementById("color-text-dark-text").value,
 | 
					 | 
				
			||||||
      background: document.getElementById("color-background-text").value,
 | 
					 | 
				
			||||||
      browser_color: document.getElementById("color-browser-color-text").value
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    data.fonts = {
 | 
					 | 
				
			||||||
      primary: {
 | 
					 | 
				
			||||||
        name: document.getElementById("font-primary").value,
 | 
					 | 
				
			||||||
        fallback: document.getElementById("font-primary-fallback").value
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      secondary: {
 | 
					 | 
				
			||||||
        name: document.getElementById("font-secondary").value,
 | 
					 | 
				
			||||||
        fallback: document.getElementById("font-secondary-fallback").value
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    };
 | 
					      // Merge with loadedConfig to avoid overwriting other sections
 | 
				
			||||||
    data.favicon = {
 | 
					      let payload = { ...loadedConfig };
 | 
				
			||||||
      path: faviconInput.value
 | 
					      switch (section) {
 | 
				
			||||||
    };
 | 
					        case "colors":
 | 
				
			||||||
    data.google_fonts = [];
 | 
					          payload.colors = getSectionValues("colors");
 | 
				
			||||||
    document.querySelectorAll("#google-fonts-fields .input-field").forEach(field => {
 | 
					          break;
 | 
				
			||||||
      const family = field.querySelector('input[name^="google_fonts"][name$="[family]"]').value;
 | 
					        case "google-fonts":
 | 
				
			||||||
      const weights = field.querySelector('input[name^="google_fonts"][name$="[weights]"]').value
 | 
					          payload.google_fonts = getSectionValues("google-fonts");
 | 
				
			||||||
        .split(",").map(w => w.trim()).filter(w => w);
 | 
					          break;
 | 
				
			||||||
      if (family) data.google_fonts.push({ family, weights });
 | 
					        case "fonts":
 | 
				
			||||||
    });
 | 
					          payload.fonts = getSectionValues("fonts");
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					        case "favicon":
 | 
				
			||||||
 | 
					          payload.favicon = getSectionValues("favicon");
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      showLoader("Saving...");
 | 
				
			||||||
      const res = await fetch("/api/theme-info", {
 | 
					      const res = await fetch("/api/theme-info", {
 | 
				
			||||||
        method: "POST",
 | 
					        method: "POST",
 | 
				
			||||||
        headers: { "Content-Type": "application/json" },
 | 
					        headers: { "Content-Type": "application/json" },
 | 
				
			||||||
      body: JSON.stringify({ theme_name: themeInfo.theme_name, theme_yaml: data })
 | 
					        body: JSON.stringify({ theme_name: themeInfo.theme_name, theme_yaml: payload })
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
      hideLoader();
 | 
					      hideLoader();
 | 
				
			||||||
      if (res.ok) {
 | 
					      if (res.ok) {
 | 
				
			||||||
      showToast("✅ Theme saved!", "success");
 | 
					        showToast("✅ Section saved!", "success");
 | 
				
			||||||
 | 
					        const updatedThemeInfo = await fetchThemeInfo();
 | 
				
			||||||
 | 
					        loadedConfig = updatedThemeInfo.theme_yaml;
 | 
				
			||||||
 | 
					        updateSectionStatus(section, loadedConfig);
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
      showToast("Error saving theme.", "error");
 | 
					        showToast("Error saving section.", "error");
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Initial status update
 | 
				
			||||||
 | 
					  ["colors", "google-fonts", "fonts", "favicon"].forEach(section => updateSectionStatus(section, loadedConfig));
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
@@ -5,11 +5,12 @@
 | 
				
			|||||||
{% block content %}
 | 
					{% block content %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<h1>Edit Site Info</h1>
 | 
					<h1>Edit Site Info</h1>
 | 
				
			||||||
    <form id="site-info-form">
 | 
					
 | 
				
			||||||
    <button type="submit" class="top-save">Save</button>
 | 
					 | 
				
			||||||
<!-- Info Section -->
 | 
					<!-- Info Section -->
 | 
				
			||||||
      <fieldset>
 | 
					<form id="info-form" autocomplete="off">
 | 
				
			||||||
 | 
					  <fieldset id="info-section">
 | 
				
			||||||
    <h2>Info</h2>
 | 
					    <h2>Info</h2>
 | 
				
			||||||
 | 
					    <p class="section-status"></p>
 | 
				
			||||||
    <p>Set the basic information for your site and SEO</p>
 | 
					    <p>Set the basic information for your site and SEO</p>
 | 
				
			||||||
    <div class="fields">
 | 
					    <div class="fields">
 | 
				
			||||||
      <div class="input-field">
 | 
					      <div class="input-field">
 | 
				
			||||||
@@ -37,11 +38,16 @@
 | 
				
			|||||||
        <input type="text" name="info.author" placeholder="Your Name" required>
 | 
					        <input type="text" name="info.author" placeholder="Your Name" required>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
 | 
					    <button type="submit" class="section-save-btn" data-section="info">Save</button>
 | 
				
			||||||
  </fieldset>
 | 
					  </fieldset>
 | 
				
			||||||
 | 
					</form>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<!-- Social Section -->
 | 
					<!-- Social Section -->
 | 
				
			||||||
      <fieldset>
 | 
					<form id="social-form" autocomplete="off">
 | 
				
			||||||
 | 
					  <fieldset id="social-section">
 | 
				
			||||||
    <h2>Social</h2>
 | 
					    <h2>Social</h2>
 | 
				
			||||||
    <p>Set your social media links and thumbnail for link sharing</p>
 | 
					    <p>Set your social media links and thumbnail for link sharing</p>
 | 
				
			||||||
 | 
					    <p class="section-status"></p>
 | 
				
			||||||
    <div class="fields">
 | 
					    <div class="fields">
 | 
				
			||||||
      <div class="input-field">
 | 
					      <div class="input-field">
 | 
				
			||||||
        <label>Instagram URL</label>
 | 
					        <label>Instagram URL</label>
 | 
				
			||||||
@@ -50,28 +56,38 @@
 | 
				
			|||||||
        <input type="file" id="thumbnail-upload" accept="image/png,image/jpeg,image/webp" style="display:none;">
 | 
					        <input type="file" id="thumbnail-upload" accept="image/png,image/jpeg,image/webp" style="display:none;">
 | 
				
			||||||
        <button type="button" id="choose-thumbnail-btn" class="up-btn">📸 Upload a photo</button>
 | 
					        <button type="button" id="choose-thumbnail-btn" class="up-btn">📸 Upload a photo</button>
 | 
				
			||||||
        <div class="thumbnail-form">
 | 
					        <div class="thumbnail-form">
 | 
				
			||||||
            <input type="hidden" name="social.thumbnail" id="social-thumbnail">
 | 
					          <input type="hidden" name="social.thumbnail" id="social-thumbnail" required>
 | 
				
			||||||
          <img id="thumbnail-preview" src="" alt="Thumbnail preview" style="max-width:100px;display:none;">
 | 
					          <img id="thumbnail-preview" src="" alt="Thumbnail preview" style="max-width:100px;display:none;">
 | 
				
			||||||
          <button type="button" id="remove-thumbnail-btn" class="remove-btn up-btn danger" style="display:none;">Remove</button>
 | 
					          <button type="button" id="remove-thumbnail-btn" class="remove-btn up-btn danger" style="display:none;">Remove</button>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
 | 
					    <button type="submit" class="section-save-btn" data-section="social">Save</button>
 | 
				
			||||||
  </fieldset>
 | 
					  </fieldset>
 | 
				
			||||||
 | 
					</form>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<!-- Menu Section -->
 | 
					<!-- Menu Section -->
 | 
				
			||||||
      <fieldset>
 | 
					<form id="menu-form" autocomplete="off">
 | 
				
			||||||
 | 
					  <fieldset id="menu-section">
 | 
				
			||||||
    <h2>Menu</h2>
 | 
					    <h2>Menu</h2>
 | 
				
			||||||
    <p>Manage your site menu items. You can use tag combination to propose custom filters</p>
 | 
					    <p>Manage your site menu items. You can use tag combination to propose custom filters</p>
 | 
				
			||||||
 | 
					    <p class="section-status"></p>
 | 
				
			||||||
    <div class="fields">
 | 
					    <div class="fields">
 | 
				
			||||||
      <div class="input-field" style="flex: 1 1 100%;">
 | 
					      <div class="input-field" style="flex: 1 1 100%;">
 | 
				
			||||||
        <div id="menu-items-list"></div>
 | 
					        <div id="menu-items-list"></div>
 | 
				
			||||||
        <button type="button" id="add-menu-item">+ Add menu item</button>
 | 
					        <button type="button" id="add-menu-item">+ Add menu item</button>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
 | 
					    <button type="submit" class="section-save-btn" data-section="menu">Save</button>
 | 
				
			||||||
  </fieldset>
 | 
					  </fieldset>
 | 
				
			||||||
 | 
					</form>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<!-- Footer Section -->
 | 
					<!-- Footer Section -->
 | 
				
			||||||
      <fieldset>
 | 
					<form id="footer-form" autocomplete="off">
 | 
				
			||||||
 | 
					  <fieldset id="footer-section">
 | 
				
			||||||
    <h2>Footer</h2>
 | 
					    <h2>Footer</h2>
 | 
				
			||||||
    <p>Set your copyright informations and legal link name</p>
 | 
					    <p>Set your copyright informations and legal link name</p>
 | 
				
			||||||
 | 
					    <p class="section-status"></p>
 | 
				
			||||||
    <div class="fields">
 | 
					    <div class="fields">
 | 
				
			||||||
      <div class="input-field">
 | 
					      <div class="input-field">
 | 
				
			||||||
        <label>Copyright</label>
 | 
					        <label>Copyright</label>
 | 
				
			||||||
@@ -79,14 +95,19 @@
 | 
				
			|||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <div class="input-field">
 | 
					      <div class="input-field">
 | 
				
			||||||
        <label>Legal Label</label>
 | 
					        <label>Legal Label</label>
 | 
				
			||||||
            <input type="text" name="footer.legal_label" re>
 | 
					        <input type="text" name="footer.legal_label" required>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
 | 
					    <button type="submit" class="section-save-btn" data-section="footer">Save</button>
 | 
				
			||||||
  </fieldset>
 | 
					  </fieldset>
 | 
				
			||||||
 | 
					</form>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<!-- Legals Section -->
 | 
					<!-- Legals Section -->
 | 
				
			||||||
      <fieldset>
 | 
					<form id="legals-form" autocomplete="off">
 | 
				
			||||||
 | 
					  <fieldset id="legals-section">
 | 
				
			||||||
    <h2>Legals</h2>
 | 
					    <h2>Legals</h2>
 | 
				
			||||||
    <p>Set your legal informations</p>
 | 
					    <p>Set your legal informations</p>
 | 
				
			||||||
 | 
					    <p class="section-status"></p>
 | 
				
			||||||
    <div class="fields">
 | 
					    <div class="fields">
 | 
				
			||||||
      <div class="input-field">
 | 
					      <div class="input-field">
 | 
				
			||||||
        <label>Hoster Name</label>
 | 
					        <label>Hoster Name</label>
 | 
				
			||||||
@@ -106,11 +127,16 @@
 | 
				
			|||||||
        <button type="button" id="add-ip-paragraph">+ Add paragraph</button>
 | 
					        <button type="button" id="add-ip-paragraph">+ Add paragraph</button>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
 | 
					    <button type="submit" class="section-save-btn" data-section="legals">Save</button>
 | 
				
			||||||
  </fieldset>
 | 
					  </fieldset>
 | 
				
			||||||
 | 
					</form>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<!-- Build Section -->
 | 
					<!-- Build Section -->
 | 
				
			||||||
      <fieldset>
 | 
					<form id="build-form" autocomplete="off">
 | 
				
			||||||
 | 
					  <fieldset id="build-section">
 | 
				
			||||||
    <h2>Build</h2>
 | 
					    <h2>Build</h2>
 | 
				
			||||||
    <p>Select a theme from the dropdown menu or add your custom theme folder</p>
 | 
					    <p>Select a theme from the dropdown menu or add your custom theme folder</p>
 | 
				
			||||||
 | 
					    <p class="section-status"></p>
 | 
				
			||||||
    <div class="fields">
 | 
					    <div class="fields">
 | 
				
			||||||
      <div class="input-field">
 | 
					      <div class="input-field">
 | 
				
			||||||
        <label>Theme</label>
 | 
					        <label>Theme</label>
 | 
				
			||||||
@@ -130,9 +156,10 @@
 | 
				
			|||||||
        </label>
 | 
					        </label>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
 | 
					    <button type="submit" class="section-save-btn" data-section="build">Save</button>
 | 
				
			||||||
  </fieldset>
 | 
					  </fieldset>
 | 
				
			||||||
      <button type="submit">Save</button>
 | 
					 | 
				
			||||||
</form>
 | 
					</form>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<!-- Stepper -->
 | 
					<!-- Stepper -->
 | 
				
			||||||
<div class="section">
 | 
					<div class="section">
 | 
				
			||||||
  <h2>Steps</h2>
 | 
					  <h2>Steps</h2>
 | 
				
			||||||
@@ -147,7 +174,6 @@
 | 
				
			|||||||
    <li><button id="stepper-build">Generate your static site!</button></li>
 | 
					    <li><button id="stepper-build">Generate your static site!</button></li>
 | 
				
			||||||
  </ul>
 | 
					  </ul>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
  </div>
 | 
					 | 
				
			||||||
<!-- Delete thumbnail confirmation modal-->
 | 
					<!-- Delete thumbnail confirmation modal-->
 | 
				
			||||||
<div class="content-inner">
 | 
					<div class="content-inner">
 | 
				
			||||||
  <div id="delete-modal" class="modal" style="display:none;">
 | 
					  <div id="delete-modal" class="modal" style="display:none;">
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -610,9 +610,9 @@ label {
 | 
				
			|||||||
  letter-spacing: 0.5px;
 | 
					  letter-spacing: 0.5px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#site-info-form input, #theme-editor-form input,
 | 
					form input,
 | 
				
			||||||
#site-info-form textarea, #theme-editor-form textarea,
 | 
					form textarea,
 | 
				
			||||||
#site-info-form select, #theme-editor-form select {
 | 
					form select {
 | 
				
			||||||
  background: #1f2223;
 | 
					  background: #1f2223;
 | 
				
			||||||
  color: #fff;
 | 
					  color: #fff;
 | 
				
			||||||
  border: 1px solid #585858;
 | 
					  border: 1px solid #585858;
 | 
				
			||||||
@@ -626,10 +626,6 @@ label {
 | 
				
			|||||||
  box-shadow: 0 2px 8px rgba(0,0,0,0.07);
 | 
					  box-shadow: 0 2px 8px rgba(0,0,0,0.07);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#site-info-form button[type="submit"].top-save, #theme-editor-form button[type="submit"].top-save {
 | 
					 | 
				
			||||||
  margin-bottom: 0;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#theme-editor-form input, #theme-editor-form textarea,#theme-editor-form select {
 | 
					#theme-editor-form input, #theme-editor-form textarea,#theme-editor-form select {
 | 
				
			||||||
  margin-bottom: 18px;
 | 
					  margin-bottom: 18px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -642,25 +638,20 @@ label {
 | 
				
			|||||||
  margin-bottom: 25px;
 | 
					  margin-bottom: 25px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#site-info-form input::placeholder,
 | 
					form input::placeholder,
 | 
				
			||||||
#theme-editor-form input::placeholder,
 | 
					form textarea::placeholder {
 | 
				
			||||||
#site-info-form textarea::placeholder,
 | 
					 | 
				
			||||||
#theme-editor-form textarea::placeholder {
 | 
					 | 
				
			||||||
  color: #585858;
 | 
					  color: #585858;
 | 
				
			||||||
  font-style: italic;
 | 
					  font-style: italic;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#site-info-form input:focus,
 | 
					form input:focus,
 | 
				
			||||||
#theme-editor-form input:focus,
 | 
					form textarea:focus,
 | 
				
			||||||
#site-info-form textarea:focus,
 | 
					form select:focus {
 | 
				
			||||||
#theme-editor-form textarea:focus,
 | 
					 | 
				
			||||||
#site-info-form select:focus,
 | 
					 | 
				
			||||||
#theme-editor-form select:focus {
 | 
					 | 
				
			||||||
  border-color: #585858;
 | 
					  border-color: #585858;
 | 
				
			||||||
  background: #161616;
 | 
					  background: #161616;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#site-info-form textarea,
 | 
					
 | 
				
			||||||
#theme-editor-form textarea {
 | 
					form textarea {
 | 
				
			||||||
  min-height: 60px;
 | 
					  min-height: 60px;
 | 
				
			||||||
  resize: vertical;
 | 
					  resize: vertical;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -679,7 +670,7 @@ img#thumbnail-preview {
 | 
				
			|||||||
  border: 1px solid #585858;
 | 
					  border: 1px solid #585858;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#site-info-form button[type="submit"], #theme-editor-form button[type="submit"] {
 | 
					form button[type="submit"] {
 | 
				
			||||||
  background: linear-gradient(135deg, #26c4ff, #016074);
 | 
					  background: linear-gradient(135deg, #26c4ff, #016074);
 | 
				
			||||||
  color: #fff;
 | 
					  color: #fff;
 | 
				
			||||||
  font-weight: 700;
 | 
					  font-weight: 700;
 | 
				
			||||||
@@ -693,11 +684,11 @@ img#thumbnail-preview {
 | 
				
			|||||||
  transition: background 0.2s;
 | 
					  transition: background 0.2s;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#site-info-form button[type="submit"]:hover,  #theme-editor-form button[type="submit"]:hover {
 | 
					form button[type="submit"]:hover {
 | 
				
			||||||
  background: linear-gradient(135deg, #72d9ff, #26657e);
 | 
					  background: linear-gradient(135deg, #72d9ff, #26657e);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#site-info-form button[type="button"], #theme-editor-form button[type="button"] {
 | 
					form button[type="button"] {
 | 
				
			||||||
  background: #00000000;
 | 
					  background: #00000000;
 | 
				
			||||||
  color: #fff;
 | 
					  color: #fff;
 | 
				
			||||||
  border: none;
 | 
					  border: none;
 | 
				
			||||||
@@ -710,34 +701,36 @@ img#thumbnail-preview {
 | 
				
			|||||||
  border: 1px solid #585858;
 | 
					  border: 1px solid #585858;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#site-info-form button[type="button"]:hover, #theme-editor-form button[type="button"]:hover {
 | 
					form button[type="button"]:hover{
 | 
				
			||||||
  background: #2d2d2d;
 | 
					  background: #2d2d2d;
 | 
				
			||||||
  color: #fff;
 | 
					  color: #fff;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#site-info-form button.remove-menu-item, #site-info-form button.remove-ip-paragraph, #theme-editor-form button.remove-menu-item, #theme-editor-form button.remove-ip-paragraph {
 | 
					form button.remove-menu-item,
 | 
				
			||||||
 | 
					form button.remove-ip-paragraph {
 | 
				
			||||||
  margin-top: 0px;
 | 
					  margin-top: 0px;
 | 
				
			||||||
  margin-bottom: 4px;
 | 
					  margin-bottom: 4px;
 | 
				
			||||||
  border-radius: 30px;
 | 
					  border-radius: 30px;
 | 
				
			||||||
  background: #2d2d2d;
 | 
					  background: #2d2d2d;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#site-info-form button.remove-menu-item:hover, #site-info-form button.remove-ip-paragraph:hover, #theme-editor-form button.remove-menu-item:hover, #theme-editor-form button.remove-ip-paragraph:hover {
 | 
					form button.remove-menu-item:hover,
 | 
				
			||||||
 | 
					form button.remove-ip-paragraph:hover {
 | 
				
			||||||
  background: rgb(121, 26, 19);
 | 
					  background: rgb(121, 26, 19);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#site-info-form button.remove-btn, #theme-editor-form button.remove-btn {
 | 
					form button.remove-btn {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  border-radius: 30px;
 | 
					  border-radius: 30px;
 | 
				
			||||||
  background: #2d2d2d;
 | 
					  background: #2d2d2d;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#site-info-form button.remove-btn:hover, #theme-editor-form button.remove-btn:hover {
 | 
					form button.remove-btn:hover {
 | 
				
			||||||
  background: rgb(121, 26, 19);
 | 
					  background: rgb(121, 26, 19);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#site-info-form .thumbnail-form-label, #theme-editor-form .thumbnail-form-label {
 | 
					form .thumbnail-form-label {
 | 
				
			||||||
  margin-top: 10px;
 | 
					  margin-top: 10px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -799,6 +792,10 @@ fieldset p, .section p {
 | 
				
			|||||||
  margin-top: 16px;
 | 
					  margin-top: 16px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.section-status {
 | 
				
			||||||
 | 
					  font-style: normal;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* --- Stepper --- */
 | 
					/* --- Stepper --- */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#stepper {
 | 
					#stepper {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,117 +9,133 @@
 | 
				
			|||||||
<div class="theme-info">
 | 
					<div class="theme-info">
 | 
				
			||||||
  <strong>Current theme:</strong> <span id="current-theme"></span>
 | 
					  <strong>Current theme:</strong> <span id="current-theme"></span>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
    <form id="theme-editor-form">
 | 
					<div id="theme-editor-form">
 | 
				
			||||||
      <button type="submit" class="top-save">Save</button>
 | 
					 | 
				
			||||||
  <!-- Colors Section -->
 | 
					  <!-- Colors Section -->
 | 
				
			||||||
 | 
					  <form id="colors-form" autocomplete="off">
 | 
				
			||||||
    <fieldset id="color-picker">
 | 
					    <fieldset id="color-picker">
 | 
				
			||||||
      <h2>Colors</h2>
 | 
					      <h2>Colors</h2>
 | 
				
			||||||
 | 
					      <p class="section-status"></p>
 | 
				
			||||||
      <p>Set the color values for your theme</p>
 | 
					      <p>Set the color values for your theme</p>
 | 
				
			||||||
      <div class="fields">
 | 
					      <div class="fields">
 | 
				
			||||||
          <!-- Example for one color field, repeat for all -->
 | 
					 | 
				
			||||||
        <div class="input-field">
 | 
					        <div class="input-field">
 | 
				
			||||||
          <label>Primary</label>
 | 
					          <label>Primary</label>
 | 
				
			||||||
          <div class="fields color-fields">
 | 
					          <div class="fields color-fields">
 | 
				
			||||||
            <button type="button" id="color-primary-btn" class="color-btn"></button>
 | 
					            <button type="button" id="color-primary-btn" class="color-btn"></button>
 | 
				
			||||||
              <input type="color" name="colors.primary" id="color-primary" class="color-input">
 | 
					            <input type="color" name="colors.primary" id="color-primary" class="color-input" required>
 | 
				
			||||||
              <input type="text" name="colors.primary_text" id="color-primary-text" style="width:100px;">
 | 
					            <input type="text" name="colors.primary_text" id="color-primary-text" style="width:100px;" required>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <div class="input-field">
 | 
					        <div class="input-field">
 | 
				
			||||||
          <label>Primary Dark</label>
 | 
					          <label>Primary Dark</label>
 | 
				
			||||||
          <div class="fields color-fields">
 | 
					          <div class="fields color-fields">
 | 
				
			||||||
            <button type="button" id="color-primary-dark-btn" class="color-btn"></button>
 | 
					            <button type="button" id="color-primary-dark-btn" class="color-btn"></button>
 | 
				
			||||||
              <input type="color" name="colors.primary_dark" id="color-primary-dark" class="color-input">
 | 
					            <input type="color" name="colors.primary_dark" id="color-primary-dark" class="color-input" required>
 | 
				
			||||||
              <input type="text" name="colors.primary_dark_text" id="color-primary-dark-text" style="width:100px;">
 | 
					            <input type="text" name="colors.primary_dark_text" id="color-primary-dark-text" style="width:100px;" required>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <div class="input-field">
 | 
					        <div class="input-field">
 | 
				
			||||||
          <label>Secondary</label>
 | 
					          <label>Secondary</label>
 | 
				
			||||||
          <div class="fields color-fields">
 | 
					          <div class="fields color-fields">
 | 
				
			||||||
            <button type="button" id="color-secondary-btn" class="color-btn"></button>
 | 
					            <button type="button" id="color-secondary-btn" class="color-btn"></button>
 | 
				
			||||||
              <input type="color" name="colors.secondary" id="color-secondary" class="color-input">
 | 
					            <input type="color" name="colors.secondary" id="color-secondary" class="color-input" required>
 | 
				
			||||||
              <input type="text" name="colors.secondary_text" id="color-secondary-text" style="width:100px;">
 | 
					            <input type="text" name="colors.secondary_text" id="color-secondary-text" style="width:100px;" required>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <div class="input-field">
 | 
					        <div class="input-field">
 | 
				
			||||||
          <label>Accent</label>
 | 
					          <label>Accent</label>
 | 
				
			||||||
          <div class="fields color-fields">
 | 
					          <div class="fields color-fields">
 | 
				
			||||||
            <button type="button" id="color-accent-btn" class="color-btn"></button>
 | 
					            <button type="button" id="color-accent-btn" class="color-btn"></button>
 | 
				
			||||||
              <input type="color" name="colors.accent" id="color-accent" class="color-input">
 | 
					            <input type="color" name="colors.accent" id="color-accent" class="color-input" required>
 | 
				
			||||||
              <input type="text" name="colors.accent_text" id="color-accent-text" style="width:100px;">
 | 
					            <input type="text" name="colors.accent_text" id="color-accent-text" style="width:100px;" required>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <div class="input-field">
 | 
					        <div class="input-field">
 | 
				
			||||||
          <label>Text Dark</label>
 | 
					          <label>Text Dark</label>
 | 
				
			||||||
          <div class="fields color-fields">
 | 
					          <div class="fields color-fields">
 | 
				
			||||||
            <button type="button" id="color-text-dark-btn" class="color-btn"></button>
 | 
					            <button type="button" id="color-text-dark-btn" class="color-btn"></button>
 | 
				
			||||||
              <input type="color" name="colors.text_dark" id="color-text-dark" class="color-input">
 | 
					            <input type="color" name="colors.text_dark" id="color-text-dark" class="color-input" required>
 | 
				
			||||||
              <input type="text" name="colors.text_dark_text" id="color-text-dark-text" style="width:100px;">
 | 
					            <input type="text" name="colors.text_dark_text" id="color-text-dark-text" style="width:100px;" required>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <div class="input-field">
 | 
					        <div class="input-field">
 | 
				
			||||||
          <label>Background</label>
 | 
					          <label>Background</label>
 | 
				
			||||||
          <div class="fields color-fields">
 | 
					          <div class="fields color-fields">
 | 
				
			||||||
            <button type="button" id="color-background-btn" class="color-btn"></button>
 | 
					            <button type="button" id="color-background-btn" class="color-btn"></button>
 | 
				
			||||||
              <input type="color" name="colors.background" id="color-background" class="color-input">
 | 
					            <input type="color" name="colors.background" id="color-background" class="color-input" required>
 | 
				
			||||||
              <input type="text" name="colors.background_text" id="color-background-text" style="width:100px;">
 | 
					            <input type="text" name="colors.background_text" id="color-background-text" style="width:100px;" required>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <div class="input-field">
 | 
					        <div class="input-field">
 | 
				
			||||||
          <label>Browser Color</label>
 | 
					          <label>Browser Color</label>
 | 
				
			||||||
          <div class="fields color-fields">
 | 
					          <div class="fields color-fields">
 | 
				
			||||||
            <button type="button" id="color-browser-color-btn" class="color-btn"></button>
 | 
					            <button type="button" id="color-browser-color-btn" class="color-btn"></button>
 | 
				
			||||||
              <input type="color" name="colors.browser_color" id="color-browser-color" class="color-input">
 | 
					            <input type="color" name="colors.browser_color" id="color-browser-color" class="color-input" required>
 | 
				
			||||||
              <input type="text" name="colors.browser_color_text" id="color-browser-color-text" style="width:100px;">
 | 
					            <input type="text" name="colors.browser_color_text" id="color-browser-color-text" style="width:100px;" required>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
 | 
					      <button type="submit" class="section-save-btn" data-section="colors">Save</button>
 | 
				
			||||||
    </fieldset>
 | 
					    </fieldset>
 | 
				
			||||||
 | 
					  </form>
 | 
				
			||||||
  <!-- Google Fonts Section -->
 | 
					  <!-- Google Fonts Section -->
 | 
				
			||||||
 | 
					  <form id="google-fonts-form" autocomplete="off">
 | 
				
			||||||
    <fieldset>
 | 
					    <fieldset>
 | 
				
			||||||
      <h2>Google Fonts</h2>
 | 
					      <h2>Google Fonts</h2>
 | 
				
			||||||
 | 
					      <p class="section-status"></p>
 | 
				
			||||||
      <p>Add Google Fonts to your theme</p>
 | 
					      <p>Add Google Fonts to your theme</p>
 | 
				
			||||||
      <div class="fields" id="google-fonts-fields">
 | 
					      <div class="fields" id="google-fonts-fields">
 | 
				
			||||||
        <!-- JS will render font family and weights inputs here -->
 | 
					        <!-- JS will render font family and weights inputs here -->
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <button type="button" id="add-google-font">Add Google Font</button>
 | 
					      <button type="button" id="add-google-font">Add Google Font</button>
 | 
				
			||||||
 | 
					      <button type="submit" class="section-save-btn" data-section="google-fonts">Save</button>
 | 
				
			||||||
    </fieldset>
 | 
					    </fieldset>
 | 
				
			||||||
 | 
					  </form>
 | 
				
			||||||
  <!-- Custom Font Upload Section -->
 | 
					  <!-- Custom Font Upload Section -->
 | 
				
			||||||
 | 
					  <form id="font-upload-form" autocomplete="off">
 | 
				
			||||||
    <fieldset>
 | 
					    <fieldset>
 | 
				
			||||||
      <h2>Upload Custom Font</h2>
 | 
					      <h2>Upload Custom Font</h2>
 | 
				
			||||||
 | 
					      <p class="section-status"></p>
 | 
				
			||||||
      <p>Supported formats: .woff, .woff2</p>
 | 
					      <p>Supported formats: .woff, .woff2</p>
 | 
				
			||||||
      <input type="file" id="font-upload" accept=".woff,.woff2" style="display:none;">
 | 
					      <input type="file" id="font-upload" accept=".woff,.woff2" style="display:none;">
 | 
				
			||||||
      <div id="local-fonts-list" class="font-list"></div>
 | 
					      <div id="local-fonts-list" class="font-list"></div>
 | 
				
			||||||
      <button type="button" id="choose-font-btn" class="up-btn">🖋️ Upload font</button>
 | 
					      <button type="button" id="choose-font-btn" class="up-btn">🖋️ Upload font</button>
 | 
				
			||||||
 | 
					      <!-- Save button not needed for upload-only section -->
 | 
				
			||||||
    </fieldset>
 | 
					    </fieldset>
 | 
				
			||||||
 | 
					  </form>
 | 
				
			||||||
  <!-- Fonts Section -->
 | 
					  <!-- Fonts Section -->
 | 
				
			||||||
 | 
					  <form id="fonts-form" autocomplete="off">
 | 
				
			||||||
    <fieldset>
 | 
					    <fieldset>
 | 
				
			||||||
      <h2>Fonts</h2>
 | 
					      <h2>Fonts</h2>
 | 
				
			||||||
 | 
					      <p class="section-status"></p>
 | 
				
			||||||
      <p>Select where to apply your fonts</p>
 | 
					      <p>Select where to apply your fonts</p>
 | 
				
			||||||
      <div class="fields">
 | 
					      <div class="fields">
 | 
				
			||||||
        <div class="input-field">
 | 
					        <div class="input-field">
 | 
				
			||||||
          <label>Primary Font</label>
 | 
					          <label>Primary Font</label>
 | 
				
			||||||
            <select name="fonts.primary.name" id="font-primary"></select>
 | 
					          <select name="fonts.primary.name" id="font-primary" required></select>
 | 
				
			||||||
          <label>Fallback</label>
 | 
					          <label>Fallback</label>
 | 
				
			||||||
            <select name="fonts.primary.fallback" id="font-primary-fallback">
 | 
					          <select name="fonts.primary.fallback" id="font-primary-fallback" required>
 | 
				
			||||||
            <option value="sans-serif">sans-serif</option>
 | 
					            <option value="sans-serif">sans-serif</option>
 | 
				
			||||||
            <option value="serif">serif</option>
 | 
					            <option value="serif">serif</option>
 | 
				
			||||||
          </select>
 | 
					          </select>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <div class="input-field">
 | 
					        <div class="input-field">
 | 
				
			||||||
          <label>Secondary Font</label>
 | 
					          <label>Secondary Font</label>
 | 
				
			||||||
            <select name="fonts.secondary.name" id="font-secondary"></select>
 | 
					          <select name="fonts.secondary.name" id="font-secondary" required></select>
 | 
				
			||||||
          <label>Fallback</label>
 | 
					          <label>Fallback</label>
 | 
				
			||||||
            <select name="fonts.secondary.fallback" id="font-secondary-fallback">
 | 
					          <select name="fonts.secondary.fallback" id="font-secondary-fallback" required>
 | 
				
			||||||
            <option value="sans-serif">sans-serif</option>
 | 
					            <option value="sans-serif">sans-serif</option>
 | 
				
			||||||
            <option value="serif">serif</option>
 | 
					            <option value="serif">serif</option>
 | 
				
			||||||
          </select>
 | 
					          </select>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
 | 
					      <button type="submit" class="section-save-btn" data-section="fonts">Save</button>
 | 
				
			||||||
    </fieldset>
 | 
					    </fieldset>
 | 
				
			||||||
 | 
					  </form>
 | 
				
			||||||
  <!-- Favicon Section -->
 | 
					  <!-- Favicon Section -->
 | 
				
			||||||
 | 
					  <form id="favicon-form" autocomplete="off">
 | 
				
			||||||
    <fieldset>
 | 
					    <fieldset>
 | 
				
			||||||
      <h2>Favicon</h2>
 | 
					      <h2>Favicon</h2>
 | 
				
			||||||
 | 
					      <p class="section-status"></p>
 | 
				
			||||||
      <p>Supported formats: .png, .jpg, .jpeg</p>
 | 
					      <p>Supported formats: .png, .jpg, .jpeg</p>
 | 
				
			||||||
      <div class="fields">
 | 
					      <div class="fields">
 | 
				
			||||||
        <div class="input-field">
 | 
					        <div class="input-field">
 | 
				
			||||||
@@ -133,9 +149,10 @@
 | 
				
			|||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
 | 
					      <button type="submit" class="section-save-btn" data-section="favicon">Save</button>
 | 
				
			||||||
    </fieldset>
 | 
					    </fieldset>
 | 
				
			||||||
      <button type="submit">Save</button>
 | 
					 | 
				
			||||||
  </form>
 | 
					  </form>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
<!-- Stepper -->
 | 
					<!-- Stepper -->
 | 
				
			||||||
<div class="section">
 | 
					<div class="section">
 | 
				
			||||||
  <h2>Steps</h2>
 | 
					  <h2>Steps</h2>
 | 
				
			||||||
@@ -150,8 +167,6 @@
 | 
				
			|||||||
    <li><button id="stepper-build">Generate your static site!</button></li>
 | 
					    <li><button id="stepper-build">Generate your static site!</button></li>
 | 
				
			||||||
  </ul>
 | 
					  </ul>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
  </div>
 | 
					 | 
				
			||||||
  </div>
 | 
					 | 
				
			||||||
<!-- Delete confirmation modal for favicon -->
 | 
					<!-- Delete confirmation modal for favicon -->
 | 
				
			||||||
<div id="delete-favicon-modal" class="modal" style="display:none;">
 | 
					<div id="delete-favicon-modal" class="modal" style="display:none;">
 | 
				
			||||||
  <div class="modal-content">
 | 
					  <div class="modal-content">
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user