/* ==========================================================================
   Jey Theme — Multi-theme system + Desktop Icons
   ========================================================================== */

/* --- Theme + full-width initialisation (runs immediately, before paint) --- */
(function () {
	var DEFAULT_THEME = "chrome";
	var saved = localStorage.getItem("jey-theme");
	// Back-compat: "modern" was renamed to "silk". Migrate existing users.
	if (saved === "modern") {
		saved = "silk";
		localStorage.setItem("jey-theme", saved);
	}
	if (!saved) {
		saved = DEFAULT_THEME;
		localStorage.setItem("jey-theme", saved);
	}
	document.documentElement.setAttribute("data-jey-theme", saved);

	// Default full-width on fresh install (user never toggled it)
	if (localStorage.getItem("container_fullwidth") === null) {
		localStorage.setItem("container_fullwidth", "true");
	}

	// Inject saved column widths immediately to avoid flash.
	// Also store grid keys so we can set data-jey-grid attributes early.
	var resizeStyle = document.createElement("style");
	resizeStyle.id = "jey-col-resize-styles";
	var rules = [];
	var earlyGridKeys = [];
	var earlyListDoctypes = [];
	for (var i = 0; i < localStorage.length; i++) {
		var k = localStorage.key(i);
		if (k.indexOf("jey-col-widths:") === 0) {
			var gridKey = k.substring(15);
			earlyGridKeys.push(gridKey);
			try {
				var widths = JSON.parse(localStorage.getItem(k));
				Object.keys(widths).forEach(function (fn) {
					rules.push(
						'[data-jey-grid="' + gridKey + '"] .grid-static-col[data-fieldname="' + fn + '"]' +
						" { flex: 0 0 " + widths[fn] + "px !important; width: " + widths[fn] + "px !important; max-width: none !important; }"
					);
				});
			} catch (e) {}
		} else if (k.indexOf("jey-list-col-widths:") === 0) {
			var listDoctype = k.substring(20);
			earlyListDoctypes.push(listDoctype);
			try {
				var lwidths = JSON.parse(localStorage.getItem(k));
				Object.keys(lwidths).forEach(function (fn) {
					var esc = (window.CSS && CSS.escape) ? CSS.escape(fn) : fn.replace(/[^a-zA-Z0-9_-]/g, "\\$&");
					rules.push(
						'.list-view[data-jey-list="' + listDoctype + '"] .list-row-col.' + esc +
						" { flex: 0 0 " + lwidths[fn] + "px !important; width: " + lwidths[fn] +
						"px !important; max-width: none !important; min-width: 0 !important; }"
					);
				});
			} catch (e) {}
		}
	}
	resizeStyle.textContent = rules.join("\n");
	document.head.appendChild(resizeStyle);

	// Set data-jey-grid on grids as soon as they appear so CSS rules apply immediately
	if (earlyGridKeys.length || earlyListDoctypes.length) {
		var earlyObs = new MutationObserver(function () {
			earlyGridKeys.forEach(function (key) {
				// key = "Sales Invoice:items" → fieldname = "items"
				var parts = key.split(":");
				if (parts.length < 2) return;
				var tableField = parts[1];
				var control = document.querySelector('.frappe-control[data-fieldname="' + tableField + '"]');
				if (control) {
					var grid = control.querySelector(".form-grid");
					if (grid && !grid.dataset.jeyGrid) {
						grid.dataset.jeyGrid = key;
					}
				}
			});
			if (earlyListDoctypes.length) {
				// Stamp data-jey-list on any .list-view whose route matches a saved doctype
				var route = (window.frappe && frappe.get_route) ? frappe.get_route() : null;
				var routeDoctype = (route && route[0] === "List") ? route[1] : null;
				if (routeDoctype && earlyListDoctypes.indexOf(routeDoctype) !== -1) {
					document.querySelectorAll(".list-view").forEach(function (lv) {
						if (lv.dataset.jeyList !== routeDoctype) {
							lv.dataset.jeyList = routeDoctype;
						}
					});
				}
			}
		});
		earlyObs.observe(document.body, { childList: true, subtree: true });
	}
})();

