// 全站色系(淺色 / 深色 / 跟隨系統)與主色 — 規格 docs/superpowers/specs/2026-10-20-dark-theme.md
// 每頁 <head> 第一個 script、同步載入(不加 defer):第一次繪製前就把 <html> 加上 jnc-dark,不會先閃一下白底。
// 偏好快取在 localStorage["jnc_theme"] / ["jnc_accent"](同一瀏覽器所有頁與 iframe 共用);帳號層級那份由 FatherPage 登入後寫進來(ChangeTheme / LoginGetMenu)。
// 別的頁或 iframe 改了偏好 → storage 事件;系統深淺色變了 → matchMedia change;兩者都即時套用、不重整。
// 頁面要自己換顏色(canvas、amCharts、動態 style)時:讀 JNCTheme.get(),並監聽 window 的 "jnc-themechange"(e.detail.theme)。
// 主色:各頁 CSS 的主色色碼都寫成 var(--jacc*, 原色碼)。選預設藍(#336699)時不設任何 --jacc*,全部走原色碼 → 外觀與原本完全一樣;
// 選其他主色時在 <html> 的 inline style 設 --jacc*(淺色一組、深色 --jacc-d* 一組,兩組同時設,切色系不必重算)。
(function () {
	if (window.JNCTheme) return;
	var KEY = "jnc_theme", KEY_ACC = "jnc_accent", DEF_ACC = "#336699";
	var cur = null, curAcc;
	var mql = null;
	try { mql = window.matchMedia ? window.matchMedia("(prefers-color-scheme: dark)") : null; } catch (e) { mql = null; }
	function norm(v) { return (v === "dark" || v === "system") ? v : "light"; }
	function normAcc(v) {
		v = String(v || "").trim().toLowerCase();
		if (/^#[0-9a-f]{3}$/.test(v)) v = "#" + v.charAt(1) + v.charAt(1) + v.charAt(2) + v.charAt(2) + v.charAt(3) + v.charAt(3);
		return /^#[0-9a-f]{6}$/.test(v) && v !== DEF_ACC ? v : "";
	}
	function lsGet(k) { try { return window.localStorage.getItem(k); } catch (e) { return null; } }
	function pref() { return norm(lsGet(KEY)); }
	function accent() { return canvasLocked ? "" : normAcc(lsGet(KEY_ACC)); }
	// 平面圖畫布上的「雲平台頁面」(iServerPage<N>)/「網頁」(iWeb<N>)元件嵌站內頁時,畫布內容不跟色系(使用者決定畫布不動):
	// 往上任一層 iframe 是這兩種 → 本頁(及其子框架)固定淺色、預設主色
	var canvasLocked = (function () {
		try {
			var w = window;
			for (var i = 0; i < 8 && w && w.frameElement; i++) {
				if (/^(iServerPage|iWeb)\d+$/.test(w.frameElement.id || "")) return true;
				w = w.parent;
			}
		} catch (e) { }
		return false;
	})();
	function effective() {
		if (canvasLocked) return "light";
		var p = pref();
		if (p === "system") return (mql && mql.matches) ? "dark" : "light";
		return p;
	}
	// ---------- 主色推算(HSL):淺色版 = 選的色(太亮的壓到 L≤45% 讓白字看得清楚);深色版 = 同色相調到與預設深色主色 #5fa2e8 相同亮度 ----------
	function hex2rgb(h) { return [parseInt(h.substr(1, 2), 16), parseInt(h.substr(3, 2), 16), parseInt(h.substr(5, 2), 16)]; }
	function rgb2hex(c) { return "#" + c.map(function (x) { x = Math.max(0, Math.min(255, Math.round(x))); return (x < 16 ? "0" : "") + x.toString(16); }).join(""); }
	function rgb2hsl(c) {
		var r = c[0] / 255, g = c[1] / 255, b = c[2] / 255, mx = Math.max(r, g, b), mn = Math.min(r, g, b), l = (mx + mn) / 2, h = 0, s = 0, d = mx - mn;
		if (d) {
			s = l > .5 ? d / (2 - mx - mn) : d / (mx + mn);
			h = mx === r ? (g - b) / d + (g < b ? 6 : 0) : mx === g ? (b - r) / d + 2 : (r - g) / d + 4;
			h /= 6;
		}
		return [h, s, l];
	}
	function hsl2rgb(h, s, l) {
		function f(p, q, t) { if (t < 0) t += 1; if (t > 1) t -= 1; return t < 1 / 6 ? p + (q - p) * 6 * t : t < .5 ? q : t < 2 / 3 ? p + (q - p) * (2 / 3 - t) * 6 : p; }
		if (!s) return [l * 255, l * 255, l * 255];
		var q = l < .5 ? l * (1 + s) : l + s - l * s, p = 2 * l - q;
		return [f(p, q, h + 1 / 3) * 255, f(p, q, h) * 255, f(p, q, h - 1 / 3) * 255];
	}
	function mix(a, b, t) { return [a[0] * t + b[0] * (1 - t), a[1] * t + b[1] * (1 - t), a[2] * t + b[2] * (1 - t)]; }
	function clamp(x, a, b) { return Math.max(a, Math.min(b, x)); }
	// 相對亮度(WCAG);深色版各色以「與預設藍同亮度」為目標找 HSL 的 L,不同色相對比強度一致(青/綠不刺眼、紫/紅不偏暗)
	function lum(c) { var v = c.map(function (x) { x /= 255; return x <= .03928 ? x / 12.92 : Math.pow((x + .055) / 1.055, 2.4); }); return .2126 * v[0] + .7152 * v[1] + .0722 * v[2]; }
	function atLum(h, s, target) { var lo = .25, hi = .9; for (var i = 0; i < 24; i++) { var m = (lo + hi) / 2; if (lum(hsl2rgb(h, s, m)) < target) lo = m; else hi = m; } return hsl2rgb(h, s, (lo + hi) / 2); }
	function derive(hex) {
		var hsl = rgb2hsl(hex2rgb(hex)), h = hsl[0], s = hsl[1], l = Math.min(hsl[2], .45);
		var gray = s < .15, sd = gray ? s : clamp(s * 1.5, .55, .85);	// 係數由已確認的預設深色值 #5fa2e8 / #4a90dc / #8cc4f5 反推
		var main = hsl2rgb(h, s, l), dMain = atLum(h, sd, .331), card = [34, 39, 47], white = [255, 255, 255];
		return {
			// 半透明主色用:rgba(var(--jacc-rgb, 51, 102, 153), .18)(逗號分量,舊 iOS 也支援;不用 color-mix)
			"--jacc-rgb": main.map(Math.round).join(", "),
			"--jacc-d-rgb": dMain.map(Math.round).join(", "),
			"--jacc": rgb2hex(main),
			"--jacc-strong": rgb2hex(hsl2rgb(h, s, l * .8)),
			"--jacc-soft": rgb2hex(mix(main, white, .08)),
			"--jacc-d": rgb2hex(dMain),
			"--jacc-d-strong": rgb2hex(atLum(h, gray ? s : clamp(s * 1.36, .5, .78), .266)),
			"--jacc-d-soft": rgb2hex(mix(dMain, card, .15)),
			"--jacc-d-soft2": rgb2hex(mix(dMain, card, .24)),
			"--jacc-d-link": rgb2hex(atLum(h, gray ? s : clamp(s * 1.68, .6, .9), .517))
		};
	}
	var ACC_PROPS = ["--jacc-rgb", "--jacc-d-rgb", "--jacc", "--jacc-strong", "--jacc-soft", "--jacc-d", "--jacc-d-strong", "--jacc-d-soft", "--jacc-d-soft2", "--jacc-d-link"];
	function applyAccent() {
		var a = accent(), de = document.documentElement;
		if (a === curAcc || !de) return;
		var first = curAcc === undefined;
		curAcc = a;
		var v = a ? derive(a) : null;
		for (var i = 0; i < ACC_PROPS.length; i++) {
			if (v) de.style.setProperty(ACC_PROPS[i], v[ACC_PROPS[i]]); else de.style.removeProperty(ACC_PROPS[i]);
		}
		// 只換主色時也派發 jnc-themechange(detail.accent = true),JS 自己上色的元件(圖表、進度條…)不必重整就換色
		if (!first) emit(cur || effective(), true);
	}
	function emit(t, acc) {
		var ev, d = { theme: t, accent: !!acc };
		try { ev = new CustomEvent("jnc-themechange", { detail: d }); }
		catch (e) { ev = document.createEvent("CustomEvent"); ev.initCustomEvent("jnc-themechange", false, false, d); }
		try { window.dispatchEvent(ev); } catch (e) { }
	}
	function apply() {
		applyAccent();
		var t = effective();
		if (t === cur) return t;
		var first = cur === null;
		cur = t;
		var de = document.documentElement;
		if (de) {
			if (t === "dark") de.classList.add("jnc-dark"); else de.classList.remove("jnc-dark");
		}
		if (!first) emit(t);
		return t;
	}
	// 本頁套用後,把底下同源 iframe 也叫一次(storage 事件在少數 WebKit 版本不一定送到子框架)
	function applyTree(win) {
		try { if (win.JNCTheme && win.JNCTheme.refresh) win.JNCTheme.refresh(); } catch (e) { }
		var fr;
		try { fr = win.frames; } catch (e) { return; }
		for (var i = 0; fr && i < fr.length; i++) {
			try { applyTree(fr[i]); } catch (e) { }
		}
	}
	function applyAll() {
		var top = window;
		try { while (top.parent && top.parent !== top && top.parent.JNCTheme) top = top.parent; } catch (e) { }
		applyTree(top);
		apply();
	}
	window.JNCTheme = {
		get: function () { return cur || effective(); },
		pref: pref,
		// 目前主色(#rrggbb);預設藍回 DEF_ACC
		accent: function () { return accent() || DEF_ACC; },
		defaultAccent: DEF_ACC,
		derive: derive,
		locked: canvasLocked,
		// 只改這台瀏覽器的快取並套用到所有開著的頁面;存到帳號由 FatherPage 送 ChangeTheme
		set: function (p) {
			p = norm(p);
			if (canvasLocked) return apply();	// 畫布內嵌的頁(例:嵌入的外框登入後套帳號色系)不改外層的快取
			try { window.localStorage.setItem(KEY, p); } catch (e) { }
			applyAll();
			return cur;
		},
		setAccent: function (hex) {
			if (canvasLocked) return;
			var a = normAcc(hex);
			try { if (a) window.localStorage.setItem(KEY_ACC, a); else window.localStorage.removeItem(KEY_ACC); } catch (e) { }
			applyAll();
		},
		refresh: apply
	};
	apply();
	try {
		window.addEventListener("storage", function (e) { if (!e || e.key === KEY || e.key === KEY_ACC || e.key === null) apply(); });
	} catch (e) { }
	if (mql) {
		var onSys = function () { if (pref() === "system") apply(); };
		try { if (mql.addEventListener) mql.addEventListener("change", onSys); else if (mql.addListener) mql.addListener(onSys); } catch (e) { }
	}
	// bfcache 回上一頁時偏好可能已在別頁改過
	try { window.addEventListener("pageshow", apply); } catch (e) { }
})();