(function () {
	"use strict";

	/* ==================================================================
	   Per-theme icon sets
	   Each theme can define its own SVGs. Falls back to "chrome" if a
	   key is missing, then to FALLBACK.
	   ================================================================== */

	var SETTINGS_PATHS = '<circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-4 0v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83 0 2 2 0 010-2.83l.06-.06A1.65 1.65 0 004.68 15a1.65 1.65 0 00-1.51-1H3a2 2 0 010-4h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 012.83-2.83l.06.06A1.65 1.65 0 009 4.68a1.65 1.65 0 001-1.51V3a2 2 0 014 0v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 2.83l-.06.06A1.65 1.65 0 0019.32 9a1.65 1.65 0 001.51 1H21a2 2 0 010 4h-.09a1.65 1.65 0 00-1.51 1z"/>';

	var HR_PATHS = '<path d="M16 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2"/><circle cx="8.5" cy="7" r="4"/><line class="plus-v" x1="20" y1="8" x2="20" y2="14"/><line class="plus-h" x1="23" y1="11" x2="17" y2="11"/>';

	var THEME_ICONS = {
		/* ============================================================
		   CHROME — detailed line-art icons from the reference HTML
		   ============================================================ */
		chrome: {
			buying:            { cls: "ico-buying",   paths: '<circle cx="9" cy="21" r="1"/><circle cx="20" cy="21" r="1"/><path d="M1 1h4l2.68 13.39a2 2 0 002 1.61h9.72a2 2 0 002-1.61L23 6H6"/>' },
			selling:           { cls: "ico-selling",   paths: '<polyline points="22 7 13.5 15.5 8.5 10.5 2 17"/><polyline class="arrow-head" points="16 7 22 7 22 13"/>' },
			stock:             { cls: "ico-stock",     paths: '<path d="M21 16V8a2 2 0 00-1-1.73l-7-4a2 2 0 00-2 0l-7 4A2 2 0 003 8v8a2 2 0 001 1.73l7 4a2 2 0 002 0l7-4A2 2 0 0021 16z"/><polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/>' },
			manufacturing:     { cls: "ico-mfg",       paths: '<g class="gear1"><circle cx="9" cy="12" r="3"/><path d="M9 9V4.5"/><path d="M6.5 10.5L3 8"/><path d="M6.5 13.5L3 16"/><path d="M9 15v4.5"/><path d="M11.5 10.5L15 8"/><path d="M11.5 13.5L15 16"/></g><g class="gear2"><circle cx="17" cy="8" r="2.5"/><path d="M17 5.5V3"/><path d="M19.2 7L21 5.5"/><path d="M19.2 9L21 10.5"/><path d="M14.8 7L13 5.5"/><path d="M14.8 9L13 10.5"/></g>' },
			projects:          { cls: "ico-proj",      paths: '<rect class="col1" x="3" y="3" width="5" height="18" rx="1"/><rect class="col2" x="10" y="3" width="5" height="12" rx="1"/><rect class="col3" x="17" y="3" width="5" height="15" rx="1"/>' },
			quality:           { cls: "ico-quality",   paths: '<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/><polyline points="9 12 11 14 15 10"/>' },
			assets:            { cls: "ico-assets",    paths: '<rect x="3" y="5" width="18" height="15" rx="2"/><line x1="3" y1="10" x2="21" y2="10"/><circle class="lock-dot" cx="12" cy="15" r="2"/><line x1="12" y1="13" x2="12" y2="10"/><line x1="7" y1="5" x2="7" y2="2"/><line x1="17" y1="5" x2="17" y2="2"/>' },
			subcontracting:    { cls: "ico-sub",       paths: '<g class="person-l"><circle cx="8" cy="7" r="3"/><path d="M7 11H4a2 2 0 00-2 2v1"/><path d="M11 18l-3-3 2-2 3 3"/></g><g class="person-r"><circle cx="16" cy="7" r="3"/><path d="M17 11h3a2 2 0 012 2v1"/><path d="M13 18l3-3-2-2-3 3"/></g>' },
			organization:      { cls: "ico-org",       paths: '<rect x="4" y="2" width="16" height="20" rx="1"/><line x1="9" y1="6" x2="9" y2="6.01"/><line x1="15" y1="6" x2="15" y2="6.01"/><line x1="9" y1="10" x2="9" y2="10.01"/><line x1="15" y1="10" x2="15" y2="10.01"/><line x1="9" y1="14" x2="9" y2="14.01"/><line x1="15" y1="14" x2="15" y2="14.01"/><path d="M10 22v-4h4v4"/>' },
			accounting:        { cls: "ico-acc",       paths: '<rect x="4" y="2" width="16" height="20" rx="2"/><rect x="7" y="5" width="10" height="4" rx="1"/><line class="acc-btn" x1="7" y1="13" x2="7" y2="13.01" stroke-width="2.5"/><line x1="12" y1="13" x2="12" y2="13.01" stroke-width="2.5"/><line class="acc-btn" x1="17" y1="13" x2="17" y2="13.01" stroke-width="2.5" style="animation-delay:0.4s"/><line x1="7" y1="17" x2="7" y2="17.01" stroke-width="2.5"/><line class="acc-btn" x1="12" y1="17" x2="12" y2="17.01" stroke-width="2.5" style="animation-delay:0.8s"/><line x1="17" y1="17" x2="17" y2="17.01" stroke-width="2.5"/>' },
			framework:         { cls: "ico-framework", paths: '<path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/>' },
			"erpnext settings": { cls: "ico-settings", paths: SETTINGS_PATHS },
			settings:          { cls: "ico-settings",  paths: SETTINGS_PATHS },
			"accounts setup":  { cls: "ico-settings",  paths: SETTINGS_PATHS },
			"account setup":   { cls: "ico-settings",  paths: SETTINGS_PATHS },
			hr:                { cls: "ico-hr",        paths: HR_PATHS },
			"hr module":       { cls: "ico-hr",        paths: HR_PATHS },
			"frappe hr":       { cls: "ico-hr",        paths: HR_PATHS },
			invoicing:         { paths: '<path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="8" y1="13" x2="16" y2="13"/><line x1="8" y1="17" x2="13" y2="17"/>' },
			payments:          { paths: '<rect x="1" y="4" width="22" height="16" rx="2"/><line x1="1" y1="10" x2="23" y2="10"/><line x1="5" y1="15" x2="9" y2="15"/><line x1="13" y1="15" x2="15" y2="15"/>' },
			"financial reports": { paths: '<path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="8" y1="13" x2="16" y2="13"/><line x1="8" y1="17" x2="16" y2="17"/><line x1="8" y1="9" x2="10" y2="9"/>' },
			taxes:             { paths: '<path d="M4 2h16a2 2 0 012 2v16a2 2 0 01-2 2H4a2 2 0 01-2-2V4a2 2 0 012-2z"/><line x1="9" y1="8" x2="15" y2="8"/><line x1="9" y1="12" x2="15" y2="12"/><line x1="9" y1="16" x2="13" y2="16"/><line x1="6" y1="8" x2="6" y2="8.01" stroke-width="2"/><line x1="6" y1="12" x2="6" y2="12.01" stroke-width="2"/><line x1="6" y1="16" x2="6" y2="16.01" stroke-width="2"/>' },
			"taxes az":        { paths: '<path d="M4 2h16a2 2 0 012 2v16a2 2 0 01-2 2H4a2 2 0 01-2-2V4a2 2 0 012-2z"/><line x1="9" y1="8" x2="15" y2="8"/><line x1="9" y1="12" x2="15" y2="12"/><line x1="9" y1="16" x2="13" y2="16"/><line x1="6" y1="8" x2="6" y2="8.01" stroke-width="2"/><line x1="6" y1="12" x2="6" y2="12.01" stroke-width="2"/><line x1="6" y1="16" x2="6" y2="16.01" stroke-width="2"/>' },
			banking:           { paths: '<path d="M3 21h18"/><path d="M3 10h18"/><path d="M12 3l9 7H3l9-7z"/><line x1="6" y1="10" x2="6" y2="21"/><line x1="10" y1="10" x2="10" y2="21"/><line x1="14" y1="10" x2="14" y2="21"/><line x1="18" y1="10" x2="18" y2="21"/>' },
			budget:            { paths: '<line x1="12" y1="1" x2="12" y2="23"/><path d="M17 5H9.5a3.5 3.5 0 000 7h5a3.5 3.5 0 010 7H6"/>' },
			"share management": { paths: '<polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/>' },
			subscription:      { paths: '<rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/><polyline points="7 10 10 13 17 7"/>' },
			crm:               { paths: '<path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 00-3-3.87"/><path d="M16 3.13a4 4 0 010 7.75"/>' },
			support:           { paths: '<path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/>' },
			/* --- Framework sub-modules --- */
			automation:        { paths: '<polyline points="16 3 21 3 21 8"/><line x1="4" y1="20" x2="21" y2="3"/><polyline points="21 16 21 21 16 21"/><line x1="15" y1="15" x2="21" y2="21"/><line x1="4" y1="4" x2="9" y2="9"/>' },
			build:             { paths: '<path d="M14.7 6.3a1 1 0 000 1.4l1.6 1.6a1 1 0 001.4 0l3.77-3.77a6 6 0 01-7.94 7.94l-6.91 6.91a2.12 2.12 0 01-3-3l6.91-6.91a6 6 0 017.94-7.94l-3.76 3.76z"/>' },
			data:              { paths: '<ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/>' },
			email:             { paths: '<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22 6 12 13 2 6"/>' },
			integrations:      { paths: '<circle cx="12" cy="12" r="3"/><circle cx="19" cy="5" r="2"/><circle cx="5" cy="5" r="2"/><circle cx="19" cy="19" r="2"/><circle cx="5" cy="19" r="2"/><line x1="14.5" y1="9.5" x2="17.5" y2="6.5"/><line x1="9.5" y1="9.5" x2="6.5" y2="6.5"/><line x1="14.5" y1="14.5" x2="17.5" y2="17.5"/><line x1="9.5" y1="14.5" x2="6.5" y2="17.5"/>' },
			printing:          { paths: '<polyline points="6 9 6 2 18 2 18 9"/><path d="M6 18H4a2 2 0 01-2-2v-5a2 2 0 012-2h16a2 2 0 012 2v5a2 2 0 01-2 2h-2"/><rect x="6" y="14" width="12" height="8"/>' },
			system:            { paths: '<rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/><line x1="6" y1="8" x2="10" y2="8"/><line x1="6" y1="11" x2="14" y2="11"/>' },
			users:             { paths: '<path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 00-3-3.87"/><path d="M16 3.13a4 4 0 010 7.75"/>' },
			website:           { paths: '<circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 014 10 15.3 15.3 0 01-4 10 15.3 15.3 0 01-4-10 15.3 15.3 0 014-10z"/>' },
			/* --- HRMS sub-modules --- */
			attendance:        { paths: '<rect x="3" y="4" width="18" height="18" rx="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/><polyline points="9 16 11 18 15 14"/>' },
			"shift & attendance": { paths: '<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>' },
			"shift &amp; attendance": { paths: '<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>' },
			leaves:            { paths: '<path d="M6 3v18"/><path d="M6 8c6-2 10 2 16 0"/><path d="M6 15c6-2 10 2 16 0"/>' },
			payroll:           { paths: '<rect x="2" y="4" width="20" height="16" rx="2"/><line x1="2" y1="10" x2="22" y2="10"/><line x1="12" y1="4" x2="12" y2="20"/><line x1="6" y1="7" x2="6" y2="7.01" stroke-width="2"/><line x1="6" y1="14" x2="9" y2="14"/><line x1="6" y1="17" x2="8" y2="17"/><line x1="15" y1="14" x2="18" y2="14"/><line x1="15" y1="17" x2="19" y2="17"/>' },
			people:            { paths: '<path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 00-3-3.87"/><path d="M16 3.13a4 4 0 010 7.75"/>' },
			employees:         { paths: '<path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 00-3-3.87"/><path d="M16 3.13a4 4 0 010 7.75"/>' },
			recruitment:       { paths: '<circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/><path d="M11 8v6"/><path d="M8 11h6"/>' },
			performance:       { paths: '<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>' },
			expenses:          { paths: '<line x1="12" y1="1" x2="12" y2="23"/><path d="M17 5H9.5a3.5 3.5 0 000 7h5a3.5 3.5 0 010 7H6"/>' },
			tenure:            { paths: '<rect x="3" y="4" width="18" height="18" rx="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/><line x1="8" y1="14" x2="8" y2="14.01" stroke-width="2"/><line x1="12" y1="14" x2="12" y2="14.01" stroke-width="2"/><line x1="16" y1="14" x2="16" y2="14.01" stroke-width="2"/><line x1="8" y1="18" x2="8" y2="18.01" stroke-width="2"/>' },
			"tax & benefits":  { paths: '<path d="M4 2h16a2 2 0 012 2v16a2 2 0 01-2 2H4a2 2 0 01-2-2V4a2 2 0 012-2z"/><path d="M16 8l-8 8"/><circle cx="9" cy="9" r="1.5"/><circle cx="15" cy="15" r="1.5"/>' },
			"tax &amp; benefits": { paths: '<path d="M4 2h16a2 2 0 012 2v16a2 2 0 01-2 2H4a2 2 0 01-2-2V4a2 2 0 012-2z"/><path d="M16 8l-8 8"/><circle cx="9" cy="9" r="1.5"/><circle cx="15" cy="15" r="1.5"/>' },
			_fallback:         '<path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/>'
		},

		/* ============================================================
		   SILK — minimal geometric icons, visually distinct
		   ============================================================ */
		silk: {
			buying:            { paths: '<path d="M6 2L3 6v14a2 2 0 002 2h14a2 2 0 002-2V6l-3-4z"/><line x1="3" y1="6" x2="21" y2="6"/><path d="M16 10a4 4 0 01-8 0"/>' },
			selling:           { paths: '<line x1="12" y1="20" x2="12" y2="10"/><polyline points="18 14 12 20 6 14"/><line x1="4" y1="4" x2="20" y2="4"/>' },
			stock:             { paths: '<rect x="2" y="2" width="8" height="8" rx="1"/><rect x="14" y="2" width="8" height="8" rx="1"/><rect x="2" y="14" width="8" height="8" rx="1"/><rect x="14" y="14" width="8" height="8" rx="1"/>' },
			manufacturing:     { paths: '<circle cx="12" cy="12" r="3"/><path d="M12 1v4"/><path d="M12 19v4"/><path d="M4.22 4.22l2.83 2.83"/><path d="M16.95 16.95l2.83 2.83"/><path d="M1 12h4"/><path d="M19 12h4"/><path d="M4.22 19.78l2.83-2.83"/><path d="M16.95 7.05l2.83-2.83"/>' },
			projects:          { paths: '<rect x="3" y="3" width="18" height="18" rx="2"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="9" y1="21" x2="9" y2="9"/>' },
			quality:           { paths: '<path d="M22 11.08V12a10 10 0 11-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/>' },
			assets:            { paths: '<rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="12" cy="12" r="3"/><line x1="12" y1="8" x2="12" y2="3"/>' },
			subcontracting:    { paths: '<circle cx="9" cy="7" r="4"/><circle cx="15" cy="7" r="4"/><path d="M3 21v-2a4 4 0 014-4h2"/><path d="M15 15h2a4 4 0 014 4v2"/>' },
			organization:      { paths: '<rect x="3" y="3" width="18" height="18" rx="2"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="3" y1="15" x2="21" y2="15"/><line x1="12" y1="3" x2="12" y2="21"/>' },
			accounting:        { paths: '<rect x="4" y="4" width="16" height="16" rx="2"/><line x1="4" y1="12" x2="20" y2="12"/><line x1="12" y1="4" x2="12" y2="20"/><line x1="8" y1="8" x2="8" y2="8.01" stroke-width="2.5"/><line x1="16" y1="16" x2="16" y2="16.01" stroke-width="2.5"/>' },
			framework:         { paths: '<polygon points="12 2 22 8.5 22 15.5 12 22 2 15.5 2 8.5 12 2"/><line x1="12" y1="22" x2="12" y2="15.5"/><polyline points="22 8.5 12 15.5 2 8.5"/>' },
			"erpnext settings": { paths: '<line x1="4" y1="21" x2="4" y2="14"/><line x1="4" y1="10" x2="4" y2="3"/><line x1="12" y1="21" x2="12" y2="12"/><line x1="12" y1="8" x2="12" y2="3"/><line x1="20" y1="21" x2="20" y2="16"/><line x1="20" y1="12" x2="20" y2="3"/><line x1="1" y1="14" x2="7" y2="14"/><line x1="9" y1="8" x2="15" y2="8"/><line x1="17" y1="16" x2="23" y2="16"/>' },
			settings:          { paths: '<line x1="4" y1="21" x2="4" y2="14"/><line x1="4" y1="10" x2="4" y2="3"/><line x1="12" y1="21" x2="12" y2="12"/><line x1="12" y1="8" x2="12" y2="3"/><line x1="20" y1="21" x2="20" y2="16"/><line x1="20" y1="12" x2="20" y2="3"/><line x1="1" y1="14" x2="7" y2="14"/><line x1="9" y1="8" x2="15" y2="8"/><line x1="17" y1="16" x2="23" y2="16"/>' },
			"accounts setup":  { paths: '<line x1="4" y1="21" x2="4" y2="14"/><line x1="4" y1="10" x2="4" y2="3"/><line x1="12" y1="21" x2="12" y2="12"/><line x1="12" y1="8" x2="12" y2="3"/><line x1="20" y1="21" x2="20" y2="16"/><line x1="20" y1="12" x2="20" y2="3"/><line x1="1" y1="14" x2="7" y2="14"/><line x1="9" y1="8" x2="15" y2="8"/><line x1="17" y1="16" x2="23" y2="16"/>' },
			"account setup":   { paths: '<line x1="4" y1="21" x2="4" y2="14"/><line x1="4" y1="10" x2="4" y2="3"/><line x1="12" y1="21" x2="12" y2="12"/><line x1="12" y1="8" x2="12" y2="3"/><line x1="20" y1="21" x2="20" y2="16"/><line x1="20" y1="12" x2="20" y2="3"/><line x1="1" y1="14" x2="7" y2="14"/><line x1="9" y1="8" x2="15" y2="8"/><line x1="17" y1="16" x2="23" y2="16"/>' },
			hr:                { paths: '<path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"/><circle cx="12" cy="7" r="4"/>' },
			"hr module":       { paths: '<path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"/><circle cx="12" cy="7" r="4"/>' },
			"frappe hr":       { paths: '<path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"/><circle cx="12" cy="7" r="4"/>' },
			invoicing:         { paths: '<path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/>' },
			payments:          { paths: '<rect x="2" y="5" width="20" height="14" rx="2"/><line x1="2" y1="10" x2="22" y2="10"/>' },
			"financial reports": { paths: '<line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/>' },
			taxes:             { paths: '<line x1="12" y1="1" x2="12" y2="23"/><path d="M17 5H9.5a3.5 3.5 0 000 7h5a3.5 3.5 0 010 7H6"/>' },
			banking:           { paths: '<rect x="2" y="4" width="20" height="16" rx="2"/><path d="M6 8h.01"/><path d="M10 8h.01"/><path d="M14 8h.01"/><line x1="2" y1="12" x2="22" y2="12"/>' },
			budget:            { paths: '<polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/>' },
			"share management": { paths: '<circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/>' },
			subscription:      { paths: '<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22 6 12 13 2 6"/>' },
			crm:               { paths: '<path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"/><circle cx="12" cy="7" r="4"/>' },
			employees:         { paths: '<path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"/><circle cx="12" cy="7" r="4"/>' },
			support:           { paths: '<circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 015.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/>' },
			_fallback:         '<circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/>'
		}
	};

	/* ==================================================================
	   Helpers
	   ================================================================== */

	function currentTheme() {
		return document.documentElement.getAttribute("data-jey-theme") || "chrome";
	}

	function getIconDef(key) {
		var theme = currentTheme();
		var icons = THEME_ICONS[theme] || THEME_ICONS.chrome;
		var icon = key && icons[key];
		if (icon) return icon;
		// fallback to chrome if silk doesn't have the key
		icon = key && THEME_ICONS.chrome[key];
		if (icon) return icon;
		return icons._fallback || THEME_ICONS.chrome._fallback;
	}

	/** Returns true only if key has an explicit entry (not _fallback). */
	function hasIconDef(key) {
		if (!key) return false;
		var theme = currentTheme();
		var icons = THEME_ICONS[theme] || THEME_ICONS.chrome;
		if (icons[key] && key !== "_fallback") return true;
		if (THEME_ICONS.chrome[key] && key !== "_fallback") return true;
		return false;
	}

	function makeSvg(icon) {
		var paths = typeof icon === "string" ? icon : icon.paths;
		var cls = (typeof icon === "object" && icon.cls) ? icon.cls : "";
		var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
		svg.setAttribute("viewBox", "0 0 24 24");
		svg.setAttribute("class", "jey-icon" + (cls ? " " + cls : ""));
		svg.innerHTML = paths;
		return svg;
	}

	function getIconKey(el) {
		var desktop = el.closest(".desktop-icon");
		if (!desktop) return null;
		var id = desktop.getAttribute("data-id");
		return id ? id.toLowerCase() : null;
	}

	function buildChrome() {
		var c = document.createElement("div");
		c.className = "jey-chrome";
		c.innerHTML =
			'<div class="jey-chrome-ring"><div class="jey-ring-glow"></div></div>' +
			'<div class="jey-chrome-gap"></div>' +
			'<div class="jey-chrome-ring-inner"></div>' +
			'<div class="jey-chrome-face">' +
			'<div class="jey-face-glow"></div>' +
			'<div class="jey-face-reflection"></div>' +
			"</div>";
		return { chrome: c, face: c.querySelector(".jey-chrome-face") };
	}

	/* ==================================================================
	   Icon processing
	   ================================================================== */

	function wrapIcon(iconContainer) {
		if (iconContainer.classList.contains("jey-chrome-done")) return;
		iconContainer.classList.add("jey-chrome-done");

		// Hide original children
		Array.from(iconContainer.children).forEach(function (child) {
			child.style.display = "none";
		});

		var parts = buildChrome();
		var key = getIconKey(iconContainer);
		parts.face.appendChild(makeSvg(getIconDef(key)));
		iconContainer.appendChild(parts.chrome);
	}

	function processIcons() {
		document.querySelectorAll(
			".desktop-icon > .icon-container:not(.jey-chrome-done)"
		).forEach(function (c) {
			if (c.closest(".folder-icon")) return;
			wrapIcon(c);
		});

		document.querySelectorAll(
			".desktop-icon > .icon-container.folder-icon:not(.jey-chrome-done)"
		).forEach(wrapIcon);

		staggerAnimations();
	}

	function staggerAnimations() {
		document.querySelectorAll(".jey-ring-glow").forEach(function (el, i) {
			el.style.animationDelay = i * -0.6 + "s";
		});
		document.querySelectorAll(".jey-face-glow").forEach(function (el, i) {
			el.style.animationDelay = i * -0.6 + "s";
		});
		document.querySelectorAll(".jey-face-reflection").forEach(function (el, i) {
			el.style.animationDelay = i * -0.8 + "s";
		});
	}

	/* ==================================================================
	   Context menu icon replacement
	   Replaces default ERPNext icons in .frappe-menu dropdowns with
	   the current theme's SVG icons (same as desktop).
	   ================================================================== */

	/* Menu titles are localized (e.g. Azerbaijani "Mühasibat uçotu"), so the
	   lookup key is taken from the untranslated img src filename first, then
	   from a reverse-lookup over frappe.boot.desktop_icons by __(label), and
	   only last from the displayed text as a best-effort fallback. */
	function resolveEnglishKey(item) {
		var img = item.querySelector("img.logo[src]");
		if (img) {
			var m = img.getAttribute("src").match(/\/([^\/]+)\.svg(\?|$)/);
			if (m) return m[1].replace(/_/g, " ").toLowerCase();
		}
		var titleEl = item.querySelector(".menu-item-title, .header-title");
		if (!titleEl) return null;
		var titleText = titleEl.textContent.trim();
		if (window.frappe && frappe.boot && Array.isArray(frappe.boot.desktop_icons) && typeof __ === "function") {
			for (var i = 0; i < frappe.boot.desktop_icons.length; i++) {
				var dIcon = frappe.boot.desktop_icons[i];
				if (dIcon && dIcon.label && __(dIcon.label) === titleText) {
					return dIcon.label.toLowerCase();
				}
			}
		}
		return titleText.toLowerCase();
	}

	function processContextMenuIcons() {
		document.querySelectorAll(".frappe-menu .dropdown-menu-item").forEach(function (item) {
			var iconDiv = item.querySelector(".menu-item-icon");
			if (!iconDiv || iconDiv.querySelector(".jey-icon")) return;

			// Skip chevron icons (the second .menu-item-icon with margin-left:auto)
			if (iconDiv.style.marginLeft === "auto") return;

			var key = resolveEnglishKey(item);
			if (!hasIconDef(key)) return;

			iconDiv.innerHTML = "";
			iconDiv.appendChild(makeSvg(getIconDef(key)));
		});
	}

	/* ==================================================================
	   Frappe-menu lucide icon remap
	   Some dropdowns (the user/settings menu at the top-left) render as
	   <svg><use href="#icon-NAME"> from the lucide sprite. Rewrite those
	   to semantically-close-but-different sprite ids so the theme feels
	   distinct from stock Frappe. Idempotent via .jey-menu-remap-done.
	   ================================================================== */

	var MENU_LUCIDE_ICON_MAP = {
		"home":               "house",
		"sliders-horizontal": "settings-2",
		"rotate-ccw":         "refresh-ccw",
		"maximize":           "chevrons-left-right",
		"moon":               "moon-star",
		"info":               "life-buoy",
		"logout":             "log-out"
	};

	function processMenuLucideIcons() {
		document.querySelectorAll(".frappe-menu .dropdown-menu-item").forEach(function (item) {
			if (item.classList.contains("jey-menu-remap-done")) return;
			item.classList.add("jey-menu-remap-done");

			item.querySelectorAll(".menu-item-icon svg > use[href^='#icon-']").forEach(function (useEl) {
				// Leave chevron-right (submenu indicator) alone.
				var from = useEl.getAttribute("href").slice("#icon-".length);
				if (from === "chevron-right") return;
				var to = MENU_LUCIDE_ICON_MAP[from];
				if (!to || to === from) return;
				var newHref = "#icon-" + to;
				useEl.setAttribute("href", newHref);
				if (useEl.hasAttribute("xlink:href")) {
					useEl.setAttribute("xlink:href", newHref);
				}
			});
		});
	}

	/* ==================================================================
	   Sidebar header icon replacement
	   Replaces <img> / alphabet SVGs inside .sidebar-header with
	   the current theme's SVG icon.
	   ================================================================== */

	function processSidebarIcons() {
		document.querySelectorAll(".sidebar-header").forEach(function (header) {
			var logo = header.querySelector(".header-logo");
			if (!logo || logo.querySelector(".jey-icon")) return;

			var key = resolveEnglishKey(header);
			if (!hasIconDef(key)) return;

			logo.innerHTML = "";
			logo.appendChild(makeSvg(getIconDef(key)));
		});
	}

	/* ==================================================================
	   Workspace sidebar item icon remap
	   Rewrites <use href="#icon-NAME"> inside .sidebar-item-icon[item-icon]
	   to point at a different but semantically related sprite id.
	   Idempotent via .jey-side-item-done marker class.
	   ================================================================== */

	var SIDEBAR_ITEM_ICON_MAP = {
		/* ERPNext / timeless legacy names */
		"home":               "house",
		"accounting":         "landmark",
		"assets":             "warehouse",
		"buying":             "shopping-cart",
		"crm":                "handshake",
		"customer":           "contact-round",
		"customization":      "palette",
		"expenses":           "banknote-arrow-up",
		"getting-started":    "rocket",
		"hr":                 "users-round",
		"integration":        "plug-2",
		"liabilities":        "scale",
		"loan":               "piggy-bank",
		"money-coins-1":      "coins",
		"non-profit":         "heart-handshake",
		"organization":       "building-2",
		"project":            "folder-kanban",
		"projects":           "kanban",
		"quality":            "badge-check",
		"sell":               "trending-up",
		"setting":            "settings-2",
		"stock":              "package-2",
		"support":            "headphones",
		"tool":               "wrench",
		"web":                "earth",
		"website":            "globe",
		"chart":              "chart-column",
		"criticize":          "thumbs-down",
		"review":             "book-open-check",
		"image-view":         "image",
		"up-arrow":           "arrow-up",
		"table_2":            "table-properties",
		"book-text":          "library",

		/* Lucide names found in workspace_sidebar fixtures */
		"arrow-down-from-line":   "download",
		"arrow-left-to-line":     "panel-left-close",
		"arrow-right-from-line":  "log-out",
		"at-sign":                "mail",
		"badge-alert":            "triangle-alert",
		"badge-cent":             "circle-dollar-sign",
		"banknote-arrow-up":      "trending-up",
		"banknote-x":             "receipt",
		"bell":                   "bell-ring",
		"bell-dot":               "bell-ring",
		"bell-ring":              "megaphone",
		"book-open-check":        "clipboard-check",
		"book-open-text":         "book-marked",
		"book-user":              "contact-round",
		"briefcase-business":     "briefcase",
		"building-2":             "landmark",
		"cable":                  "network",
		"calendar-check":         "calendar-check-2",
		"calendar-check-2":       "calendar-fold",
		"calendar-clock":         "timer",
		"calendar-days":          "calendar-range",
		"calendar-heart":         "heart",
		"calendar-range":         "calendar-fold",
		"calendar-sync":          "rotate-cw",
		"caravan":                "tent",
		"circle-dollar-sign":     "coins",
		"circle-user-round":      "user-round",
		"clipboard-check":        "clipboard-list",
		"clipboard-pen":          "notebook-pen",
		"computer":               "monitor",
		"database":               "hard-drive",
		"database-backup":        "save",
		"dollar-sign":            "coins",
		"earth":                  "globe",
		"factory":                "warehouse",
		"file":                   "file-text",
		"file-lock":              "shield",
		"file-question-mark":     "file-text",
		"file-up":                "upload",
		"file-user":              "contact-round",
		"flag":                   "flame",
		"folder-tree":            "list-tree",
		"funnel":                 "filter",
		"git-pull-request-arrow": "git-branch",
		"goal":                   "target",
		"graduation-cap":         "school",
		"grid-2x2-check":         "layout-grid",
		"hammer":                 "wrench",
		"heading":                "text",
		"heart-pulse":            "stethoscope",
		"import":                 "file-up",
		"inspection-panel":       "clipboard-check",
		"laptop":                 "monitor",
		"layout-dashboard":       "gauge",
		"lightbulb":              "zap",
		"link":                   "link-2",
		"list":                   "list-checks",
		"list-end":               "list-ordered",
		"list-todo":              "clipboard-list",
		"list-tree":              "folder-tree",
		"lock-keyhole":           "shield",
		"lock-keyhole-open":      "shield-alert",
		"logs":                   "scroll-text",
		"mail":                   "mail-open",
		"mail-x":                 "inbox",
		"message-square-reply":   "speech",
		"milestone":              "flag",
		"monitor-check":          "square-check",
		"move-horizontal":        "arrow-left-right",
		"notebook-pen":           "square-pen",
		"notebook-text":          "notepad-text",
		"notepad-text":           "scroll-text",
		"orbit":                  "atom",
		"panel-bottom-close":     "panel-top-open",
		"panel-top-close":        "panel-bottom-open",
		"pen-tool":               "pen-line",
		"person-standing":        "user-round",
		"piggy-bank":             "vault",
		"plane":                  "send",
		"pointer":                "mouse-pointer-2",
		"printer":                "file-output",
		"receipt":                "receipt-text",
		"receipt-text":           "scroll-text",
		"repeat":                 "rotate-cw",
		"repeat-2":               "rotate-ccw",
		"scroll-text":            "book-text",
		"server":                 "database",
		"settings":               "sliders-horizontal",
		"sheet":                  "file-spreadsheet",
		"split":                  "git-branch",
		"sprout":                 "leaf",
		"square-activity":        "heart-pulse",
		"square-pen":             "pen-line",
		"square-user-round":      "contact-round",
		"star":                   "sparkles",
		"table":                  "table-2",
		"thumbs-up":              "heart",
		"trash":                  "archive",
		"trending-down":          "chart-line",
		"triangle":               "triangle-alert",
		"truck":                  "ship",
		"tv-minimal":             "monitor",
		"upload":                 "cloud-upload",
		"user":                   "user-round",
		"user-cog":               "user-round-cog",
		"user-lock":              "shield",
		"user-round":             "circle-user-round",
		"user-round-check":       "user-check",
		"user-round-minus":       "user-x",
		"user-star":              "award",
		"users":                  "users-round",
		"users-round":            "contact-round",
		"videotape":              "film",
		"wallpaper":              "image",
		"washing-machine":        "container",
		"webhook":                "cable"
	};

	function processSidebarItemIcons() {
		var nodes = document.querySelectorAll(
			".body-sidebar .sidebar-item-icon[item-icon]:not(.jey-side-item-done)"
		);
		if (!nodes.length) return;

		for (var i = 0; i < nodes.length; i++) {
			var span = nodes[i];
			var from = (span.getAttribute("item-icon") || "").trim().toLowerCase();

			// Always mark done — even on unknown / identity — so we never revisit.
			span.classList.add("jey-side-item-done");

			var to = SIDEBAR_ITEM_ICON_MAP[from];
			if (!to || to === from) continue;

			var useEl = span.querySelector("svg > use");
			if (!useEl) continue;

			var newHref = "#icon-" + to;
			if (useEl.getAttribute("href") !== newHref) {
				useEl.setAttribute("href", newHref);
			}
			if (useEl.hasAttribute("xlink:href") &&
				useEl.getAttribute("xlink:href") !== newHref) {
				useEl.setAttribute("xlink:href", newHref);
			}
			span.setAttribute("data-jey-orig-icon", from);
			span.setAttribute("item-icon", to);
		}
	}

	/* ==================================================================
	   Rebuild all icons (called on theme switch)
	   ================================================================== */

	function rebuildAllIcons() {
		document.querySelectorAll(".icon-container.jey-chrome-done").forEach(function (c) {
			// Remove chrome DOM
			var chrome = c.querySelector(".jey-chrome");
			if (chrome) chrome.remove();
			c.classList.remove("jey-chrome-done");

			// Restore original children visibility (they stay hidden,
			// wrapIcon will re-hide them anyway, but we need the class removed)
		});
		// Reset context menu / sidebar icons so they rebuild with new theme SVGs
		document.querySelectorAll(".frappe-menu .menu-item-icon .jey-icon").forEach(function (svg) {
			svg.remove();
		});
		document.querySelectorAll(".sidebar-header .header-logo .jey-icon").forEach(function (svg) {
			svg.remove();
		});
		processIcons();
		processContextMenuIcons();
		processMenuLucideIcons();
		processSidebarIcons();
		processSidebarItemIcons();
	}

	// Expose for theme switcher
	window.jeyRebuildIcons = rebuildAllIcons;

	/* ==================================================================
	   Boot
	   ================================================================== */

	function processAll() { processIcons(); processContextMenuIcons(); processMenuLucideIcons(); processSidebarIcons(); processSidebarItemIcons(); }

	$(document).on("page-change", processAll);

	var attempts = 0;
	var poll = setInterval(function () {
		processAll();
		attempts++;
		if (attempts > 30) clearInterval(poll);
	}, 300);

	var observer = new MutationObserver(processAll);
	observer.observe(document.body, { childList: true, subtree: true });
})();

/* ==========================================================================
   Toggle Full Width button in sidebar (above Collapse)
   ========================================================================== */

(function () {
	"use strict";

	// Insert our labeled sidebar links at the top of .body-sidebar-bottom.
	// We don't anchor on .collapse-sidebar-link anymore — Frappe brought it
	// back in v16.17 as a tiny icon-button inside a horizontal flex row
	// (.sidebar-bottom-actions), so inserting before it would push our links
	// into that row and overflow the sidebar.
	window.jeySidebarAnchor = window.jeySidebarAnchor || function () {
		var bottom = document.querySelector(".body-sidebar .body-sidebar-bottom");
		if (bottom) return { parent: bottom, before: bottom.firstChild };
		return null;
	};

	function addFullWidthButton() {
		if (document.querySelector(".jey-fullwidth-link")) return;
		var anchor = window.jeySidebarAnchor();
		if (!anchor) return;

		var link = document.createElement("a");
		link.className = "jey-fullwidth-link";

		function updateIcon() {
			link.innerHTML =
				frappe.utils.icon("chevrons-left-right", "sm", "", "", "text-ink-gray-7 current-color", true) +
				"<span>" + __("Full Width") + "</span>";
		}

		link.addEventListener("click", function () {
			frappe.ui.toolbar.toggle_full_width();
			updateIcon();
		});

		updateIcon();
		anchor.parent.insertBefore(link, anchor.before);
	}

	$(document).ready(addFullWidthButton);
	$(document).on("page-change", addFullWidthButton);
})();

/* ==========================================================================
   Toggle Theme button in sidebar (above Collapse)
   ========================================================================== */

(function () {
	"use strict";

	function addThemeSwitchButton() {
		if (document.querySelector(".jey-theme-switch-link")) return;
		var anchor = window.jeySidebarAnchor && window.jeySidebarAnchor();
		if (!anchor) return;

		var link = document.createElement("a");
		link.className = "jey-theme-switch-link";
		link.innerHTML =
			frappe.utils.icon("palette", "sm", "", "", "text-ink-gray-7 current-color", true) +
			"<span>" + __("Switch Theme") + "</span>";

		link.addEventListener("click", function () {
			new frappe.ui.ThemeSwitcher().show();
		});

		anchor.parent.insertBefore(link, anchor.before);
	}

	$(document).ready(addThemeSwitchButton);
	$(document).on("page-change", addThemeSwitchButton);
})();

/* ==========================================================================
   Collapse / Expand Sidebar button (restored; v16 removed the default)
   ========================================================================== */

(function () {
	"use strict";

	function isExpanded() {
		var c = document.querySelector(".body-sidebar-container");
		return !!(c && c.classList.contains("expanded"));
	}

	function render(link) {
		var expanded = isExpanded();
		var iconName = expanded ? "chevrons-left" : "chevrons-right";
		var label = expanded ? __("Collapse Sidebar") : __("Expand Sidebar");

		var span = link.querySelector("span");
		var use = link.querySelector("svg use");

		if (!span || !use) {
			// First render — build the DOM.
			link.innerHTML =
				frappe.utils.icon(iconName, "sm", "", "", "text-ink-gray-7 current-color", true) +
				"<span>" + label + "</span>";
			return;
		}
		// Update in place so the span keeps its identity and the
		// max-width transition plays smoothly across collapse/expand.
		use.setAttribute("href", "#icon-" + iconName);
		span.textContent = label;
	}

	function addCollapseButton() {
		if (document.querySelector(".jey-collapse-link")) return;
		var anchor = window.jeySidebarAnchor && window.jeySidebarAnchor();
		if (!anchor) return;

		var link = document.createElement("a");
		link.className = "jey-collapse-link";
		render(link);

		link.addEventListener("click", function () {
			if (frappe.app && frappe.app.sidebar && frappe.app.sidebar.toggle_width) {
				frappe.app.sidebar.toggle_width();
			}
			// toggle_width updates class synchronously; re-render
			render(link);
		});

		anchor.parent.insertBefore(link, anchor.before);

		// Keep label in sync if sidebar state changes elsewhere
		$(document).off("sidebar-expand.jey-collapse").on("sidebar-expand.jey-collapse", function () {
			var el = document.querySelector(".jey-collapse-link");
			if (el) render(el);
		});
	}

	$(document).ready(addCollapseButton);
	$(document).on("page-change", addCollapseButton);
})();

/* ==========================================================================
   Hide "Website" item from sidebar user dropdown
   ========================================================================== */

(function () {
	"use strict";

	function tagWebsiteItem() {
		var label = __("Website");
		document.querySelectorAll(".dropdown-menu-item:not(.jey-hidden-item)").forEach(function (el) {
			var title = el.querySelector(".menu-item-title");
			if (title && title.textContent.trim() === label) {
				el.classList.add("jey-hidden-item");
			}
		});
	}

	$(document).ready(tagWebsiteItem);
	var observer = new MutationObserver(tagWebsiteItem);
	observer.observe(document.body, { childList: true, subtree: true });
})();

/* ==========================================================================
   Form sidebar: hidden by default
   Frappe shows the form sidebar for saved docs; we hide it by default and
   remember the user's explicit toggle via localStorage.
   ========================================================================== */
(function () {
	"use strict";

	var STORAGE_KEY = "jey-form-sidebar"; // "hidden" | "visible"

	function getPreference() {
		return localStorage.getItem(STORAGE_KEY) === "visible" ? "visible" : "hidden";
	}

	function applyPreference(frm) {
		if (!frm || !frm.sidebar || !frm.sidebar.sidebar) return;
		if (frm.doc && frm.doc.__islocal) return; // Frappe already hides new docs
		var wrap = frm.sidebar.sidebar.parent();
		if (!wrap || !wrap.length) return;
		var page_sidebar = frm.sidebar.page && frm.sidebar.page.sidebar;
		if (getPreference() === "hidden") {
			wrap.hide();
			if (page_sidebar) page_sidebar.addClass("hide-sidebar");
		} else {
			wrap.show();
			if (page_sidebar) page_sidebar.removeClass("hide-sidebar");
		}
	}

	$(document).on("form-refresh", function (e, frm) {
		applyPreference(frm);
	});

	// When user clicks "Toggle Sidebar", persist the new state
	$(document.body).on("toggleSidebar", function () {
		var frm = window.cur_frm;
		if (!frm || !frm.sidebar || !frm.sidebar.sidebar) return;
		var wrap = frm.sidebar.sidebar.parent();
		if (!wrap || !wrap.length) return;
		localStorage.setItem(STORAGE_KEY, wrap.is(":visible") ? "visible" : "hidden");
	});
})();

/* ==========================================================================
   Reload button next to Notifications in sidebar
   ========================================================================== */

(function () {
	"use strict";

	function addReloadButton() {
		var notif = document.querySelector(".sidebar-notification");
		if (!notif || document.querySelector(".jey-reload-btn")) return;

		var btn = notif.cloneNode(true);
		btn.classList.remove("sidebar-notification");
		btn.classList.remove("hidden");
		btn.classList.add("jey-reload-btn");
		btn.removeAttribute("id");
		btn.setAttribute("title", __("Reload"));

		var iconWrap = btn.querySelector(".sidebar-item-icon");
		if (iconWrap) {
			iconWrap.setAttribute("item-icon", "rotate-ccw");
			iconWrap.innerHTML = frappe.utils.icon(
				"rotate-ccw", "sm", "", "", "text-ink-gray-7 current-color", true
			);
		}
		var label = btn.querySelector(".sidebar-item-label");
		if (label) label.textContent = __("Reload");

		btn.addEventListener("click", function (e) {
			e.preventDefault();
			e.stopPropagation();
			frappe.ui.toolbar.clear_cache();
		});

		notif.parentNode.insertBefore(btn, notif.nextSibling);
	}

	$(document).ready(function () {
		addReloadButton();
		var tries = 0;
		var poll = setInterval(function () {
			addReloadButton();
			if (++tries > 30 || document.querySelector(".jey-reload-btn")) {
				clearInterval(poll);
			}
		}, 300);
	});
	$(document).on("page-change", addReloadButton);
})();

/* ==========================================================================
   Fix: nested menus left orphaned when parent menu is toggled closed
   (sidebar-header app-switcher → Workspaces submenu stays on screen)
   ========================================================================== */

(function () {
	"use strict";

	function patchMenuHide() {
		if (!window.frappe || !frappe.ui || !frappe.ui.menu) return false;
		var proto = frappe.ui.menu.prototype;
		if (proto.__jeyHidePatched) return true;
		var origHide = proto.hide;
		proto.hide = function () {
			if (this.nested_menus && this.nested_menus.length) {
				this.nested_menus.forEach(function (m) { m && m.hide && m.hide(); });
			}
			this.current_menu = null;
			return origHide.apply(this, arguments);
		};
		proto.__jeyHidePatched = true;
		return true;
	}

	if (!patchMenuHide()) {
		var tries = 0;
		var poll = setInterval(function () {
			if (patchMenuHide() || ++tries > 50) clearInterval(poll);
		}, 200);
	}
})();

/* ==========================================================================
   Override Frappe Theme Switcher — Jey themes
   ========================================================================== */

(function () {
	"use strict";

	var JEY_THEMES = [
		{
			name: "chrome",
			label: "Chrome",
			info: "Metallic chrome buttons with animated rings",
		},
		{
			name: "silk",
			label: "Silk",
			info: "Soft silk & champagne gold",
		},
	];

	function getCurrentJeyTheme() {
		return document.documentElement.getAttribute("data-jey-theme") || "chrome";
	}

	function setJeyTheme(name) {
		document.documentElement.setAttribute("data-jey-theme", name);
		localStorage.setItem("jey-theme", name);
		// Rebuild icons with the new theme's SVGs
		if (window.jeyRebuildIcons) window.jeyRebuildIcons();
	}

	$(document).ready(function () {
		if (!frappe || !frappe.ui) return;

		frappe.ui.ThemeSwitcher = class JeyThemeSwitcher {
			constructor() {
				this.current = getCurrentJeyTheme();
				this.setup_dialog();
			}

			setup_dialog() {
				this.dialog = new frappe.ui.Dialog({
					title: __("Switch Theme"),
				});
				this.body = $('<div class="jey-theme-grid"></div>').appendTo(
					this.dialog.$body
				);
				this.render();
				this.bind_keys();
			}

			bind_keys() {
				this.dialog.$wrapper.on("keydown", (e) => {
					var key = frappe.ui.keys.get_key(e);
					if (key === "left" || key === "right") {
						var idx = JEY_THEMES.findIndex((t) => t.name === this.current);
						var next = key === "right" ? idx + 1 : idx - 1;
						if (JEY_THEMES[next]) this.select(JEY_THEMES[next].name);
						return false;
					}
					if (e.keyCode === 13) { this.dialog.hide(); return false; }
				});
			}

			render() {
				var me = this;
				this.body.empty();

				JEY_THEMES.forEach(function (theme) {
					var selected = me.current === theme.name ? "selected" : "";
					var $card = $([
						'<div class="jey-theme-card ' + selected + '" data-theme="' + theme.name + '">',
						'  <div class="jey-theme-preview jey-theme-preview--' + theme.name + '">',
						'    <div class="jey-theme-preview-icon"></div>',
						"  </div>",
						'  <div class="jey-theme-label">' + __(theme.label) + "</div>",
						'  <div class="jey-theme-info">' + __(theme.info) + "</div>",
						"</div>",
					].join(""));

					$card.on("click", function () { me.select(theme.name); });
					me.body.append($card);
				});
			}

			select(name) {
				this.current = name;
				setJeyTheme(name);
				this.body.find(".jey-theme-card").removeClass("selected");
				this.body.find('.jey-theme-card[data-theme="' + name + '"]').addClass("selected");
				frappe.show_alert(__("Theme: " + name), 3);
			}

			show() {
				this.current = getCurrentJeyTheme();
				this.render();
				this.dialog.show();
			}

			hide() {
				this.dialog.hide();
			}
		};
	});
})();

/* ==========================================================================
   Grid Column Resize — drag handles on table header columns
   Uses a persistent <style> tag so widths survive Frappe DOM rebuilds.
   ========================================================================== */

(function () {
	"use strict";

	var MIN_COL_WIDTH = 40;
	var STORAGE_PREFIX = "jey-col-widths:";

	// Reuse the <style> element created in the init IIFE
	var styleEl = document.getElementById("jey-col-resize-styles");

	// Runtime widths: { "Sales Invoice:items": { item_code: 200, ... }, ... }
	var allWidths = {};

	function rebuildStylesheet() {
		var rules = [];
		Object.keys(allWidths).forEach(function (gridKey) {
			var widths = allWidths[gridKey];
			Object.keys(widths).forEach(function (fieldname) {
				var w = widths[fieldname];
				rules.push(
					'[data-jey-grid="' + gridKey + '"] .grid-static-col[data-fieldname="' + fieldname + '"]' +
					" { flex: 0 0 " + w + "px !important; width: " + w + "px !important; max-width: none !important; }"
				);
			});
		});
		styleEl.textContent = rules.join("\n");
	}

	function getGridKey(grid) {
		if (grid.dataset.jeyGrid) return grid.dataset.jeyGrid;

		// Build stable key: "ParentDoctype:table_fieldname"
		var control = grid.closest(".frappe-control[data-fieldname]");
		var tableField = control ? control.getAttribute("data-fieldname") : null;
		var doctype = cur_frm ? cur_frm.doctype : null;

		var key = (doctype && tableField) ? doctype + ":" + tableField : null;
		if (key) {
			grid.dataset.jeyGrid = key;
			return key;
		}
		return null;
	}

	function saveWidths(gridKey) {
		if (!gridKey || !allWidths[gridKey]) return;
		localStorage.setItem(STORAGE_PREFIX + gridKey, JSON.stringify(allWidths[gridKey]));
	}

	function loadWidths(gridKey) {
		if (!gridKey) return null;
		var stored = localStorage.getItem(STORAGE_PREFIX + gridKey);
		if (stored) {
			try { return JSON.parse(stored); } catch (e) { return null; }
		}
		return null;
	}

	function addHandlesToGrid(grid) {
		var cols = grid.querySelectorAll(
			".grid-heading-row .grid-row:not(.filter-row) .grid-static-col"
		);
		if (!cols.length) return;

		var gridKey = getGridKey(grid);
		if (!gridKey) return;

		// Load saved widths from localStorage on first encounter
		if (!allWidths[gridKey]) {
			var saved = loadWidths(gridKey);
			if (saved) {
				allWidths[gridKey] = saved;
				rebuildStylesheet();
			}
		}

		cols.forEach(function (col) {
			if (col.querySelector(".jey-col-resize")) return;

			// Don't override position:sticky on sticky columns
			if (!col.classList.contains("sticky-grid-col") && !col.classList.contains("row-index")) {
				col.style.position = "relative";
			}
			var handle = document.createElement("div");
			handle.className = "jey-col-resize";
			col.appendChild(handle);

			handle.addEventListener("mousedown", function (e) {
				e.preventDefault();
				e.stopPropagation();
				startResize(grid, col, e.clientX);
			});

			handle.addEventListener("dblclick", function (e) {
				e.preventDefault();
				e.stopPropagation();
				autoFitColumn(grid, col);
			});
		});
	}

	function autoFitColumn(grid, headerCol) {
		var fieldname = headerCol.getAttribute("data-fieldname");
		var gridKey = getGridKey(grid);
		if (!fieldname || !gridKey) return;

		// Measure header text width
		var label = headerCol.querySelector(".static-area, .sort-header, span");
		var text = label ? label.textContent.trim() : fieldname;

		var canvas = document.createElement("canvas");
		var ctx = canvas.getContext("2d");
		var style = getComputedStyle(headerCol);
		ctx.font = style.fontWeight + " " + style.fontSize + " " + style.fontFamily;
		var textWidth = ctx.measureText(text).width;

		// Add padding (8px each side + 16px buffer for sort icon etc)
		var newWidth = Math.max(MIN_COL_WIDTH, Math.ceil(textWidth) + 32);

		if (!allWidths[gridKey]) allWidths[gridKey] = {};
		allWidths[gridKey][fieldname] = newWidth;
		rebuildStylesheet();
		saveWidths(gridKey);
	}

	function startResize(grid, headerCol, startX) {
		var fieldname = headerCol.getAttribute("data-fieldname");
		if (!fieldname) return;

		var gridKey = getGridKey(grid);
		if (!gridKey) return;

		var startWidth = headerCol.getBoundingClientRect().width;

		if (!allWidths[gridKey]) allWidths[gridKey] = {};
		var widths = allWidths[gridKey];

		// Lock all columns that aren't already saved
		grid.querySelectorAll(
			".grid-heading-row .grid-row:not(.filter-row) .grid-static-col"
		).forEach(function (c) {
			var fn = c.getAttribute("data-fieldname");
			if (fn && !widths[fn]) {
				widths[fn] = c.getBoundingClientRect().width;
			}
		});
		rebuildStylesheet();

		document.body.style.cursor = "col-resize";
		document.body.style.userSelect = "none";

		function onMove(e) {
			var newWidth = Math.max(MIN_COL_WIDTH, startWidth + (e.clientX - startX));
			widths[fieldname] = newWidth;
			rebuildStylesheet();
		}

		function onUp() {
			document.removeEventListener("mousemove", onMove);
			document.removeEventListener("mouseup", onUp);
			document.body.style.cursor = "";
			document.body.style.userSelect = "";
			saveWidths(gridKey);
		}

		document.addEventListener("mousemove", onMove);
		document.addEventListener("mouseup", onUp);
	}

	function processGrids() {
		document.querySelectorAll(".form-grid").forEach(addHandlesToGrid);
	}

	// Add handles when grids appear — only needs to run for new header columns
	var handleTimer = null;
	function scheduleHandles() {
		clearTimeout(handleTimer);
		handleTimer = setTimeout(processGrids, 100);
	}

	$(document).on("page-change", scheduleHandles);

	var observer = new MutationObserver(scheduleHandles);
	observer.observe(document.body, { childList: true, subtree: true });

	/* ==================================================================
	   Column Drag & Drop Reorder
	   Uses Frappe's native user_settings.GridView — order is applied
	   by Frappe BEFORE first render, so no flash.
	   ================================================================== */

	function getGridInstance(gridEl) {
		var control = gridEl.closest(".frappe-control[data-fieldname]");
		if (!control || typeof cur_frm === "undefined" || !cur_frm) return null;
		var fieldname = control.getAttribute("data-fieldname");
		var field = cur_frm.fields_dict[fieldname];
		return field && field.grid ? field.grid : null;
	}

	function saveColumnOrder(gridInstance, newOrder) {
		if (!gridInstance.frm) return;

		var settings = newOrder.map(function (fieldname) {
			var vc = gridInstance.visible_columns.find(function (v) {
				return v[0].fieldname === fieldname;
			});
			if (!vc) return null;
			return {
				fieldname: fieldname,
				columns: vc[1],
				sticky: vc[0].sticky ? 1 : 0
			};
		}).filter(Boolean);

		var parentDoctype = gridInstance.frm.doctype;
		var childDoctype = gridInstance.doctype;

		// Update in-memory cache for immediate re-render
		if (!frappe.model.user_settings[parentDoctype]) {
			frappe.model.user_settings[parentDoctype] = {};
		}
		if (!frappe.model.user_settings[parentDoctype].GridView) {
			frappe.model.user_settings[parentDoctype].GridView = {};
		}
		frappe.model.user_settings[parentDoctype].GridView[childDoctype] = settings;

		// Save to server directly (bypass comparison that would skip due to cache already updated)
		frappe.model.user_settings.update(parentDoctype, frappe.model.user_settings[parentDoctype]);
	}

	// Custom mouse-based drag (more reliable than HTML5 drag API)
	var dragState = null;

	function addDragListeners(gridEl) {
		var cols = gridEl.querySelectorAll(
			".grid-heading-row .grid-row:not(.filter-row) .grid-static-col[data-fieldname]"
		);

		cols.forEach(function (col) {
			if (col.dataset.jeyDraggable) return;
			col.dataset.jeyDraggable = "1";

			col.addEventListener("mousedown", function (e) {
				// Don't start drag from resize handle
				if (e.target.classList.contains("jey-col-resize")) return;
				// Only left button
				if (e.button !== 0) return;

				var fieldname = col.getAttribute("data-fieldname");
				if (!fieldname) return;

				dragState = {
					gridEl: gridEl,
					fromField: fieldname,
					startX: e.clientX,
					startY: e.clientY,
					dragging: false
				};
			});
		});
	}

	// Global mouse handlers for drag
	document.addEventListener("mousemove", function (e) {
		if (!dragState) return;

		// Start dragging after 5px movement (prevents accidental drag on click)
		if (!dragState.dragging) {
			var dx = Math.abs(e.clientX - dragState.startX);
			var dy = Math.abs(e.clientY - dragState.startY);
			if (dx < 5 && dy < 5) return;
			dragState.dragging = true;
			document.body.style.cursor = "grabbing";
			document.body.style.userSelect = "none";
		}

		// Find which header column the mouse is over
		var gridEl = dragState.gridEl;
		gridEl.querySelectorAll(".jey-drop-target").forEach(function (c) {
			c.classList.remove("jey-drop-target");
		});

		var target = document.elementFromPoint(e.clientX, e.clientY);
		if (target) {
			var col = target.closest(".grid-heading-row .grid-static-col[data-fieldname]");
			if (col && col.getAttribute("data-fieldname") !== dragState.fromField) {
				// Determine direction for indicator
				var headerCols = Array.from(gridEl.querySelectorAll(
					".grid-heading-row .grid-row:not(.filter-row) .grid-static-col[data-fieldname]"
				));
				var fi = headerCols.findIndex(function (c) { return c.getAttribute("data-fieldname") === dragState.fromField; });
				var ti = headerCols.indexOf(col);
				col.classList.add("jey-drop-target");
				col.classList.toggle("jey-drop-right", fi < ti);
				col.classList.toggle("jey-drop-left", fi >= ti);
				dragState.toField = col.getAttribute("data-fieldname");
			} else {
				dragState.toField = null;
			}
		}
	});

	document.addEventListener("mouseup", function () {
		if (!dragState) return;

		var state = dragState;
		dragState = null;

		document.body.style.cursor = "";
		document.body.style.userSelect = "";

		state.gridEl.querySelectorAll(".jey-drop-target").forEach(function (c) {
			c.classList.remove("jey-drop-target");
		});

		if (!state.dragging || !state.toField) return;

		var gridInstance = getGridInstance(state.gridEl);
		if (!gridInstance) return;

		// Build new order
		var headerCols = state.gridEl.querySelectorAll(
			".grid-heading-row .grid-row:not(.filter-row) .grid-static-col[data-fieldname]"
		);
		var order = [];
		headerCols.forEach(function (c) { order.push(c.getAttribute("data-fieldname")); });

		var fromIdx = order.indexOf(state.fromField);
		var toIdx = order.indexOf(state.toField);
		if (fromIdx === -1 || toIdx === -1 || fromIdx === toIdx) return;

		var draggingRight = fromIdx < toIdx;

		// Remove dragged column
		order.splice(fromIdx, 1);

		// Find target in modified array
		var newToIdx = order.indexOf(state.toField);
		if (newToIdx === -1) return;

		// Dragging right → insert AFTER target, left → insert BEFORE
		order.splice(draggingRight ? newToIdx + 1 : newToIdx, 0, state.fromField);

		saveColumnOrder(gridInstance, order);
		gridInstance.reset_grid();
	});

	function processDragGrids() {
		document.querySelectorAll(".form-grid").forEach(addDragListeners);
	}

	var dragTimer = null;
	function scheduleDrag() {
		clearTimeout(dragTimer);
		dragTimer = setTimeout(processDragGrids, 150);
	}

	$(document).on("page-change", scheduleDrag);
	var dragObserver = new MutationObserver(scheduleDrag);
	dragObserver.observe(document.body, { childList: true, subtree: true });
})();

/* ==========================================================================
   List View Column Resize + Reorder
   Drag the right edge of a list header to resize; drag the header body to
   reorder. Widths + order persist in localStorage per doctype:
     jey-list-col-widths:<Doctype>   { fieldname: pxWidth, ... }
     jey-list-col-order:<Doctype>    [fieldname, fieldname, ...]
   ========================================================================== */

(function () {
	"use strict";

	var MIN_COL_WIDTH = 60;
	var WIDTH_PREFIX = "jey-list-col-widths:";
	var ORDER_PREFIX = "jey-list-col-order:";
	var DRAG_THRESHOLD = 5;

	// Classnames that appear on .list-row-col but are NOT fieldnames.
	var NON_FIELD_CLASSES = {
		"list-row-col": 1, "ellipsis": 1, "hidden-xs": 1, "text-right": 1,
		"list-subject": 1, "level": 1, "level-item": 1,
		"tag-col": 1, "hide": 1, "bold": 1, "seen": 1, "notseen": 1,
		"inner-group-button": 1
	};

	var styleEl = document.getElementById("jey-col-resize-styles");
	// { "Sales Invoice": { customer: 220, ... }, ... }
	var allListWidths = {};
	// Tracks doctypes already initialised from localStorage during this session
	var loaded = {};

	function escapeClass(fn) {
		return (window.CSS && CSS.escape) ? CSS.escape(fn) : fn.replace(/[^a-zA-Z0-9_-]/g, "\\$&");
	}

	// Rebuild only the list-scoped portion of the shared stylesheet.
	// Grid rules live inside their own <style> section? No — they share this element.
	// To avoid trashing grid rules, we keep a dedicated second stylesheet for lists.
	var listStyleEl = document.getElementById("jey-list-col-styles");
	if (!listStyleEl) {
		listStyleEl = document.createElement("style");
		listStyleEl.id = "jey-list-col-styles";
		document.head.appendChild(listStyleEl);
	}

	// FOUC-only stylesheet: used before JS has had a chance to stamp inline
	// styles on each cell. Targets header cells via their native fieldname
	// class (Frappe sets that from the first paint). Runtime width locking
	// happens through applyWidthsToCells(), which writes inline !important
	// styles by position — that's the one that has to win against Frappe's
	// apply_column_widths() and the inherent flex weirdness of .level-left.
	function rebuildListStylesheet() {
		var rules = [];
		Object.keys(allListWidths).forEach(function (doctype) {
			var widths = allListWidths[doctype];
			Object.keys(widths).forEach(function (fn) {
				var w = widths[fn];
				var esc = escapeClass(fn);
				rules.push(
					'.list-view[data-jey-list="' + doctype + '"] .list-row-col.' + esc +
					" { flex: 0 0 " + w + "px !important; width: " + w +
					"px !important; max-width: none !important; min-width: 0 !important; }"
				);
			});
		});
		listStyleEl.textContent = rules.join("\n");
	}

	function setCellWidthStyle(cell, w) {
		cell.style.setProperty("flex", "0 0 " + w + "px", "important");
		cell.style.setProperty("width", w + "px", "important");
		cell.style.setProperty("max-width", "none", "important");
		cell.style.setProperty("min-width", "0", "important");
	}

	// Freeze EVERY cell (header + data rows) at a fixed width. Saved width
	// wins if we have one for the column's fieldname; otherwise we use the
	// cell's current rendered width.
	//
	// Freezing unconditionally — even columns we can't identify a fieldname
	// for (Status without a docfield, Button columns, etc.) — is essential:
	// otherwise one flex-shrinkable column absorbs all of another column's
	// growth and you get "right edge doesn't move, left edge slides left".
	function applyWidthsToCells(listview) {
		var root = listview.$result && listview.$result[0];
		if (!root) return;
		var saved = allListWidths[listview.doctype] || {};
		var headerCells = root.querySelectorAll(".list-row-head .level-left > .list-row-col");
		if (!headerCells.length) return;

		// Pass 1: decide each column's target width.
		var widths = [];
		var fieldOrder = [];
		headerCells.forEach(function (col) {
			var fn = fieldnameOf(col);
			fieldOrder.push(fn);
			var w;
			if (fn && saved[fn] != null) {
				w = saved[fn];
			} else {
				w = Math.round(col.getBoundingClientRect().width);
			}
			widths.push(w);
		});

		// Pass 2: apply. Header first.
		headerCells.forEach(function (col, idx) {
			if (widths[idx] > 0) setCellWidthStyle(col, widths[idx]);
		});

		// Then every data row, matched to header by position.
		root.querySelectorAll(".list-row:not(.list-row-head) .level-left").forEach(function (rowLeft) {
			var cells = rowLeft.querySelectorAll(":scope > .list-row-col");
			cells.forEach(function (cell, idx) {
				if (widths[idx] > 0) setCellWidthStyle(cell, widths[idx]);
			});
		});
	}

	// During drag, only the active column's cells change. Update just those —
	// same O(rows) cost as the full apply, but narrower scope.
	function applyActiveColumnWidth(listview, fn, w) {
		if (!fn) return;
		var root = listview.$result && listview.$result[0];
		if (!root) return;
		var headerCells = root.querySelectorAll(".list-row-head .level-left > .list-row-col");
		var activeIdx = -1;
		for (var i = 0; i < headerCells.length; i++) {
			if (fieldnameOf(headerCells[i]) === fn) { activeIdx = i; break; }
		}
		if (activeIdx === -1) return;

		setCellWidthStyle(headerCells[activeIdx], w);
		root.querySelectorAll(".list-row:not(.list-row-head) .level-left").forEach(function (rowLeft) {
			var cells = rowLeft.querySelectorAll(":scope > .list-row-col");
			if (cells[activeIdx]) setCellWidthStyle(cells[activeIdx], w);
		});
	}

	function loadWidths(doctype) {
		if (loaded[doctype]) return allListWidths[doctype] || null;
		loaded[doctype] = true;
		try {
			var raw = localStorage.getItem(WIDTH_PREFIX + doctype);
			if (raw) {
				var parsed = JSON.parse(raw);
				if (parsed && typeof parsed === "object") {
					allListWidths[doctype] = parsed;
					rebuildListStylesheet();
					return parsed;
				}
			}
		} catch (e) {}
		return null;
	}

	function saveWidths(doctype) {
		if (!doctype || !allListWidths[doctype]) return;
		localStorage.setItem(WIDTH_PREFIX + doctype, JSON.stringify(allListWidths[doctype]));
	}

	function loadOrder(doctype) {
		try {
			var raw = localStorage.getItem(ORDER_PREFIX + doctype);
			if (raw) {
				var parsed = JSON.parse(raw);
				if (Array.isArray(parsed)) return parsed;
			}
		} catch (e) {}
		return null;
	}

	function saveOrder(doctype, order) {
		localStorage.setItem(ORDER_PREFIX + doctype, JSON.stringify(order));
	}

	function fieldnameOf(colEl) {
		// Prefer the stamped attribute (covers Status/Tag/etc cells that
		// ship without a fieldname class).
		if (colEl.dataset && colEl.dataset.jeyField) return colEl.dataset.jeyField;
		// Headers always carry <span data-sort-by="<fieldname>">.
		var sortSpan = colEl.querySelector && colEl.querySelector("[data-sort-by]");
		if (sortSpan) {
			var fn = sortSpan.getAttribute("data-sort-by");
			if (fn) return fn;
		}
		// Fallback: first non-structural class.
		var classes = (colEl.className || "").split(/\s+/);
		for (var i = 0; i < classes.length; i++) {
			var c = classes[i];
			if (c && !NON_FIELD_CLASSES[c] && c.indexOf("jey-") !== 0) return c;
		}
		return null;
	}

	// Stamp data-jey-field on every column cell (header + data rows) so the
	// rest of the module can target columns uniformly. Frappe ships some
	// built-in columns (Tag, Status, Button, …) without a fieldname class
	// or a data-sort-by span — we assign a synthetic position-based key
	// so those still participate in resize and reorder.
	function stampFieldAttributes(listview) {
		var $result = listview.$result;
		if (!$result || !$result.length) return;
		var root = $result[0];

		var headerLeft = root.querySelector(".list-row-head .level-left");
		if (!headerLeft) return;

		var headerCells = headerLeft.querySelectorAll(":scope > .list-row-col");
		var fieldOrder = [];
		headerCells.forEach(function (col, idx) {
			var fn = fieldnameOf(col);
			if (!fn) fn = "__jey_col_" + idx + "__";
			col.dataset.jeyField = fn;
			fieldOrder.push(fn);
		});

		root.querySelectorAll(".list-row:not(.list-row-head) .level-left").forEach(function (rowLeft) {
			var cells = rowLeft.querySelectorAll(":scope > .list-row-col");
			cells.forEach(function (cell, idx) {
				var fn = fieldOrder[idx];
				if (fn) cell.dataset.jeyField = fn;
			});
		});
	}

	function isSubject(colEl) {
		return colEl.classList.contains("list-subject");
	}

	function getListContainer(listview) {
		var root = listview && listview.$result ? listview.$result[0] : null;
		if (!root) return null;
		return root.closest(".list-view");
	}

	function applyOrder(listview) {
		var doctype = listview.doctype;
		var order = loadOrder(doctype);
		if (!order || !order.length) return;

		var $result = listview.$result;
		if (!$result || !$result.length) return;

		// Header first, then every data row.
		var groups = [];
		var headerLeft = $result[0].querySelector(".list-row-head .level-left");
		if (headerLeft) groups.push(headerLeft);
		$result[0].querySelectorAll(".list-row:not(.list-row-head) .level-left").forEach(function (g) {
			groups.push(g);
		});

		groups.forEach(function (group) {
			var byField = {};
			group.querySelectorAll(":scope > .list-row-col").forEach(function (col) {
				var fn = fieldnameOf(col);
				if (fn) byField[fn] = col;
			});
			// Append in saved order; subject column stays where it is (skipped).
			order.forEach(function (fn) {
				var node = byField[fn];
				if (node && !isSubject(node)) group.appendChild(node);
			});
		});
	}

	function buildCurrentOrder(listview) {
		var $result = listview.$result;
		if (!$result || !$result.length) return [];
		var header = $result[0].querySelector(".list-row-head .level-left");
		if (!header) return [];
		var order = [];
		header.querySelectorAll(":scope > .list-row-col").forEach(function (col) {
			if (isSubject(col)) return;
			var fn = fieldnameOf(col);
			if (fn) order.push(fn);
		});
		return order;
	}

	function injectResizeHandles(listview) {
		var $result = listview.$result;
		if (!$result || !$result.length) return;
		var headerCols = $result[0].querySelectorAll(".list-row-head .level-left > .list-row-col");
		var doctype = listview.doctype;

		headerCols.forEach(function (col) {
			// Subject gets a resize handle too (right edge, well away from
			// the checkbox on the left) — its content often outruns Frappe's
			// auto-calculated width and users need to widen it.
			if (col.querySelector(".jey-col-resize")) return;

			var handle = document.createElement("div");
			handle.className = "jey-col-resize";
			col.appendChild(handle);

			handle.addEventListener("mousedown", function (e) {
				e.preventDefault();
				e.stopPropagation();
				startResize(listview, col, e.clientX);
			});

			handle.addEventListener("dblclick", function (e) {
				e.preventDefault();
				e.stopPropagation();
				autoFit(listview, col);
			});
		});
	}

	function autoFit(listview, headerCol) {
		var doctype = listview.doctype;
		var fn = fieldnameOf(headerCol);
		if (!fn) return;
		var label = headerCol.querySelector("span, a, .list-subject");
		var text = label ? label.textContent.trim() : fn;
		var canvas = document.createElement("canvas");
		var ctx = canvas.getContext("2d");
		var style = getComputedStyle(headerCol);
		ctx.font = style.fontWeight + " " + style.fontSize + " " + style.fontFamily;
		var w = Math.max(MIN_COL_WIDTH, Math.ceil(ctx.measureText(text).width) + 40);
		if (!allListWidths[doctype]) allListWidths[doctype] = {};
		allListWidths[doctype][fn] = w;
		applyActiveColumnWidth(listview, fn, w);
		rebuildListStylesheet();
		saveWidths(doctype);
	}

	function startResize(listview, headerCol, startX) {
		var doctype = listview.doctype;
		var fn = fieldnameOf(headerCol);
		if (!fn) return;

		var startWidth = headerCol.getBoundingClientRect().width;
		if (!allListWidths[doctype]) allListWidths[doctype] = {};
		var widths = allListWidths[doctype];

		// Freeze every header column at its current rendered width so only the
		// dragged one moves. Includes .list-subject — it has flex-shrink:1
		// from list.scss and would otherwise compress while another column
		// grows, visually anchoring the dragged column's right edge.
		var cols = listview.$result[0].querySelectorAll(".list-row-head .level-left > .list-row-col");
		cols.forEach(function (c) {
			var f = fieldnameOf(c);
			if (f && !widths[f]) {
				widths[f] = Math.round(c.getBoundingClientRect().width);
			}
		});
		applyWidthsToCells(listview);

		document.body.style.cursor = "col-resize";
		document.body.style.userSelect = "none";

		function onMove(e) {
			var w = Math.max(MIN_COL_WIDTH, Math.round(startWidth + (e.clientX - startX)));
			widths[fn] = w;
			applyActiveColumnWidth(listview, fn, w);
		}
		function onUp() {
			document.removeEventListener("mousemove", onMove);
			document.removeEventListener("mouseup", onUp);
			document.body.style.cursor = "";
			document.body.style.userSelect = "";
			rebuildListStylesheet();
			saveWidths(doctype);
		}
		document.addEventListener("mousemove", onMove);
		document.addEventListener("mouseup", onUp);
	}

	/* -------- Drag to reorder -------- */

	var dragState = null;

	function bindReorder(listview) {
		var $result = listview.$result;
		if (!$result || !$result.length) return;
		var headerCols = $result[0].querySelectorAll(".list-row-head .level-left > .list-row-col");

		headerCols.forEach(function (col) {
			if (isSubject(col)) return;
			if (col.dataset.jeyDraggable) return;
			col.dataset.jeyDraggable = "1";

			col.addEventListener("mousedown", function (e) {
				if (e.target.classList.contains("jey-col-resize")) return;
				if (e.button !== 0) return;
				var fn = fieldnameOf(col);
				if (!fn) return;
				dragState = {
					listview: listview,
					fromField: fn,
					startX: e.clientX,
					startY: e.clientY,
					dragging: false,
					toField: null
				};
			});
		});
	}

	document.addEventListener("mousemove", function (e) {
		if (!dragState) return;

		if (!dragState.dragging) {
			var dx = Math.abs(e.clientX - dragState.startX);
			var dy = Math.abs(e.clientY - dragState.startY);
			if (dx < DRAG_THRESHOLD && dy < DRAG_THRESHOLD) return;
			dragState.dragging = true;
			document.body.style.cursor = "grabbing";
			document.body.style.userSelect = "none";
		}

		var root = dragState.listview.$result[0];
		root.querySelectorAll(".list-row-head .jey-drop-target").forEach(function (c) {
			c.classList.remove("jey-drop-target", "jey-drop-left", "jey-drop-right");
		});

		var target = document.elementFromPoint(e.clientX, e.clientY);
		if (!target) return;
		var col = target.closest(".list-row-head .level-left > .list-row-col");
		if (!col || !root.contains(col) || isSubject(col)) {
			dragState.toField = null;
			return;
		}
		var fn = fieldnameOf(col);
		if (!fn || fn === dragState.fromField) {
			dragState.toField = null;
			return;
		}

		var headerCols = Array.from(root.querySelectorAll(".list-row-head .level-left > .list-row-col"));
		var fi = headerCols.findIndex(function (c) { return fieldnameOf(c) === dragState.fromField; });
		var ti = headerCols.indexOf(col);
		col.classList.add("jey-drop-target");
		col.classList.toggle("jey-drop-right", fi < ti);
		col.classList.toggle("jey-drop-left", fi >= ti);
		dragState.toField = fn;
	});

	document.addEventListener("mouseup", function () {
		if (!dragState) return;
		var state = dragState;
		dragState = null;
		document.body.style.cursor = "";
		document.body.style.userSelect = "";

		var root = state.listview.$result ? state.listview.$result[0] : null;
		if (root) {
			root.querySelectorAll(".list-row-head .jey-drop-target").forEach(function (c) {
				c.classList.remove("jey-drop-target", "jey-drop-left", "jey-drop-right");
			});
		}

		if (!state.dragging || !state.toField) return;

		var order = buildCurrentOrder(state.listview);
		var fromIdx = order.indexOf(state.fromField);
		var toIdx = order.indexOf(state.toField);
		if (fromIdx === -1 || toIdx === -1 || fromIdx === toIdx) return;

		var draggingRight = fromIdx < toIdx;
		order.splice(fromIdx, 1);
		var newToIdx = order.indexOf(state.toField);
		if (newToIdx === -1) return;
		order.splice(draggingRight ? newToIdx + 1 : newToIdx, 0, state.fromField);

		saveOrder(state.listview.doctype, order);
		applyOrder(state.listview);
	});

	/* -------- Wire into ListView render pipeline -------- */

	function onListRender(listview) {
		if (!listview || listview.view_name !== "List") return;

		var container = getListContainer(listview);
		if (container) container.dataset.jeyList = listview.doctype;

		loadWidths(listview.doctype);
		stampFieldAttributes(listview);
		applyOrder(listview);
		applyWidthsToCells(listview);
		injectResizeHandles(listview);
		bindReorder(listview);
	}

	function wrapRenderList() {
		if (!window.frappe || !frappe.views || !frappe.views.ListView) return false;
		var proto = frappe.views.ListView.prototype;
		if (proto.__jeyRenderListWrapped) return true;
		proto.__jeyRenderListWrapped = true;

		var originalRender = proto.render_list;
		proto.render_list = function () {
			var result = originalRender.apply(this, arguments);
			try { onListRender(this); } catch (e) { /* noop */ }
			return result;
		};
		return true;
	}

	// ListView is only loaded when the user first opens a list — poll briefly.
	if (!wrapRenderList()) {
		var attempts = 0;
		var iv = setInterval(function () {
			attempts++;
			if (wrapRenderList() || attempts > 100) clearInterval(iv);
		}, 200);
	}
})();

/* ==========================================================================
   List View — Period filter (Between) for date fields like posting_date
   Replaces the single-date "=" standard filter with a DateRange "Between"
   control, or injects one after the ID filter if it's not in standard_filter.
   ========================================================================== */

(function () {
	"use strict";

	/* Trigger fields: if doctype has any of these as Date/Datetime, we upgrade.
	   Order matters — first match wins. Extend freely. */
	var TRIGGER_FIELDS = ["posting_date"];

	function findTriggerDf(listview) {
		if (!listview || !listview.meta || !Array.isArray(listview.meta.fields)) return null;
		var fields = listview.meta.fields;
		for (var i = 0; i < TRIGGER_FIELDS.length; i++) {
			var name = TRIGGER_FIELDS[i];
			for (var j = 0; j < fields.length; j++) {
				var f = fields[j];
				if (f.fieldname === name && (f.fieldtype === "Date" || f.fieldtype === "Datetime")) {
					return f;
				}
			}
		}
		return null;
	}

	function patchFilterAreaProto(filter_area) {
		var proto = Object.getPrototypeOf(filter_area);
		if (!proto || proto.__jeyBetweenPatched) return;
		proto.__jeyBetweenPatched = true;

		var origSetStandard = proto.set_standard_filter;
		proto.set_standard_filter = function (filters) {
			if (!filters || filters.length === 0) return origSetStandard.call(this, filters);

			var fields_dict = this.list_view.page.fields_dict || {};
			var handled = [];
			var passthrough = [];

			filters.forEach(function (filter) {
				var fieldname = filter[1];
				var condition = filter[2];
				var value = filter[3];
				var ctrl = fields_dict[fieldname];
				if (
					condition === "Between" &&
					ctrl &&
					ctrl.df &&
					ctrl.df.fieldtype === "DateRange"
				) {
					handled.push({ ctrl: ctrl, value: value });
				} else {
					passthrough.push(filter);
				}
			});

			var result = origSetStandard.call(this, passthrough);

			if (handled.length) {
				result.promise = (result.promise || Promise.resolve()).then(function () {
					handled.forEach(function (h) {
						h.ctrl.df.match_type = "Between";
						h.ctrl.df.condition = "Between";
						return h.ctrl.set_value(h.value);
					});
				});
			}
			return result;
		};
	}

	function buildDateRangeField(listview, df) {
		var page = listview.page;
		var $section = page.page_form.find(".standard-filter-section");
		if (!$section.length) $section = page.page_form;

		var existing = page.fields_dict[df.fieldname];
		var $placeholder = null;

		if (existing && existing.$wrapper) {
			var $existingWrap = $(existing.$wrapper);
			if ($existingWrap.parent().length) {
				$placeholder = $('<span class="jey-period-anchor" style="display:none"></span>');
				$existingWrap.before($placeholder);
			}
			$existingWrap.remove();
			delete page.fields_dict[df.fieldname];
		}

		var newField = page.add_field(
			{
				fieldtype: "DateRange",
				label: df.label || "Posting Date",
				fieldname: df.fieldname,
				condition: "Between",
				is_filter: 1,
				onchange: function () {
					if (
						listview &&
						listview.filter_area &&
						typeof listview.filter_area.refresh_list_view === "function"
					) {
						listview.filter_area.trigger_refresh = true;
						listview.filter_area.refresh_list_view();
					}
				},
			},
			$section
		);

		if (!newField) return null;

		newField.df.match_type = "Between";
		newField.df.condition = "Between";

		var $newWrap = $(newField.$wrapper || newField.wrapper);
		$newWrap.addClass("jey-period-filter");

		if ($placeholder && $placeholder.length) {
			$placeholder.replaceWith($newWrap);
		} else {
			var $idWrap = $section.find('[data-fieldname="name"]').closest(".frappe-control");
			if ($idWrap.length) {
				$idWrap.after($newWrap);
			} else {
				$section.prepend($newWrap);
			}
		}

		return newField;
	}

	function reflectExistingFilter(listview, df, field) {
		try {
			if (
				!listview.filter_area ||
				!listview.filter_area.filter_list ||
				typeof listview.filter_area.filter_list.get_filters !== "function"
			) {
				return;
			}
			var filters = listview.filter_area.filter_list.get_filters() || [];
			var match = null;
			for (var i = 0; i < filters.length; i++) {
				var f = filters[i];
				if (f[1] === df.fieldname && f[2] === "Between") {
					match = f;
					break;
				}
			}
			if (!match) return;

			var sidebarFilter = listview.filter_area.filter_list.get_filter(df.fieldname);
			if (sidebarFilter && typeof sidebarFilter.remove === "function") {
				sidebarFilter.remove();
			}

			field.df.match_type = "Between";
			field.df.condition = "Between";
			field.set_value(match[3]);
		} catch (e) {
			/* noop */
		}
	}

	function setup(listview) {
		if (!listview || listview.view_name !== "List") return;
		if (listview.__jeyPeriodSet) return;
		if (!listview.page || !listview.page.page_form) return;

		var df = findTriggerDf(listview);
		if (!df) return;

		if (listview.filter_area) patchFilterAreaProto(listview.filter_area);

		var field = buildDateRangeField(listview, df);
		if (!field) return;

		reflectExistingFilter(listview, df, field);

		listview.__jeyPeriodSet = true;
	}

	function wrapRenderList() {
		if (!window.frappe || !frappe.views || !frappe.views.ListView) return false;
		var proto = frappe.views.ListView.prototype;
		if (proto.__jeyPeriodWrapped) return true;
		proto.__jeyPeriodWrapped = true;

		var originalRender = proto.render_list;
		proto.render_list = function () {
			var result = originalRender.apply(this, arguments);
			try { setup(this); } catch (e) { /* noop */ }
			return result;
		};
		return true;
	}

	if (!wrapRenderList()) {
		var attempts = 0;
		var iv = setInterval(function () {
			attempts++;
			if (wrapRenderList() || attempts > 100) clearInterval(iv);
		}, 200);
	}
})();

/* ==========================================================================
   List View — Override SVG sprite icons
   Replaces <symbol> content in Frappe's SVG sprite sheets so every
   <use href="#icon-..."> picks up the new artwork automatically.
   ========================================================================== */

(function () {
	"use strict";

	var SPRITE_OVERRIDES = {
		/* icon-list (Lucide, 24×24, stroke) — 2×2 grid of squares */
		"icon-list":
			'<rect x="3" y="3" width="8" height="8" rx="1.5"/>' +
			'<rect x="13" y="3" width="8" height="8" rx="1.5"/>' +
			'<rect x="3" y="13" width="8" height="8" rx="1.5"/>' +
			'<rect x="13" y="13" width="8" height="8" rx="1.5"/>',

		/* icon-sort-descending (Timeless, 16×16, stroke) — vertical bar chart tall→short */
		"icon-sort-descending":
			'<path d="M3 13V3M6.33 13V5.5M9.67 13V8M13 13V10.5" ' +
			'stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>',

		/* icon-sort-ascending (Timeless, 16×16, stroke) — vertical bar chart short→tall */
		"icon-sort-ascending":
			'<path d="M3 13V10.5M6.33 13V8M9.67 13V5.5M13 13V3" ' +
			'stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>',

		/* es-line-filter (Espresso, 16×16, fill) — solid funnel shape */
		"es-line-filter":
			'<g class="es-line-filter">' +
			'<path d="M1.5 2.5h13L10 8v3.5H6V8L1.5 2.5z"/>' +
			'</g>',

		/* icon-home — simple house silhouette */
		"icon-home":
			'<path d="M12 1L0 12h4v10h7v-7h2v7h7V12h4z"/>'
	};

	var totalIcons = Object.keys(SPRITE_OVERRIDES).length;

	function overrideSprites() {
		var replaced = 0;
		for (var id in SPRITE_OVERRIDES) {
			var symbol = document.getElementById(id);
			if (symbol && symbol.tagName.toLowerCase() === "symbol") {
				symbol.innerHTML = SPRITE_OVERRIDES[id];
				replaced++;
			}
		}
		return replaced;
	}

	function tryOverride() {
		if (overrideSprites() >= totalIcons) return;
		var attempts = 0;
		var timer = setInterval(function () {
			if (overrideSprites() >= totalIcons || ++attempts > 20) {
				clearInterval(timer);
			}
		}, 200);
	}

	if (document.readyState === "loading") {
		document.addEventListener("DOMContentLoaded", tryOverride);
	} else {
		tryOverride();
	}
})();

/* ==========================================================================
   Form Tabs — Re-trigger section cascade on tab switch
   ========================================================================== */
(function () {
	document.addEventListener("click", function (e) {
		var tab = e.target.closest(".form-tabs .nav-link");
		if (!tab) return;

		// Small delay so Frappe shows the tab content first
		setTimeout(function () {
			var sections = document.querySelectorAll(".form-layout .form-section .section-head, .form-layout .form-section .section-body");
			sections.forEach(function (s) {
				s.style.animation = "none";
				// Force reflow then restart animation
				void s.offsetHeight;
				s.style.animation = "";
			});
		}, 20);
	});
})();




/* ==========================================================================
   Form Sections — Set exact max-height for smooth collapse animation
   ========================================================================== */
(function () {
	document.addEventListener("click", function (e) {
		var head = e.target.closest(".section-head.collapsible");
		if (!head) return;
		var body = head.nextElementSibling;
		if (!body || !body.classList.contains("section-body")) return;

		// Set exact max-height so transition has a real start/end value
		body.style.maxHeight = body.scrollHeight + "px";
		// Clip overflow during the transition so expanding content doesn't
		// spill out before the section has grown to fit it. Removed at
		// transitionend so Link-field dropdowns can escape section bounds.
		body.style.overflow = "hidden";
		// Force reflow — browser must register this value before Frappe toggles .collapsed
		void body.offsetHeight;
	}, true);

	document.addEventListener("transitionend", function (e) {
		var body = e.target;
		if (!body.classList || !body.classList.contains("section-body")) return;
		if (e.propertyName !== "max-height") return;
		var head = body.previousElementSibling;
		if (!head || !head.classList.contains("collapsible")) return;
		// After expand: drop the inline overflow so dropdowns can escape.
		// After collapse: the `.collapsed + .section-body` CSS rule keeps
		// overflow hidden, so clearing the inline value is still safe.
		body.style.overflow = "";
	}, true);
})();


/* ==========================================================================
   Search Modal — smooth close (backdrop cleanup)
   ========================================================================== */
// Bootstrap leaves the backdrop when we override display.
// Remove it manually when .show is removed.
(function () {
	var searchModal = null;
	var observer = new MutationObserver(function (mutations) {
		for (var i = 0; i < mutations.length; i++) {
			var m = mutations[i];
			if (m.attributeName === "class" && searchModal) {
				if (!searchModal.classList.contains("show") && searchModal.dataset.jeyWasOpen) {
					delete searchModal.dataset.jeyWasOpen;
					// After transition ends, clean up backdrop
					setTimeout(function () {
						var bd = document.querySelector(".modal-backdrop");
						if (bd) bd.remove();
						document.body.classList.remove("modal-open");
						document.body.style.removeProperty("padding-right");
					}, 550);
				}
				if (searchModal.classList.contains("show")) {
					searchModal.dataset.jeyWasOpen = "1";
					// Re-focus input after CSS transition so awesomplete opens
					var input = searchModal.querySelector("#navbar-search");
					if (input) {
						setTimeout(function () {
							input.blur();
							input.focus();
						}, 550);
					}
				}
			}
		}
	});

	// Find search modal and start observing
	var bodyObserver = new MutationObserver(function () {
		if (searchModal) return;
		var el = document.querySelector(".modal:has(.cool-awesomebar-modal-footer)");
		if (!el) return;
		searchModal = el;
		bodyObserver.disconnect();
		observer.observe(searchModal, { attributes: true, attributeFilter: ["class"] });
		// If .show is already present (race condition on first open), trigger focus now
		if (searchModal.classList.contains("show")) {
			searchModal.dataset.jeyWasOpen = "1";
			var input = searchModal.querySelector("#navbar-search");
			if (input) {
				setTimeout(function () {
					input.blur();
					input.focus();
				}, 550);
			}
		}
	});
	bodyObserver.observe(document.body, { childList: true, subtree: true });
})();


/* ==========================================================================
   Dropdown fade-out — freeze Popper position during hide animation
   Bootstrap removes .show and then Popper.destroy() wipes the menu's
   inline transform/top/left, so a CSS transition would fade the menu at
   (0,0) instead of its visible spot. On hide.bs.dropdown we snapshot the
   computed position, then re-apply it in a microtask (after Bootstrap's
   synchronous cleanup) together with the .jey-dropdown-hiding class that
   plays the opacity animation. Cleanup on animation end or next show.
   ========================================================================== */
(function () {
	if (typeof window.jQuery === "undefined") return;

	function cleanup(menu) {
		menu.classList.remove("jey-dropdown-hiding");
		menu.style.removeProperty("transform");
		menu.style.removeProperty("top");
		menu.style.removeProperty("left");
		menu.style.removeProperty("position");
		if (menu._jeyHideTimer) {
			clearTimeout(menu._jeyHideTimer);
			menu._jeyHideTimer = null;
		}
	}

	jQuery(document).on("hide.bs.dropdown", function (e) {
		var menu = e.target && e.target.querySelector(".dropdown-menu.show");
		if (!menu) return;
		var cs = getComputedStyle(menu);
		var transform = cs.transform;
		var top = cs.top;
		var left = cs.left;
		var position = cs.position;
		Promise.resolve().then(function () {
			menu.style.setProperty("transform", transform, "important");
			menu.style.setProperty("top", top, "important");
			menu.style.setProperty("left", left, "important");
			menu.style.setProperty("position", position, "important");
			menu.classList.add("jey-dropdown-hiding");
			menu._jeyHideTimer = setTimeout(function () {
				cleanup(menu);
			}, 220);
		});
	});

	// If the dropdown reopens before fade-out finishes, clear our frozen
	// styles so Popper can position it fresh.
	jQuery(document).on("show.bs.dropdown", function (e) {
		var menu = e.target && e.target.querySelector(".dropdown-menu");
		if (!menu) return;
		cleanup(menu);
	});
})();


/* ==========================================================================
   Awesomplete (link field) fade-out
   Awesomplete.close() just toggles the [hidden] attribute, so the listbox
   vanishes with no transition. On awesomplete-close we tag the ul with
   .jey-awesomplete-hiding to play a fade-out animation, and clear the tag
   on re-open (or after the animation finishes) so the in-animation can
   play cleanly next time.
   ========================================================================== */
(function () {
	if (typeof window.jQuery === "undefined") return;

	function findListbox(input) {
		var wrapper = input && input.closest && input.closest(".awesomplete");
		return wrapper ? wrapper.querySelector('ul[role="listbox"]') : null;
	}

	jQuery(document).on("awesomplete-close", function (e) {
		var ul = findListbox(e.target);
		if (!ul) return;
		ul.classList.add("jey-awesomplete-hiding");
		if (ul._jeyHideTimer) clearTimeout(ul._jeyHideTimer);
		ul._jeyHideTimer = setTimeout(function () {
			ul.classList.remove("jey-awesomplete-hiding");
			ul._jeyHideTimer = null;
		}, 220);
	});

	jQuery(document).on("awesomplete-open", function (e) {
		var ul = findListbox(e.target);
		if (!ul) return;
		ul.classList.remove("jey-awesomplete-hiding");
		if (ul._jeyHideTimer) {
			clearTimeout(ul._jeyHideTimer);
			ul._jeyHideTimer = null;
		}
	});
})();


/* ==========================================================================
   Sprite icon swaps
   Frappe hard-codes #icon-heart for the "like" action and #es-line-chat-alt
   for the comment count in list/form templates. Rewrite the <use href=...>
   on render so lists/forms show a star and a comment bubble instead.

   For the comment icon we inject our own sprite symbol (#jey-icon-comment)
   because Frappe's bundled #icon-message-square-text is the newer lucide
   variant with 3 text lines, and we want the older 2-line version.
   ========================================================================== */
(function () {
	var SWAPS = {
		"#icon-heart": "#icon-star",
		"#es-line-chat-alt": "#jey-icon-comment",
	};

	function injectSprite() {
		if (document.getElementById("jey-icon-sprite")) return;
		var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
		svg.id = "jey-icon-sprite";
		svg.setAttribute("width", "0");
		svg.setAttribute("height", "0");
		svg.setAttribute("aria-hidden", "true");
		svg.style.position = "absolute";
		svg.innerHTML =
			'<symbol viewBox="0 0 24 24" id="jey-icon-comment">' +
				'<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>' +
				'<path d="M13 8H7"/>' +
				'<path d="M17 12H7"/>' +
			'</symbol>';
		(document.body || document.documentElement).insertBefore(
			svg, (document.body || document.documentElement).firstChild
		);
	}

	function swapIn(root) {
		if (!root || root.nodeType !== 1) return;
		Object.keys(SWAPS).forEach(function (from) {
			var to = SWAPS[from];
			// href= and xlink:href= both appear in the wild
			var nodes = root.querySelectorAll(
				'use[href="' + from + '"], use[*|href="' + from + '"]'
			);
			nodes.forEach(function (u) {
				if (u.getAttribute("href") === from) u.setAttribute("href", to);
				if (u.getAttributeNS("http://www.w3.org/1999/xlink", "href") === from) {
					u.setAttributeNS("http://www.w3.org/1999/xlink", "href", to);
				}
			});
		});
	}

	function init() {
		injectSprite();
		swapIn(document.body);
		var obs = new MutationObserver(function (mutations) {
			for (var i = 0; i < mutations.length; i++) {
				var m = mutations[i];
				if (m.type === "childList") {
					m.addedNodes.forEach(function (n) {
						if (n.nodeType !== 1) return;
						// If the added node itself is a <use>, handle it directly
						if (n.tagName && n.tagName.toLowerCase() === "use") {
							var href = n.getAttribute("href");
							if (href && SWAPS[href]) n.setAttribute("href", SWAPS[href]);
						}
						swapIn(n);
					});
				} else if (m.type === "attributes" && m.target && m.target.tagName &&
					m.target.tagName.toLowerCase() === "use") {
					var href2 = m.target.getAttribute("href");
					if (href2 && SWAPS[href2]) m.target.setAttribute("href", SWAPS[href2]);
				}
			}
		});
		obs.observe(document.body, {
			childList: true,
			subtree: true,
			attributes: true,
			attributeFilter: ["href", "xlink:href"],
		});
	}

	if (document.readyState === "loading") {
		document.addEventListener("DOMContentLoaded", init);
	} else {
		init();
	}
})();

/* ==========================================================================
   Sidebar empty-state fix (works around a Frappe v16 core bug)

   frappe.ui.Sidebar.set_workspace_sidebar() decides which workspace sidebar to
   render for a List/Form route. When the route's doctype is referenced by MORE
   THAN ONE workspace sidebar but none of them is the doctype's own module
   workspace, the core method falls through without calling setup() — leaving
   .sidebar-items empty (no items, not even the "No Sidebar Items" placeholder,
   because create_sidebar() never runs). It appears to "self-heal" only because
   navigating in from a matching workspace sets this.sidebar_title, which an
   early return in the method then preserves; a hard reload (e.g. after
   clear-cache / migrate / build) loses that state and the sidebar goes blank.

   Repro: hard-reload on /app/price-list. Price List's module is "Stock", but it
   is only linked from the "Selling" and "Buying" sidebars, so
   get_workspace_for_module("Stock") yields "Stock" — which is not in
   ["Selling","Buying"] — so setup() is never called.

   Fix: after the core method runs, if the items container is still empty, fall
   back to the first sidebar that references the route's doctype, otherwise to
   the route's module home workspace. The fallback calls setup() directly (never
   set_workspace_sidebar), so it can't recurse; an empty workspace still renders
   the "No Sidebar Items" placeholder (one child), so isEmpty stays false.
   ========================================================================== */
(function () {
	function patch() {
		if (
			!window.frappe ||
			!frappe.ui ||
			!frappe.ui.Sidebar ||
			!frappe.ui.Sidebar.prototype ||
			frappe.ui.Sidebar.prototype.__jeySidebarFallbackPatched
		) {
			return false;
		}

		var proto = frappe.ui.Sidebar.prototype;
		var original = proto.set_workspace_sidebar;
		proto.__jeySidebarFallbackPatched = true;

		proto.set_workspace_sidebar = function (router) {
			original.call(this, router);
			try {
				var container = this.$items_container;
				if (container && container.children().length > 0) return;

				// Fallback 1: first sidebar that references the current doctype.
				if (this.preferred_sidebars && this.preferred_sidebars.length) {
					this.setup(this.preferred_sidebars[0]);
					return;
				}

				// Fallback 2: the home workspace for the route's module.
				var module = router && router.meta && router.meta.module;
				var workspace = module && this.get_workspace_for_module(module);
				if (workspace && frappe.boot.workspace_sidebar_item[workspace.toLowerCase()]) {
					this.setup(workspace);
				}
			} catch (e) {
				console.error("[jey_theme] sidebar fallback failed", e);
			}
		};
		return true;
	}

	// frappe.ui.Sidebar may not be defined yet when this file runs; retry until
	// the desk bundle has loaded. Patching the prototype affects the already
	// created frappe.app.sidebar instance too, since lookup happens at call time.
	if (!patch()) {
		var tries = 0;
		var timer = setInterval(function () {
			if (patch() || ++tries > 100) clearInterval(timer);
		}, 50);
	}
})();

// ---------------------------------------------------------------------------
// jey_layout inline editor loader.
// Loaded dynamically (NOT via app_include) so the editor's JS/CSS are not added
// to the desk's `Link: rel=preload` response header. On this deployment that
// header is ~4 KB and the edge proxy's proxy_buffer_size is 4 KB, so any extra
// app_include asset tips it over -> 502 "upstream sent too big header". Dynamic
// <script>/<link> tags add nothing to that header. Only the design machine
// (jey_design_mode) actually loads the editor.
// ---------------------------------------------------------------------------
(function () {
	function loadEditor() {
		try {
			if (!window.frappe || !frappe.boot || !frappe.boot.jey_design_mode) return;
			if (document.getElementById("jey-layout-editor-js")) return;
			var css = document.createElement("link");
			css.rel = "stylesheet";
			css.href = "/assets/jey_layout/css/jey_layout.css";
			document.head.appendChild(css);
			var js = document.createElement("script");
			js.id = "jey-layout-editor-js";
			js.src = "/assets/jey_layout/js/jey_layout.js";
			document.head.appendChild(js);
		} catch (e) {}
	}
	if (window.$ && window.frappe) $(document).on("app_ready", loadEditor);
	if (window.frappe && frappe.boot) loadEditor();
})();
