/**
 * 泽森排盘 · 五页框架（首页→基本信息/基础排盘/专业细断/键振详断/会员收藏）
 * 蓝本：问真八字。排盘用 lunar.js（十神/藏干/星运/纳音/空亡/大运），
 * 判局用 KREngine + KRRelations（令地/双冻结/键·共振·纠缠/烈度）。
 * © 2026 Jason Li（泽森）
 */
(function () {
  "use strict";
  var $ = function (id) { return document.getElementById(id); };
  var DZ = ["子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥"];
  var STEM_WX = { 甲: "木", 乙: "木", 丙: "火", 丁: "火", 戊: "土", 己: "土", 庚: "金", 辛: "金", 壬: "水", 癸: "水" };
  var BR_WX = { 子: "水", 丑: "土", 寅: "木", 卯: "木", 辰: "土", 巳: "火", 午: "火", 未: "土", 申: "金", 酉: "金", 戌: "土", 亥: "水" };
  var CS = ["长生", "沐浴", "冠带", "临官", "帝旺", "衰", "病", "死", "墓", "绝", "胎", "养"];
  var CS_START = { 甲: "亥", 丙: "寅", 戊: "寅", 庚: "巳", 壬: "申", 乙: "午", 丁: "酉", 己: "酉", 辛: "子", 癸: "卯" };
  var YANG = "甲丙戊庚壬";
  function wxc(ch) { var w = STEM_WX[ch] || BR_WX[ch]; return w ? "wx-" + w : ""; }
  function changSheng(gan, zhi) {
    var s = DZ.indexOf(CS_START[gan]), z = DZ.indexOf(zhi);
    var idx = YANG.indexOf(gan) >= 0 ? (z - s + 12) % 12 : (s - z + 12) % 12;
    return window.KRLang ? KRLang.cs(CS[idx]) : CS[idx];
  }

  // 状态
  var state = { gender: "M", cal: "S", ts: true };
  var cur = "zh", themeMode = "light", lastCtx = null, proState = null, keyFlow = null;
  var liveAnchors = { birth: null, device: null, stand: null };
  var layerState = null; // 问真式洋葱：点到哪一层就同步到哪一层

  // 令：旺1.5／相1.25／休0.75／囚⅞／死⅝；戌影响1.0、午影响1.0×¼，先戌后午。合化临界仍用月支本气旺相化、休囚死绊。
  var STEM_WXI = { 甲: 0, 乙: 0, 丙: 1, 丁: 1, 戊: 2, 己: 2, 庚: 3, 辛: 3, 壬: 4, 癸: 4 };
  var BR_WXI = { 子: 4, 丑: 2, 寅: 0, 卯: 0, 辰: 2, 巳: 1, 午: 1, 未: 2, 申: 3, 酉: 3, 戌: 2, 亥: 4 };
  var HIDE_Z = {
    子: [["癸", 1]], 丑: [["己", 0.6], ["癸", 0.3], ["辛", 0.1]], 寅: [["甲", 0.6], ["丙", 0.3], ["戊", 0.1]], 卯: [["乙", 1]],
    辰: [["戊", 0.6], ["乙", 0.3], ["癸", 0.1]], 巳: [["丙", 0.6], ["戊", 0.3], ["庚", 0.1]], 午: [["丁", 0.7], ["己", 0.3]],
    未: [["己", 0.6], ["丁", 0.3], ["乙", 0.1]], 申: [["庚", 0.6], ["壬", 0.3], ["戊", 0.1]], 酉: [["辛", 1]],
    戌: [["戊", 0.6], ["辛", 0.3], ["丁", 0.1]], 亥: [["壬", 0.7], ["甲", 0.3]],
  };
  function lingStage(elIdx, monthBranch) {
    var ling = BR_WXI[monthBranch];
    if (elIdx === ling) return "wang";           // 旺（当令）
    if ((ling + 1) % 5 === elIdx) return "xiang"; // 令生者相
    if ((elIdx + 1) % 5 === ling) return "xiu";   // 生令者休
    if ((ling + 2) % 5 === elIdx) return "si";    // 令克者 → 死
    return "qiu";                                  // 克令者 → 囚
  }
  function lingF(elIdx, monthBranch) {
    var idx = typeof monthBranch === "number" ? monthBranch : DZ.indexOf(monthBranch);
    if (window.KREngine && KREngine.layerLingFactor) {
      return KREngine.layerLingFactor(elIdx, idx, window.KRParams);
    }
    var L = (window.KRParams && window.KRParams.lingLayer) || { wang: 1.5, xiang: 1.25, xiu: 0.75, qiu: 0.75, si: 0.25 };
    return L[lingStage(elIdx, monthBranch)] || 1;
  }
  function campSplit(el, dayStem) {
    var WX = ["木", "火", "土", "金", "水"], dm = STEM_WXI[dayStem];
    var wo = (el[WX[dm]] || 0) + (el[WX[(dm + 4) % 5]] || 0);
    var ta = (el[WX[(dm + 1) % 5]] || 0) + (el[WX[(dm + 2) % 5]] || 0) + (el[WX[(dm + 3) % 5]] || 0);
    return { wo: wo, ta: ta };
  }
  // 八字含藏干「字能」：先戌令 1−(1−L)×E戌，后午地 1−(1−D)×¼×E午。地打满盘。
  function lingApply(L, E) {
    if (window.KREngine && KREngine.lingApply) return KREngine.lingApply(L, E);
    return 1 - (1 - L) * (E == null ? 1 : E);
  }
  function diApply(D, E, diRel) {
    if (window.KREngine && KREngine.diApply) return KREngine.diApply(D, E, diRel);
    var r = diRel == null ? 0.25 : diRel;
    return 1 - (1 - D) * r * (E == null ? 1 : E);
  }
  function charEnergy(pillars, diRel) {
    diRel = diRel == null ? 0.25 : diRel;
    var monthBr = pillars.month[1];
    var hourBr = pillars.time[1];
    var stemBase = { year: 0.75, month: 0.875, day: 1.0, time: 0.75 };
    var branchBase = { year: 0.75, month: 1.0, day: 0.875, time: 0.75 };
    var idx = { year: 0, month: 1, day: 2, time: 3 };
    var fall = window.KREngine || {};
    return [["year", "年"], ["month", "月"], ["day", "日"], ["time", "时"]].map(function (kk) {
      var gz = pillars[kk[0]], gan = gz[0], zhi = gz[1], i = idx[kk[0]];
      var eLingG = fall.lingFallE ? fall.lingFallE(i, "gan") : 1;
      var eLingZ = fall.lingFallE ? fall.lingFallE(i, "zhi") : 1;
      var eDiG = fall.diFallE ? fall.diFallE(i, "gan") : 1;
      var eDiZ = fall.diFallE ? fall.diFallE(i, "zhi") : 1;
      var ge = stemBase[kk[0]] * lingApply(lingF(STEM_WXI[gan], monthBr), eLingG)
        * diApply(lingF(STEM_WXI[gan], hourBr), eDiG, diRel);
      var hides = (HIDE_Z[zhi] || []).map(function (x) {
        return { ch: x[0], e: +(x[1] * branchBase[kk[0]] * lingApply(lingF(STEM_WXI[x[0]], monthBr), eLingZ)
          * diApply(lingF(STEM_WXI[x[0]], hourBr), eDiZ, diRel)).toFixed(3) };
      });
      return { key: kk[0], lab: kk[1], gan: { ch: gan, e: +ge.toFixed(3) }, zhi: zhi, hides: hides };
    });
  }
  function charEnergyLive(c, extras) {
    var di = (window.KRParams && window.KRParams.diRelativeToLing) || 0.25;
    var base = charEnergy(c.pillars, di);
    var fall = window.KREngine || {};
    (extras || []).forEach(function (dp) {
      if (!dp.gz || dp.gz.length < 2) return;
      var gan = dp.gz[0], zhi = dp.gz[1];
      var kind = dp.kind || (/流/.test(dp.label || "") ? "flow" : "yun");
      var e;
      if (kind === "flow") {
        var idx = dp.flowIdx != null ? dp.flowIdx
          : (fall.flowLayerIndex ? fall.flowLayerIndex(dp.label, 0) : 0);
        e = fall.flowLayerE ? fall.flowLayerE(idx) : Math.pow(0.5, idx);
      } else {
        e = dp.scale != null ? dp.scale : (dp.label === "小运" ? 0.125 : 1);
      }
      var hides = (HIDE_Z[zhi] || []).map(function (x) {
        return { ch: x[0], e: +(x[1] * e).toFixed(3) };
      });
      base.push({ key: dp.label, lab: dp.label, gan: { ch: gan, e: +e.toFixed(3) }, zhi: zhi, hides: hides });
    });
    return base;
  }

  // 五语文案（界面壳；命理干支/断语为通用汉字，保留）
  var LANG = {
    zh: { tagline: "全球真太阳时 · 键共振判局 · 参数 2026-08-11", name: "姓名", gender: "性别", male: "男", female: "女", sexMale: "男", sexFemale: "女", calendar: "历法", solar: "公历", lunar: "农历", birthDate: "出生日", time: "时间", birthPlace: "出生地", standPlace: "现住地", lon: "经度", lat: "纬度", waterFire: "水火", trueSolar: "真太阳", on: "开", off: "关", paipan: "排 盘", tabInfo: "基本信息", tabChart: "基础排盘", tabPro: "专业细断", tabKey: "键振详断", tabVip: "会员收藏", water: "水", fire: "火" },
    en: { tagline: "Global true-solar · Key-resonance judgment · params 2026-08-11", name: "Name", gender: "Gender", male: "Male", female: "Female", calendar: "Calendar", solar: "Solar", lunar: "Lunar", birthDate: "Birth date", time: "Time", birthPlace: "Birthplace", standPlace: "Now city", lon: "Longitude", lat: "Latitude", waterFire: "Water/Fire", trueSolar: "True solar", on: "On", off: "Off", paipan: "Chart", tabInfo: "Info", tabChart: "Chart", tabPro: "Pro", tabKey: "Resonance", tabVip: "Member", water: "Water", fire: "Fire" },
    es: { tagline: "Tiempo solar verdadero global · Resonancia clave · 2026-08-11", name: "Nombre", gender: "Género", male: "Hombre", female: "Mujer", calendar: "Calendario", solar: "Solar", lunar: "Lunar", birthDate: "Fecha nac.", time: "Hora", birthPlace: "Lugar nac.", standPlace: "Ciudad actual", lon: "Longitud", lat: "Latitud", waterFire: "Agua/Fuego", trueSolar: "Solar real", on: "Sí", off: "No", paipan: "Calcular", tabInfo: "Datos", tabChart: "Carta", tabPro: "Pro", tabKey: "Resonancia", tabVip: "Miembro", water: "Agua", fire: "Fuego" },
    ja: { tagline: "全球真太陽時 · キー共鳴判局 · 2026-08-11", name: "氏名", gender: "性別", male: "男", female: "女", calendar: "暦", solar: "新暦", lunar: "旧暦", birthDate: "生年月日", time: "時刻", birthPlace: "出生地", standPlace: "現住地", lon: "経度", lat: "緯度", waterFire: "水火", trueSolar: "真太陽", on: "入", off: "切", paipan: "排 盤", tabInfo: "基本情報", tabChart: "排盤", tabPro: "専門", tabKey: "キー詳断", tabVip: "会員", water: "水", fire: "火" },
    ar: { tagline: "التوقيت الشمسي الحقيقي · حكم رنين المفاتيح · 2026-08-11", name: "الاسم", gender: "الجنس", male: "ذكر", female: "أنثى", calendar: "التقويم", solar: "ميلادي", lunar: "قمري", birthDate: "الميلاد", time: "الوقت", birthPlace: "المكان", standPlace: "المدينة الحالية", lon: "الطول", lat: "العرض", waterFire: "ماء/نار", trueSolar: "شمسي", on: "تشغيل", off: "إيقاف", paipan: "احسب", tabInfo: "معلومات", tabChart: "اللوحة", tabPro: "احترافي", tabKey: "رنين", tabVip: "عضوية", water: "ماء", fire: "نار" },
  };
  function t(k, vars) { return window.KRLang ? KRLang.t(k, vars) : ((LANG[cur] && LANG[cur][k]) || LANG.zh[k] || k); }
  function term(s) { return window.KRLang ? KRLang.term(s) : s; }
  function applyLang() {
    if (window.KRLang) { KRLang.set(cur); KRLang.applyChrome(document); }
    Array.prototype.forEach.call(document.querySelectorAll("[data-k]"), function (el) {
      var v = t(el.getAttribute("data-k")); if (v) el.textContent = v;
    });
    document.body.setAttribute("dir", cur === "ar" ? "rtl" : "ltr");
    Array.prototype.forEach.call($("langs").querySelectorAll("button"), function (b) { b.classList.toggle("on", b.getAttribute("data-lang") === cur); });
    renderWF("wfHome", homeWF());
    mountDemo();
    if (lastCtx) render(lastCtx);
    markTabPays();
  }
  function mountDemo() {
    if ($("demoCase") && window.KRShifu && KRShifu.homeHtml) {
      $("demoCase").innerHTML = KRShifu.homeHtml();
      KRShifu.homeBind(function () { paipan(); });
    }
    if ($("defaultHint")) $("defaultHint").textContent = t("defaultHint");
  }
  function setTheme(mode) {
    themeMode = mode || "light";
    var dark = themeMode === "dark" || (themeMode === "auto" && (function () { var h = new Date().getHours(); return h < 6 || h >= 18; })());
    document.body.setAttribute("data-theme", dark ? "dark" : "light");
    try { localStorage.setItem("kr_theme_pref", themeMode === "auto" ? "auto" : (dark ? "night" : "day")); } catch (e) { }
    var b = $("themeBtn"); if (b) b.textContent = themeMode === "auto" ? "◐ 自动" : (dark ? "☾ 夜" : "☀ 日");
  }
  function bootTheme() {
    var q = "";
    try { q = new URLSearchParams(location.search).get("theme") || ""; } catch (e) { }
    if (q === "light" || q === "day") return setTheme("light");
    if (q === "dark" || q === "night") return setTheme("dark");
    var pref = "";
    try { pref = localStorage.getItem("kr_theme_pref") || ""; } catch (e) { }
    if (pref === "night") return setTheme("dark");
    if (pref === "auto") return setTheme("auto");
    setTheme("light");
  }
  function homeWF() {
    var lat = parseFloat($("lat") ? $("lat").value : "0"); if (isNaN(lat)) lat = 0;
    var water = Math.min(1, Math.abs(lat) / 90), fire = 1 - water;
    return { water: water, fire: fire };
  }
  function pairInline(aL, aV, aC, bL, bV, bC) {
    function one(L, V, C) { return '<span class="wf"><span style="color:' + C + '">' + L + '</span><span class="tr"><span class="fl" style="width:' + Math.round(Math.min(1, Math.max(0, V)) * 100) + '%;background:' + C + '"></span></span><span style="color:var(--mist)">' + V.toFixed(2) + '</span></span>'; }
    return one(aL, aV, aC) + one(bL, bV, bC);
  }
  function wxCol(w) {
    return { 木: "var(--wood)", 火: "var(--fire)", 土: "var(--earth)", 金: "var(--metal)", 水: "var(--water)" }[w] || "var(--earth)";
  }
  function wfBarsHTML(wf) { return pairInline(t("water"), wf.water, "var(--water)", t("fire"), wf.fire, "var(--fire)"); }
  function mwBarsHTML(mw) { return pairInline(t("metal"), (mw && mw.metal) || 0, "var(--metal)", t("wood"), (mw && mw.wood) || 0, "var(--wood)"); }
  function isAuthor() { var A = window.KRAuthor; return !!(A && (A.unlocked || (A.ensure && A.ensure()))); }
  function hasPro() { return window.KRPricing ? window.KRPricing.hasPro() : isAuthor(); }
  function hasResonance() { return window.KRPricing ? window.KRPricing.hasResonance() : isAuthor(); }
  function z2(n) { return (n < 10 ? "0" : "") + n; }
  function nowDT() { var d = new Date(); return { y: d.getFullYear(), mo: d.getMonth() + 1, d: d.getDate(), h: d.getHours(), mi: d.getMinutes() }; }
  function nowAtCity(cityId, zoneFallback) {
    var CT = window.KRCities;
    if (CT && CT.nowAtCity) return CT.nowAtCity(cityId, zoneFallback);
    return nowDT();
  }
  function cityOptsHTML(selectedId) {
    var opts = '<option value="">— 自定义经纬 —</option>';
    var CT = window.KRCities;
    if (!CT) return opts;
    Object.keys(CT.groups).forEach(function (gr) {
      opts += '<optgroup label="' + gr + '">';
      CT.groups[gr].forEach(function (o) {
        opts += '<option value="' + o.id + '"' + (o.id === selectedId ? " selected" : "") + ">" + o.name + "</option>";
      });
      opts += "</optgroup>";
    });
    return opts;
  }
  function fillCitySelect(sel, selectedId) {
    if (!sel) return;
    sel.innerHTML = cityOptsHTML(selectedId);
    if (selectedId) sel.value = selectedId;
  }
  function placeOf(cityId, fallbackLon, fallbackLat, fallbackZone) {
    var o = cityId && window.KRCities && window.KRCities.byId[cityId];
    if (o) return { id: o.id, name: o.name, lon: o.lng, lat: o.lat, zone: o.zone, tz: o.tz };
    return { id: cityId || "", name: "", lon: fallbackLon, lat: fallbackLat, zone: fallbackZone, tz: null };
  }
  function liveStandName(c) {
    var a = liveAnchors.stand;
    if (a) {
      var o = a.city && window.KRCities && KRCities.byId[a.city];
      if (o) return o.name;
      if (a.lon != null && a.lat != null) return t("customPlace");
    }
    var id = (proState && proState.city) || (keyFlow && keyFlow.city) || c.standId || "";
    var hit = id && window.KRCities && KRCities.byId[id];
    return (hit && hit.name) || c.standName || c.cityName || "";
  }
  function liveStandLon(c) {
    var a = liveAnchors.stand || proState || keyFlow;
    if (a && a.lon != null && !isNaN(a.lon)) return +a.lon;
    return c.standLon != null ? +c.standLon : +c.lon;
  }
  function liveStandLat(c) {
    var a = liveAnchors.stand || proState || keyFlow;
    if (a && a.lat != null && !isNaN(a.lat)) return +a.lat;
    return c.standLat != null ? +c.standLat : +c.lat;
  }
  function placeNameOf(a, fallback) {
    if (!a) return fallback || "—";
    if (a.name) return a.name;
    var o = a.city && window.KRCities && KRCities.byId[a.city];
    if (o) return o.name;
    if (a.lon != null && a.lat != null) return t("customPlace");
    return fallback || "—";
  }
  function fmtAnchorDT(dt) {
    if (!dt) return "—";
    return dt.y + "-" + z2(dt.mo) + "-" + z2(dt.d) + " " + z2(dt.h) + ":" + z2(dt.mi);
  }
  function fmtAnchorGeo(a) {
    if (!a || a.lon == null || a.lat == null || isNaN(a.lon) || isNaN(a.lat)) return "—";
    return Number(a.lon).toFixed(2) + ", " + Number(a.lat).toFixed(2);
  }
  function threeStepHTML() {
    var b = liveAnchors.birth, d = liveAnchors.device, s = liveAnchors.stand;
    function line(lab, a) {
      return '<div>' + lab + " " + placeNameOf(a) + " · " + fmtAnchorGeo(a) + " · " + fmtAnchorDT(a && a.dt) + "</div>";
    }
    return '<div class="verdict" style="font-size:12px;color:var(--bronze);margin:0 0 6px">' +
      line(t("stepBirth"), b) + line(t("stepDevice"), d) + line(t("stepStand"), s) + "</div>" +
      '<div class="verdict" style="font-size:11.5px;color:var(--mist);margin:0 0 8px">' + t("stepHint") + "</div>";
  }
  function localNow(cityId, lon, lat, zone) {
    if (cityId && window.KRCities && KRCities.byId[cityId]) return nowAtCity(cityId, zone);
    if (window.KRCities && KRCities.nowAtLonLat && lon != null && lat != null && !isNaN(lon) && !isNaN(lat)) {
      return KRCities.nowAtLonLat(lat, lon, zone);
    }
    return nowAtCity("", zone != null ? zone : (lon / 15));
  }
  var lastDeviceFix = null;
  function loadDeviceFix() {
    if (lastDeviceFix && lastDeviceFix.lat != null && lastDeviceFix.lon != null) return lastDeviceFix;
    try {
      var o = JSON.parse(localStorage.getItem("zeisen_device_fix") || "null");
      if (o && o.lat != null && o.lon != null) lastDeviceFix = o;
    } catch (e) { }
    return lastDeviceFix;
  }
  function persistDeviceFix(lat, lon) {
    lastDeviceFix = { lat: lat, lon: lon, at: Date.now() };
    try { localStorage.setItem("zeisen_device_fix", JSON.stringify(lastDeviceFix)); } catch (e) { }
  }
  function placeFromCoords(lat, lon) {
    var hit = window.KRCities && KRCities.nearest ? KRCities.nearest(lat, lon, 180) : null;
    if (hit && hit.city) return { city: hit.city.id, lon: lon, lat: lat };
    return { city: "", lon: lon, lat: lat };
  }
  function commitLive(c, stand, opts) {
    opts = opts || {};
    if (isNaN(stand.lon) || isNaN(stand.lat)) { stand.lon = c.lon; stand.lat = c.lat; }
    liveAnchors.stand = stand;
    proState = { city: stand.city || "", lon: stand.lon, lat: stand.lat, dt: stand.dt };
    keyFlow = { city: stand.city || "", lon: stand.lon, lat: stand.lat, dt: stand.dt };
    if (opts.syncLayer !== false) syncDtToLayer(stand.dt);
    if (c && opts.render !== false) {
      renderOnion(c); renderPro(c); renderKey(c); renderChart(c); renderInfo(c);
    }
  }
  function applyCityLocalNow(c, cityId) {
    if (!cityId) {
      var prev = proState || keyFlow || {};
      applyCoordsLocalNow(c, prev.lon, prev.lat);
      return;
    }
    var prev2 = proState || keyFlow || {};
    var pl = placeOf(cityId, prev2.lon != null ? prev2.lon : c.lon, prev2.lat != null ? prev2.lat : c.lat, c.standZone != null ? c.standZone : c.zone);
    commitLive(c, { city: pl.id || cityId, lon: pl.lon, lat: pl.lat, dt: localNow(pl.id || cityId, pl.lon, pl.lat, pl.zone), source: "place" });
  }
  function applyCoordsLocalNow(c, lon, lat) {
    lon = parseFloat(lon); lat = parseFloat(lat);
    if (isNaN(lon) || isNaN(lat)) return;
    var p = placeFromCoords(lat, lon);
    var zone = (p.city && window.KRCities && KRCities.byId[p.city]) ? KRCities.byId[p.city].zone : lon / 15;
    commitLive(c, { city: p.city || "", lon: lon, lat: lat, dt: localNow(p.city, lon, lat, zone), source: "place" });
  }
  function seedDeviceFromKnown() {
    var fix = loadDeviceFix();
    if (fix) {
      var p = placeFromCoords(fix.lat, fix.lon);
      liveAnchors.device = { city: p.city || "", lon: p.lon, lat: p.lat, dt: nowDT(), source: "device" };
      return;
    }
    var tzCity = window.KRCities && KRCities.byDeviceTz && KRCities.byDeviceTz();
    if (tzCity) {
      liveAnchors.device = { city: tzCity.id, lon: tzCity.lng, lat: tzCity.lat, dt: nowDT(), source: "device" };
      return;
    }
    liveAnchors.device = { city: "", lon: null, lat: null, dt: nowDT(), source: "device" };
  }
  function adoptDevice(c, city, lon, lat) {
    var dt = nowDT();
    liveAnchors.device = { city: city || "", lon: lon, lat: lat, dt: dt, source: "device" };
    commitLive(c, { city: city || "", lon: lon, lat: lat, dt: dt, source: "device" });
  }
  function refreshDevice(c, opts) {
    opts = opts || {};
    function done(city, lon, lat) {
      var dt = nowDT();
      liveAnchors.device = { city: city || "", lon: lon, lat: lat, dt: dt, source: "device" };
      var stand = liveAnchors.stand;
      if (opts.adopt || !stand || stand.source === "device") adoptDevice(c, city, lon, lat);
      else if (c) { renderPro(c); renderKey(c); renderInfo(c); }
      if (opts.onDone) opts.onDone();
    }
    function fallback() {
      var fix = loadDeviceFix();
      if (fix) { var p = placeFromCoords(fix.lat, fix.lon); done(p.city, p.lon, p.lat); return; }
      var tzCity = window.KRCities && KRCities.byDeviceTz && KRCities.byDeviceTz();
      if (tzCity) { done(tzCity.id, tzCity.lng, tzCity.lat); return; }
      var ps = proState || keyFlow || {};
      done(ps.city || "", ps.lon != null ? ps.lon : c.lon, ps.lat != null ? ps.lat : c.lat);
    }
    if (!navigator.geolocation) { fallback(); return; }
    navigator.geolocation.getCurrentPosition(
      function (pos) {
        var lat = pos.coords.latitude, lon = pos.coords.longitude;
        persistDeviceFix(lat, lon);
        var p = placeFromCoords(lat, lon);
        done(p.city, p.lon, p.lat);
      },
      function () { fallback(); },
      { enableHighAccuracy: true, timeout: 8000, maximumAge: 60000 }
    );
  }
  function applyShiDiShiKe(c, btn) {
    var locLabel = t("locating");
    if (locLabel === "locating") locLabel = "定位中…";
    if (btn) { btn.disabled = true; btn.textContent = locLabel; }
    refreshDevice(c, {
      adopt: true,
      onDone: function () {
        if (btn) { btn.disabled = false; btn.textContent = t("locateNow"); }
      }
    });
  }
  function renderWF(elId, wf) { var el = $(elId); if (el) el.innerHTML = wfBarsHTML(wf); }
  function sumEl(el) { var s = 0; ["木", "火", "土", "金", "水"].forEach(function (w) { s += (el && el[w]) || 0; }); return s; }
  function elBars(el) {
    var COL = { 木: "var(--wood)", 火: "var(--fire)", 土: "var(--earth)", 金: "var(--metal)", 水: "var(--water)" };
    var max = Math.max.apply(null, ["木", "火", "土", "金", "水"].map(function (w) { return el[w] || 0; })) || 1;
    return ["木", "火", "土", "金", "水"].map(function (w) {
      var v = el[w] || 0, pct = Math.round(v / max * 100);
      return '<div class="bar"><span class="n ' + wxc(w) + '">' + term(w) + '</span><span class="track"><span class="fill" style="width:' + pct + '%;background:' + COL[w] + '"></span></span><span style="width:44px;text-align:right;color:var(--mist)">' + v.toFixed(2) + '</span></div>';
    }).join("");
  }
  function stagesOf(A) {
    if (A && A.energy && A.energy.stages) return A.energy.stages;
    var el0 = (A && A.energy && (A.energy.lingDi || A.energy.diHou)) || {};
    var el1 = (A && A.energy && (A.energy.shengKeZi || A.energy.shengKe)) || el0;
    var el2 = (A && A.energy && (A.energy.liveZi || A.energy.live || A.energy.flow)) || el1;
    var z0 = (A && A.zhengDiHou) || {};
    var g = (A && A.geo) || {};
    var led = A && A.xinGong && A.xinGong.ledger;
    return {
      beforeWork: { zi: el0, wo: z0.wo, ta: z0.ta, nei: led && led.nei, wai: led && led.wai, fireWater: g.fireWaterBirth, metalWood: g.metalWoodBirth },
      beforeYun: { zi: el1, wo: z0.wo, ta: z0.ta, nei: led && (led.neiAfter != null ? led.neiAfter : led.nei), wai: led && (led.waiAfter != null ? led.waiAfter : led.wai), fireWater: g.fireWaterBirth, metalWood: g.metalWoodBirth },
      afterFlow: { zi: el2, wo: z0.wo, ta: z0.ta, nei: led && (led.neiFlow != null ? led.neiFlow : led.nei), wai: led && (led.waiFlow != null ? led.waiFlow : led.wai), fireWater: g.fireWaterStand, metalWood: g.metalWoodStand },
    };
  }
  function n3(v) { var n = Number(v); return isNaN(n) ? 0 : n; }
  function colOpen(n) {
    if (n === 1) return true;
    if (n === 2) return hasPro();
    return hasResonance();
  }
  function markTabPays() {
    var bar = $("tabbar"); if (!bar) return;
    var proBtn = bar.querySelector('[data-p="pro"]');
    var keyBtn = bar.querySelector('[data-p="key"]');
    if (proBtn) proBtn.classList.toggle("need-pay", !hasPro());
    if (keyBtn) keyBtn.classList.toggle("need-pay", !hasResonance());
  }
  function tchip(n, page) {
    var meta = { 1: ["stage1", "stage1Sub", "base"], 2: ["stage2", "stage2Sub", "pro"], 3: ["stage3", "stage3Sub", "key"] }[n];
    var open = colOpen(n);
    return '<span class="tchip t' + n + (page === meta[2] ? " on" : "") + (open ? "" : " locked") + '">' +
      t(meta[0]) + "<small>" + t(meta[1]) + (open ? "" : " 🔒") + "</small></span>";
  }
  function tierBanner(page) {
    var now = { base: ["tierBase", "tierBasePay"], pro: ["tierPro", "tierProPay"], key: ["tierKey", "tierKeyPay"] }[page] || ["tierBase", "tierBasePay"];
    return '<div class="tier-banner page-' + page + '">' +
      '<div class="tier-now">' + t("pageNow") + " · " + t(now[0]) + " · " + t(now[1]) + "</div>" +
      '<div class="tier-row">' + tchip(1, page) + tchip(2, page) + tchip(3, page) + "</div>" +
      '<div class="verdict" style="font-size:11.5px;color:var(--mist);margin-top:6px">' + t("tierSplitHint") + "</div></div>";
  }
  function triBar(label, v1, v2, v3, color, cls, page) {
    v1 = n3(v1); v2 = n3(v2); v3 = n3(v3);
    var o1 = colOpen(1), o2 = colOpen(2), o3 = colOpen(3);
    var max = 0.01;
    if (o1) max = Math.max(max, v1);
    if (o2) max = Math.max(max, v2);
    if (o3) max = Math.max(max, v3);
    function cell(v, n, open) {
      var on = (page === "base" && n === 1) || (page === "pro" && n === 2) || (page === "key" && n === 3);
      if (!open) return '<span class="track t' + n + ' locked" title="' + t("stageLock") + '"><span class="fill"></span></span>';
      return '<span class="track t' + n + (on ? " on" : "") + '"><span class="fill" style="width:' + Math.round(v / max * 100) + '%;background:' + color + '"></span></span>';
    }
    function num(v, open) { return open ? v.toFixed(2) : "🔒"; }
    return '<div class="tribar"><span class="n ' + (cls || "") + '">' + label + "</span>" +
      '<span class="tri-col">' + cell(v1, 1, o1) + cell(v2, 2, o2) + cell(v3, 3, o3) + "</span>" +
      '<span class="tri-num">' + num(v1, o1) + " → " + num(v2, o2) + " → " + num(v3, o3) + "</span></div>";
  }
  function threeStageHTML(stages, opts) {
    opts = opts || {};
    if (!stages) return "";
    var page = opts.page || "base";
    var a = stages.beforeWork || {}, b = stages.beforeYun || {}, c = stages.afterFlow || {};
    function zi(st, w) { return ((st.zi || st.el || {})[w]) || 0; }
    var html = '<div class="merge">' +
      '<div class="merge-leg">' + tchip(1, page) + tchip(2, page) + tchip(3, page) + "</div>" +
      '<div class="verdict" style="font-size:11.5px;color:var(--mist);margin:0 0 4px">' + t("stageHint") + "</div>";
    html += '<div class="merge-g">' + t("wfBirth") + " / " + t("wfStand") + "</div>";
    var fwA = a.fireWater || {}, fwB = b.fireWater || {}, fwC = c.fireWater || {};
    html += triBar(t("water"), fwA.water, fwB.water, fwC.water, "var(--water)", "wx-水", page);
    html += triBar(t("fire"), fwA.fire, fwB.fire, fwC.fire, "var(--fire)", "wx-火", page);
    if (opts.metalWood) {
      html += '<div class="merge-g">' + t("mwBirth") + " / " + t("mwStand") + "</div>";
      var mwA = a.metalWood || {}, mwB = b.metalWood || {}, mwC = c.metalWood || {};
      html += triBar(t("metal"), mwA.metal, mwB.metal, mwC.metal, "var(--metal)", "wx-金", page);
      html += triBar(t("wood"), mwA.wood, mwB.wood, mwC.wood, "var(--wood)", "wx-木", page);
    }
    html += '<div class="merge-g">' + t("elZi") + "</div>";
    ["木", "火", "土", "金", "水"].forEach(function (w) {
      html += triBar(term(w), zi(a, w), zi(b, w), zi(c, w), wxCol(w), "wx-" + w, page);
    });
    html += '<div class="merge-g">' + t("woTaShort") + "</div>";
    html += triBar(t("wo"), a.wo, b.wo, c.wo, "var(--wood)", "", page);
    html += triBar(t("ta"), a.ta, b.ta, c.ta, "var(--fire)", "", page);
    html += '<div class="merge-g">' + t("neiWaiShort") + "</div>";
    html += triBar(t("nei"), a.nei, b.nei, c.nei, "var(--bronze)", "", page);
    html += triBar(t("wai"), a.wai, b.wai, c.wai, "var(--water)", "", page);
    return html + "</div>";
  }
  function dualBar(aL, aV, aC, bL, bV, bC) {
    var tot = (aV + bV) || 1, ap = Math.round(aV / tot * 100), bp = 100 - ap;
    return '<div class="bar"><span class="track" style="display:flex"><span class="fill" style="width:' + ap + '%;background:' + aC + '"></span><span class="fill" style="width:' + bp + '%;background:' + bC + '"></span></span></div>' +
      '<div class="verdict" style="font-size:12px;color:var(--mist)">' + aL + ' <b>' + aV.toFixed(2) + '</b>（' + ap + '%） ∶ ' + bL + ' <b>' + bV.toFixed(2) + '</b>（' + bp + '%）</div>';
  }
  function signedDelta(v) {
    var n = Number(v) || 0;
    return (n >= 0 ? "+" : "") + n.toFixed(2);
  }
  function vitalityNote(V) {
    if (!V) return "";
    var bits = [];
    if (V.shou && V.shou.zh) bits.push("夭寿：" + V.shou.zh);
    if (V.shen && V.shen.zh) bits.push("身体：" + V.shen.zh);
    if (V.neng && V.neng.zh) bits.push(V.neng.zh);
    return bits.length ? '<div class="verdict" style="font-size:12px;color:var(--mist)">' + bits.join(" · ") + "</div>" : "";
  }
  function godsLiBaiHTML(gods) {
    if (!gods || !gods.length) return "";
    return '<div class="verdict" style="font-size:12px;color:var(--mist);margin-top:4px">' + gods.map(function (g) {
      return g.name + " " + (g.before || 0).toFixed(2) + "→" + (g.after || 0).toFixed(2) +
        "（" + signedDelta(g.delta) + "）对日主" + g.toDay + "、对自家" + g.toJia;
    }).join("；") + "</div>";
  }

  function initHome() {
    // 全球城市下拉（按洲分组）：出生地填经纬；现住地只定当前时地
    var sel = $("city"), stand = $("standCity"), CT = window.KRCities;
    var def = (window.KRShifu && KRShifu.AUTHOR_CASE) || { birthCity: "guangzhou", standCity: "chicago" };
    if (window.KRShifu && KRShifu.applyCaseToForm) KRShifu.applyCaseToForm($);
    fillCitySelect(sel, def.birthCity || "guangzhou");
    fillCitySelect(stand, def.standCity || "chicago");
    if (sel && CT) {
      var birth = CT.byId[sel.value];
      if (birth) { $("lon").value = birth.lng; $("lat").value = birth.lat; }
      sel.onchange = function () {
        var p = CT.byId[sel.value];
        if (p) { $("lon").value = p.lng; $("lat").value = p.lat; renderWF("wfHome", homeWF()); }
      };
    }
    seg("gM", "gF", function (v) { state.gender = v ? "M" : "F"; });
    seg("cS", "cL", function (v) { state.cal = v ? "S" : "L"; });
    seg("tsOn", "tsOff", function (v) { state.ts = v; });
    if ($("lat")) $("lat").addEventListener("input", function () { renderWF("wfHome", homeWF()); });
    // 语言键
    if (window.KRLang) cur = KRLang.boot();
    Array.prototype.forEach.call($("langs").querySelectorAll("button"), function (b) {
      b.onclick = function () { cur = b.getAttribute("data-lang"); applyLang(); };
    });
    // 日/夜键：夜→日→自动 循环
    $("themeBtn").onclick = function () {
      setTheme(themeMode === "dark" ? "light" : (themeMode === "light" ? "auto" : "dark"));
    };
    bootTheme(); applyLang();
    $("go").onclick = paipan;
    $("reset").onclick = function () { $("chart").style.display = "none"; $("home").style.display = "block"; $("tabbar").style.display = "none"; };
    // 底部页签
    Array.prototype.forEach.call($("tabbar").querySelectorAll("button"), function (b) {
      b.onclick = function () { showPage(b.getAttribute("data-p"), b); };
    });
    initAdmin();
    applyUrlChart();
  }
  function paintSerial() {
    if (!window.KRSerial) return;
    KRSerial.stashSet({
      name: $("name") ? $("name").value : "",
      date: $("date") ? $("date").value : "",
      time: $("time") ? $("time").value : "",
      city: $("city") ? $("city").value : "",
      stand: $("standCity") ? $("standCity").value : "",
      gender: state.gender === "F" ? "F" : "M"
    });
    KRSerial.render({ base: "app", current: "zeisen", mountId: "serialHome" });
    KRSerial.render({ base: "app", current: "zeisen", mountId: "serialChart" });
  }
  function applyUrlChart() {
    var q, st = {};
    try { q = new URLSearchParams(location.search); } catch (e) { return; }
    var isNew = q.get("new") === "1";
    if (!isNew && window.KRSerial) st = KRSerial.stashGet() || {};
    if (isNew && !q.get("date") && !q.get("name")) {
      if ($("name")) $("name").value = "";
      if ($("date")) $("date").value = "";
      if ($("time")) $("time").value = "";
    }
    var name = q.get("name") || st.name;
    var date = q.get("date") || st.date;
    var time = q.get("time") || st.time;
    var city = q.get("city") || q.get("birth") || st.city;
    var stand = q.get("stand") || st.stand;
    var gender = q.get("gender") || q.get("sex") || st.gender;
    if (name && $("name")) $("name").value = name;
    if (date && $("date")) $("date").value = date;
    if (time && $("time")) $("time").value = time;
    var CT = window.KRCities;
    if (city && $("city") && CT && CT.byId[city]) {
      $("city").value = city;
      $("lon").value = CT.byId[city].lng;
      $("lat").value = CT.byId[city].lat;
    }
    if (stand && $("standCity") && CT && CT.byId[stand]) {
      $("standCity").value = stand;
    }
    if ((gender === "F" || gender === "female") && $("gF")) {
      $("gF").classList.add("on"); if ($("gM")) $("gM").classList.remove("on"); state.gender = "F";
    }
    paintSerial();
    var go = q.get("go") || q.get("auto");
    if (isNew && !date) return;
    if (go === "1" || go === "paipan") setTimeout(paipan, 0);
  }
  function seg(onId, offId, cb) {
    var on = $(onId), off = $(offId); if (!on) return;
    on.onclick = function () { on.classList.add("on"); off.classList.remove("on"); cb(true); };
    off.onclick = function () { off.classList.add("on"); on.classList.remove("on"); cb(false); };
  }
  function showPage(p, btn) {
    ["info", "chart", "pro", "key", "vip"].forEach(function (k) { $("p-" + k).classList.toggle("on", k === p); });
    Array.prototype.forEach.call($("tabbar").querySelectorAll("button"), function (b) { b.classList.toggle("on", b === btn); });
    window.scrollTo(0, 0);
  }

  function paipan() {
    try {
      var d = $("date").value.trim().split(/[-/]/).map(Number);
      var t = $("time").value.trim().split(":").map(Number);
      var y = d[0], mo = d[1], da = d[2], h = t[0] || 0, mi = t[1] || 0;
      var cityId = $("city").value, CT = window.KRCities;
      var lon = parseFloat($("lon").value), lat = parseFloat($("lat").value);
      var usedDefault = false, cityName = (cityId && CT && CT.byId[cityId]) ? CT.byId[cityId].name : "";
      if (isNaN(lon) || isNaN(lat)) { lon = 116.4074; lat = 39.9042; usedDefault = true; cityName = "北京"; $("lon").value = lon; $("lat").value = lat; }
      var zone = (cityId && CT && CT.byId[cityId]) ? CT.byId[cityId].zone : (usedDefault ? 8 : lon / 15);
      var sexNum = state.gender === "M" ? 1 : 0;
      var ti0 = null; try { ti0 = window.KREngine.correctTime({ y: y, m: mo, d: da, h: h, mi: mi }, lon, zone, state.ts, false); } catch (e) { }
      var Solar = window.Solar, Lunar = window.Lunar;
      var solar, lunar;
      if (state.cal === "L") { lunar = Lunar.fromYmdHms(y, mo, da, h, mi, 0); solar = lunar.getSolar(); }
      else { solar = Solar.fromYmdHms(y, mo, da, h, mi, 0); lunar = solar.getLunar(); }
      var ec = lunar.getEightChar();
      var pillars = { year: ec.getYear(), month: ec.getMonth(), day: ec.getDay(), time: ec.getTime() };
      // 大运（保留 lunar 对象，供流年/小运展开）
      var daYun = [], yunObj = null;
      try {
        yunObj = ec.getYun(sexNum, 1);
        var list = (yunObj.getDaYun ? yunObj.getDaYun(10) : []) || [];
        daYun = list.map(function (x, i) {
          var gz = x.getGanZhi ? x.getGanZhi() : "";
          var sa = x.getStartAge ? x.getStartAge() : null; if (sa == null) sa = i * 10;
          var sy = x.getStartYear ? x.getStartYear() : null;
          var ey = x.getEndYear ? x.getEndYear() : (sy ? sy + 9 : null);
          var ea = x.getEndAge ? x.getEndAge() : (sa != null ? sa + 9 : null);
          return { gz: gz, startAge: sa, startYear: sy, endYear: ey, endAge: ea, obj: x, i: i };
        });
      } catch (e) { }
      // 键引擎判局
      var A = window.KREngine.analyze({
        pillars: pillars, birthLat: lat, birthLon: lon, params: window.KRParams,
        localHour: h, localMinute: mi, timeInfo: ti0,
        dayun: daYun, sex: sexNum ? "M" : "F",
      });
      var standId = $("standCity") ? $("standCity").value : "";
      var stand = placeOf(standId || cityId, lon, lat, zone);
      if (!standId) { stand.name = stand.name || cityName || "同出生地"; }
      var nowStand = localNow(stand.id || cityId, stand.lon, stand.lat, stand.zone);
      var ctx = { name: $("name").value.trim() || "命主", sexNum: sexNum, solar: solar, lunar: lunar, ec: ec, pillars: A.pillars, rawPillars: pillars, daYun: daYun, yunObj: yunObj, A: A, lon: lon, lat: lat, y: y, mo: mo, da: da, birthH: h, birthMi: mi, ti: ti0, usedDefault: usedDefault, cityName: cityName, zone: zone, standId: stand.id, standName: stand.name, standLon: stand.lon, standLat: stand.lat, standZone: stand.zone };
      liveAnchors.birth = { city: cityId || "", name: cityName, lon: lon, lat: lat, dt: { y: y, mo: mo, d: da, h: h, mi: mi }, source: "birth" };
      seedDeviceFromKnown();
      var standSrc = stand.id ? "place" : "device";
      var live0 = { city: stand.id, lon: stand.lon, lat: stand.lat, dt: nowStand, source: standSrc };
      if (!stand.id && liveAnchors.device && liveAnchors.device.lon != null) {
        live0 = { city: liveAnchors.device.city || "", lon: liveAnchors.device.lon, lat: liveAnchors.device.lat, dt: liveAnchors.device.dt, source: "device" };
      }
      var pending = pendingStandRestore; pendingStandRestore = null;
      if (pending && pending.lon != null && pending.lat != null && !isNaN(+pending.lon) && !isNaN(+pending.lat)) {
        var pCity = pending.city || "";
        live0 = {
          city: pCity,
          lon: +pending.lon,
          lat: +pending.lat,
          dt: localNow(pCity, +pending.lon, +pending.lat, stand.zone),
          source: "place"
        };
      }
      commitLive(ctx, live0, { render: false });
      layerState = { kind: "liushi", y: live0.dt.y, mo: live0.dt.mo, d: live0.dt.d, h: live0.dt.h, mi: live0.dt.mi, pick: null };
      lastCtx = ctx; render(ctx);
      $("home").style.display = "none"; $("chart").style.display = "block"; $("tabbar").style.display = "grid";
      showPage("info", $("tabbar").querySelector('[data-p="info"]'));
      refreshDevice(ctx, { adopt: live0.source !== "place" });
    } catch (err) {
      alert("排盘出错：" + err.message);
    }
  }

  function gzCell(gz, lab) {
    return '<div class="p"><div class="lab">' + lab + '</div><div class="gz"><span class="' + wxc(gz[0]) + '">' + gz[0] + '</span><span class="' + wxc(gz[1]) + '">' + gz[1] + '</span></div></div>';
  }

  // —— 问真式洋葱盘：八字→大运→流年(小运)→流月→流日→流时；点到哪一层就同步到哪一层 ——
  var SHI_CHEN = [
    { z: "子", h: 23 }, { z: "丑", h: 1 }, { z: "寅", h: 3 }, { z: "卯", h: 5 },
    { z: "辰", h: 7 }, { z: "巳", h: 9 }, { z: "午", h: 11 }, { z: "未", h: 13 },
    { z: "申", h: 15 }, { z: "酉", h: 17 }, { z: "戌", h: 19 }, { z: "亥", h: 21 },
  ];
  function ensureLayer() {
    if (!layerState) { var n = nowDT(); layerState = { kind: "liushi", y: n.y, mo: n.mo, d: n.d, h: n.h, mi: n.mi, pick: null }; }
    return layerState;
  }
  function atSolarLunar(y, mo, d, h, mi) {
    try {
      var s = window.Solar.fromYmdHms(y, mo, d, h || 0, mi || 0, 0);
      var l = s.getLunar(), ec = l.getEightChar();
      return {
        solar: s, lunar: l,
        sYmd: s.toYmd(), sHm: z2(h || 0) + ":" + z2(mi || 0),
        lStr: (l.getYearInChinese ? (l.getYearInChinese() + "年" + l.getMonthInChinese() + "月" + l.getDayInChinese()) : l.toString()),
        gz: { year: ec.getYear(), month: ec.getMonth(), day: ec.getDay(), time: ec.getTime() },
      };
    } catch (e) { return { solar: null, lunar: null, sYmd: y + "-" + z2(mo) + "-" + z2(d), sHm: z2(h || 0) + ":" + z2(mi || 0), lStr: "—", gz: { year: "—", month: "—", day: "—", time: "—" } }; }
  }
  function daYunAt(c, year) {
    var hit = null;
    (c.daYun || []).forEach(function (x) {
      if (!x.gz || x.gz.length < 2) return;
      if (x.startYear && x.endYear && year >= x.startYear && year <= x.endYear) hit = x;
    });
    return hit;
  }
  function xiaoYunGz(c, year) {
    var dy = daYunAt(c, year);
    if (!dy || !dy.obj || !dy.obj.getXiaoYun) return "";
    try {
      var list = dy.obj.getXiaoYun(12) || [];
      for (var i = 0; i < list.length; i++) {
        if (list[i].getYear && list[i].getYear() === year) return list[i].getGanZhi() || "";
      }
    } catch (e) { }
    return "";
  }
  function applyLayer(c, patch) {
    var ls = ensureLayer();
    Object.keys(patch).forEach(function (k) { ls[k] = patch[k]; });
    var dt = { y: ls.y, mo: ls.mo, d: ls.d, h: ls.h, mi: ls.mi };
    if (!proState) proState = { city: c.standId || "", lon: c.standLon != null ? c.standLon : c.lon, lat: c.standLat != null ? c.standLat : c.lat, dt: dt };
    else proState.dt = dt;
    if (!keyFlow) keyFlow = { city: c.standId || "", lon: c.standLon != null ? c.standLon : c.lon, lat: c.standLat != null ? c.standLat : c.lat, dt: dt };
    else keyFlow.dt = dt;
    if (liveAnchors.stand) liveAnchors.stand.dt = dt;
    renderOnion(c);
    renderInfo(c); renderChart(c); renderPro(c); renderKey(c);
    markTabPays();
  }
  function layerExtras(c, dt) {
    var out = [];
    var dy = daYunAt(c, dt.y);
    if (dy && dy.gz) out.push({ gz: dy.gz, label: "大运", kind: "yun", scale: 1 });
    var xy = xiaoYunGz(c, dt.y);
    if (xy) out.push({ gz: xy, label: "小运", kind: "yun", scale: 0.125 });
    try {
      var s2 = window.Solar.fromYmdHms(dt.y, dt.mo, dt.d, dt.h, dt.mi, 0);
      var ec2 = s2.getLunar().getEightChar();
      out.push({ gz: ec2.getYear(), label: "流年", kind: "flow", scale: 1, flowIdx: 0 });
      out.push({ gz: ec2.getMonth(), label: "流月", kind: "flow", scale: 1, flowIdx: 1 });
      out.push({ gz: ec2.getDay(), label: "流日", kind: "flow", scale: 1, flowIdx: 2 });
      out.push({ gz: ec2.getTime(), label: "流时", kind: "flow", scale: 1, flowIdx: 3 });
    } catch (e) { }
    return out;
  }
  function syncDtToLayer(dt) {
    var ls = ensureLayer();
    ls.y = dt.y; ls.mo = dt.mo; ls.d = dt.d; ls.h = dt.h; ls.mi = dt.mi;
  }
  function gzStack(gz) {
    gz = gz || "—";
    return '<div class="ogz"><div class="ogan ' + wxc(gz[0]) + '">' + (gz[0] || "—") + '</div><div class="ozhi ' + wxc(gz[1]) + '">' + (gz[1] || "") + "</div></div>";
  }
  function okeyHTML(opts) {
    var gz = opts.gz || "—";
    return '<div class="okey' + (opts.on ? " on" : "") + (opts.natal ? " natal" : "") + '" data-k="' + opts.k + '"' + (opts.val != null ? ' data-v="' + opts.val + '"' : "") + ">" +
      '<div class="olab">' + opts.lab + "</div>" +
      gzStack(gz) +
      (opts.sub ? '<div class="oxy">' + opts.sub + "</div>" : "") +
      '<div class="odt">' + (opts.solar || "") + (opts.lunar ? "<br>" + opts.lunar : "") + "</div></div>";
  }
  function lifeYears(c) {
    var ys = [], a = c.daYun || [];
    var y0 = c.y, y1 = c.y + 80;
    if (a.length) {
      a.forEach(function (x) {
        if (x.startYear && x.startYear < y0) y0 = x.startYear;
        var end = x.endYear || (x.startYear ? x.startYear + 9 : y1);
        if (end > y1) y1 = end;
      });
    }
    for (var y = y0; y <= y1; y++) ys.push(y);
    return ys;
  }
  function monthDays(y, mo) { return new Date(y, mo, 0).getDate(); }
  function togglePick(c, kind, extra) {
    var ls = ensureLayer();
    var pick = ls.pick === kind ? null : kind;
    var patch = { kind: extra && extra.kind != null ? extra.kind : kind, pick: pick };
    if (extra) Object.keys(extra).forEach(function (k) { if (k !== "kind") patch[k] = extra[k]; });
    applyLayer(c, patch);
  }
  function focusOnionDefault(ls) {
    if (!ls || !ls.pick) return;
    function go() {
      var key = ls.pick.indexOf("natal-") === 0 ? ls.pick : ls.kind;
      var el = $("onion").querySelector('.orow .okey[data-k="' + key + '"]');
      if (el) el.scrollIntoView({ inline: "nearest", block: "nearest" });
      var on = $("onion").querySelector(".opick .okey.on");
      if (on) on.scrollIntoView({ inline: "center", block: "nearest" });
    }
    requestAnimationFrame(function () { requestAnimationFrame(go); });
  }
  function renderOnion(c) {
    var ls = ensureLayer(), fl = atSolarLunar(ls.y, ls.mo, ls.d, ls.h, ls.mi);
    var natal = atSolarLunar(c.y, c.mo, c.da, c.birthH, c.birthMi);
    var p = c.pillars;
    var dy = daYunAt(c, ls.y);
    var xy = xiaoYunGz(c, ls.y);
    $("whoMeta").textContent = t("sync") + " " + fl.sYmd + " " + fl.sHm + " · " + liveStandName(c) + " · " + (ls.kind === "natal" ? t("natal") : ({ dayun: t("dayun"), liunian: t("liunian"), liuyue: t("liuyue"), liuri: t("liuri"), liushi: t("liushi") }[ls.kind] || ls.kind));
    var row = okeyHTML({ k: "liushi", lab: t("liushi"), gz: fl.gz.time, solar: fl.sHm, lunar: t("lunarShort") + " " + fl.gz.time, on: ls.kind === "liushi" }) +
      okeyHTML({ k: "liuri", lab: t("liuri"), gz: fl.gz.day, solar: fl.sYmd, lunar: fl.lStr.replace(/年/, " ").slice(-8), on: ls.kind === "liuri" }) +
      okeyHTML({ k: "liuyue", lab: t("liuyue"), gz: fl.gz.month, solar: ls.y + "-" + z2(ls.mo), lunar: t("lunarShort") + " " + (fl.lunar && fl.lunar.getMonthInChinese ? fl.lunar.getMonthInChinese() : fl.gz.month), on: ls.kind === "liuyue" }) +
      okeyHTML({ k: "liunian", lab: t("liunian"), gz: fl.gz.year, sub: xy || "", solar: ls.y + "", lunar: t("lunarShort") + " " + (fl.lunar && fl.lunar.getYearInGanZhi ? fl.lunar.getYearInGanZhi() : fl.gz.year), on: ls.kind === "liunian" }) +
      okeyHTML({ k: "dayun", lab: t("dayun"), gz: (dy && dy.gz) || "—", solar: dy ? (dy.startYear + "–" + (dy.endYear || "")) : "—", lunar: dy ? (t("lunarShort") + " " + dy.gz) : "—", on: ls.kind === "dayun" }) +
      okeyHTML({ k: "natal-year", lab: t("natalYear"), gz: p.year, natal: true, solar: natal.sYmd.slice(0, 4), lunar: t("lunarShort") + " " + (c.lunar.getYearInChinese ? c.lunar.getYearInChinese() : ""), on: ls.pick === "natal-year" || (ls.kind === "natal" && !ls.pick) }) +
      okeyHTML({ k: "natal-month", lab: t("natalMonth"), gz: p.month, natal: true, solar: natal.sYmd.slice(5, 7), lunar: t("lunarShort") + " " + (c.lunar.getMonthInChinese ? c.lunar.getMonthInChinese() : ""), on: ls.pick === "natal-month" || (ls.kind === "natal" && !ls.pick) }) +
      okeyHTML({ k: "natal-day", lab: t("natalDay"), gz: p.day, natal: true, solar: natal.sYmd.slice(8, 10), lunar: t("lunarShort") + " " + (c.lunar.getDayInChinese ? c.lunar.getDayInChinese() : ""), on: ls.pick === "natal-day" || (ls.kind === "natal" && !ls.pick) }) +
      okeyHTML({ k: "natal-time", lab: t("natalTime"), gz: p.time, natal: true, solar: natal.sHm, lunar: t("lunarShort") + " " + p.time, on: ls.pick === "natal-time" || (ls.kind === "natal" && !ls.pick) });
    var pick = onionPicker(c, ls, fl);
    $("onion").innerHTML = '<div class="orow">' + row + "</div>" + (pick || "");
    wireOnion(c);
    requestAnimationFrame(function () { focusOnionDefault(ls); });
  }
  function onionPicker(c, ls, fl) {
    var pick = ls.pick; if (!pick) return "";
    var cells = "";
    if (pick === "dayun") {
      (c.daYun || []).forEach(function (x) {
        if (!x.gz || x.gz.length < 2) return;
        cells += okeyHTML({ k: "pick-dayun", val: x.startYear, lab: x.startAge + "岁", gz: x.gz, solar: x.startYear + "–" + (x.endYear || ""), lunar: "农" + x.gz, on: dyOn(x, ls.y) });
      });
    } else if (pick === "liunian") {
      var dy = daYunAt(c, ls.y);
      var years = [];
      if (dy && dy.startYear) { for (var y = dy.startYear; y <= (dy.endYear || dy.startYear + 9); y++) years.push(y); }
      else { for (var i = -4; i <= 5; i++) years.push(ls.y + i); }
      years.forEach(function (y) {
        var a = atSolarLunar(y, 6, 1, 12, 0);
        var xy = xiaoYunGz(c, y);
        cells += okeyHTML({ k: "pick-liunian", val: y, lab: y + "年", gz: a.gz.year, sub: xy ? ("小 " + xy) : "", solar: y + "", lunar: "农" + a.gz.year, on: y === ls.y });
      });
    } else if (pick === "liuyue") {
      for (var m = 1; m <= 12; m++) {
        var a = atSolarLunar(ls.y, m, 1, 12, 0);
        cells += okeyHTML({ k: "pick-liuyue", val: m, lab: m + "月", gz: a.gz.month, solar: ls.y + "-" + z2(m), lunar: "农" + (a.lunar && a.lunar.getMonthInChinese ? a.lunar.getMonthInChinese() : a.gz.month), on: m === ls.mo });
      }
    } else if (pick === "liuri") {
      var dim = new Date(ls.y, ls.mo, 0).getDate();
      for (var d = 1; d <= dim; d++) {
        var a = atSolarLunar(ls.y, ls.mo, d, ls.h, ls.mi);
        cells += okeyHTML({ k: "pick-liuri", val: d, lab: d + "日", gz: a.gz.day, solar: z2(ls.mo) + "-" + z2(d), lunar: a.lunar && a.lunar.getDayInChinese ? a.lunar.getDayInChinese() : "", on: d === ls.d });
      }
    } else if (pick === "liushi") {
      SHI_CHEN.forEach(function (sc) {
        var a = atSolarLunar(ls.y, ls.mo, ls.d, sc.h, 0);
        cells += okeyHTML({ k: "pick-liushi", val: sc.h, lab: sc.z + "时", gz: a.gz.time, solar: z2(sc.h) + ":00", lunar: "农" + a.gz.time, on: shiOn(ls.h, sc.h) });
      });
    } else if (pick === "natal-year") {
      lifeYears(c).forEach(function (y) {
        var a = atSolarLunar(y, c.mo, Math.min(c.da, 28), 12, 0);
        cells += okeyHTML({ k: "pick-natal-year", val: y, lab: y + "年", gz: a.gz.year, solar: y + "", lunar: "农" + a.gz.year, on: y === ls.y || (ls.kind === "natal" && y === c.y) });
      });
    } else if (pick === "natal-month") {
      for (var m = 1; m <= 12; m++) {
        var a = atSolarLunar(c.y, m, 1, 12, 0);
        cells += okeyHTML({ k: "pick-natal-month", val: m, lab: m + "月", gz: a.gz.month, solar: c.y + "-" + z2(m), lunar: "农" + (a.lunar && a.lunar.getMonthInChinese ? a.lunar.getMonthInChinese() : a.gz.month), on: ls.kind === "natal" ? m === c.mo : (ls.y === c.y && m === ls.mo) });
      }
    } else if (pick === "natal-day") {
      var dimN = monthDays(c.y, c.mo);
      for (var d = 1; d <= dimN; d++) {
        var a = atSolarLunar(c.y, c.mo, d, c.birthH, c.birthMi);
        cells += okeyHTML({ k: "pick-natal-day", val: d, lab: d + "日", gz: a.gz.day, solar: z2(c.mo) + "-" + z2(d), lunar: a.lunar && a.lunar.getDayInChinese ? a.lunar.getDayInChinese() : "", on: ls.kind === "natal" ? d === c.da : (ls.y === c.y && ls.mo === c.mo && d === ls.d) });
      }
    } else if (pick === "natal-time") {
      SHI_CHEN.forEach(function (sc) {
        var a = atSolarLunar(c.y, c.mo, c.da, sc.h, 0);
        cells += okeyHTML({ k: "pick-natal-time", val: sc.h, lab: sc.z + "时", gz: a.gz.time, solar: z2(sc.h) + ":00", lunar: "农" + a.gz.time, on: ls.kind === "natal" ? shiOn(c.birthH, sc.h) : (ls.y === c.y && ls.mo === c.mo && ls.d === c.da && shiOn(ls.h, sc.h)) });
      });
    }
    return cells ? '<div class="opick">' + cells + "</div>" : "";
  }
  function dyOn(x, year) { return !!(x.startYear && x.endYear && year >= x.startYear && year <= x.endYear); }
  function shiOn(h, start) { if (start === 23) return h === 23 || h === 0; return h >= start && h < start + 2; }
  function wireOnion(c) {
    Array.prototype.forEach.call($("onion").querySelectorAll(".okey"), function (el) {
      el.onclick = function () {
        var k = el.getAttribute("data-k"), v = el.getAttribute("data-v");
        if (k === "natal-year") { togglePick(c, "natal-year", { kind: "natal", y: c.y, mo: c.mo, d: c.da, h: c.birthH, mi: c.birthMi }); return; }
        if (k === "natal-month") { togglePick(c, "natal-month", { kind: "natal", y: c.y, mo: c.mo, d: c.da, h: c.birthH, mi: c.birthMi }); return; }
        if (k === "natal-day") { togglePick(c, "natal-day", { kind: "natal", y: c.y, mo: c.mo, d: c.da, h: c.birthH, mi: c.birthMi }); return; }
        if (k === "natal-time") { togglePick(c, "natal-time", { kind: "natal", y: c.y, mo: c.mo, d: c.da, h: c.birthH, mi: c.birthMi }); return; }
        if (k === "dayun") { togglePick(c, "dayun"); return; }
        if (k === "liunian") { togglePick(c, "liunian"); return; }
        if (k === "liuyue") { togglePick(c, "liuyue"); return; }
        if (k === "liuri") { togglePick(c, "liuri"); return; }
        if (k === "liushi") { togglePick(c, "liushi"); return; }
        if (k === "pick-dayun") { applyLayer(c, { kind: "dayun", y: +v, pick: "dayun" }); return; }
        if (k === "pick-liunian") { applyLayer(c, { kind: "liunian", y: +v, pick: "liunian" }); return; }
        if (k === "pick-liuyue") { applyLayer(c, { kind: "liuyue", mo: +v, d: Math.min(layerState.d, monthDays(layerState.y, +v)), pick: "liuyue" }); return; }
        if (k === "pick-liuri") { applyLayer(c, { kind: "liuri", d: +v, pick: "liuri" }); return; }
        if (k === "pick-liushi") { applyLayer(c, { kind: "liushi", h: +v, mi: 0, pick: "liushi" }); return; }
        if (k === "pick-natal-year") {
          if (+v === c.y) applyLayer(c, { kind: "natal", y: c.y, mo: c.mo, d: c.da, h: c.birthH, mi: c.birthMi, pick: "natal-year" });
          else applyLayer(c, { kind: "liunian", y: +v, pick: "natal-year" });
          return;
        }
        if (k === "pick-natal-month") {
          if (+v === c.mo) applyLayer(c, { kind: "natal", y: c.y, mo: c.mo, d: c.da, h: c.birthH, mi: c.birthMi, pick: "natal-month" });
          else applyLayer(c, { kind: "liuyue", y: c.y, mo: +v, d: Math.min(layerState.d, monthDays(c.y, +v)), pick: "natal-month" });
          return;
        }
        if (k === "pick-natal-day") {
          if (+v === c.da) applyLayer(c, { kind: "natal", y: c.y, mo: c.mo, d: c.da, h: c.birthH, mi: c.birthMi, pick: "natal-day" });
          else applyLayer(c, { kind: "liuri", y: c.y, mo: c.mo, d: +v, h: c.birthH, mi: c.birthMi, pick: "natal-day" });
          return;
        }
        if (k === "pick-natal-time") {
          if (shiOn(c.birthH, +v)) applyLayer(c, { kind: "natal", y: c.y, mo: c.mo, d: c.da, h: c.birthH, mi: c.birthMi, pick: "natal-time" });
          else applyLayer(c, { kind: "liushi", y: c.y, mo: c.mo, d: c.da, h: +v, mi: 0, pick: "natal-time" });
          return;
        }
      };
    });
  }

  function render(c) {
    $("whoName").innerHTML = "<b>" + c.name + "</b> · " + (c.sexNum ? t("sexMale") : t("sexFemale"));
    renderOnion(c);
    renderInfo(c); renderChart(c); renderPro(c); renderKey(c); renderVip(c);
    markTabPays();
  }

  // ① 基本信息（同问真：干支八字 + 时间地点 + 水火含量）
  function renderInfo(c) {
    var ec = c.ec, lunar = c.lunar, p = c.pillars, g = c.A.geo || {}, dm = p.day[0];
    var s = "";
    // 干支八字（主星·天干·地支）
    var cols = [[t("yearP"), p.year], [t("monthP"), p.month], [t("dayP"), p.day], [t("timeP"), p.time]];
    var bazi = '<div class="fourbar" style="margin:2px 0 4px">' + cols.map(function (col) {
      var ssg = col[1][0] === dm ? t("dayMaster") : ss(dm, col[1][0]);
      return '<div class="p"><div class="lab">' + col[0] + " · " + ssg + "</div>" + gzStack(col[1]) + "</div>";
    }).join("") + "</div>";
    s += sec(t("secBazi"), bazi + '<div class="verdict" style="font-size:12.5px">' + t("fourPillars") + "：<b>" + pillarStr(p) + "</b> · " + t("dayMaster") + ' <b class="' + wxc(dm) + '">' + dm + "</b>（" + term(STEM_WX[dm] || "") + "）</div>");
    s += sec(t("secWho"),
      kv(t("name"), c.name) + kv(t("gender"), c.sexNum ? t("sexMale") : t("sexFemale")) +
      kv(t("zodiac"), safe(function () { return lunar.getYearShengXiao(); })) +
      kv(t("sign"), safe(function () { return c.solar.getXingZuo(); })) +
      kv(t("mingGong"), safe(function () { return ec.getMingGong(); })) +
      kv(t("taiYuan"), safe(function () { return ec.getTaiYuan(); })));
    var z0 = c.A.zhengDiHou || {};
    var eotMin = (c.ti && c.ti.eotSec != null) ? (c.ti.eotSec / 60) : null;
    var lonMin = (c.ti && c.ti.lonMin != null) ? c.ti.lonMin : null;
    var deltaMin = (eotMin != null || lonMin != null) ? ((lonMin || 0) + (eotMin || 0)) : null;
    s += sec(t("secWhen"),
      kv(t("solar"), c.solar.toYmd() + " " + $("time").value) +
      kv(t("lunar"), safe(function () { return lunar.toString(); })) +
      kv(t("trueSolar"), c.ti && c.ti.trueSolar && $("tsOn").classList.contains("on") ? fmtDT(c.ti.trueSolar) : t("trueSolarOff")) +
      kv(t("eotLabel"), deltaMin == null ? "—" : t("eotFmt", { delta: (deltaMin >= 0 ? "+" : "") + deltaMin.toFixed(1), lon: lonMin == null ? "—" : lonMin.toFixed(1), eot: eotMin == null ? "—" : eotMin.toFixed(1) })) +
      kv(t("birthPlace"), (c.cityName || t("customPlace")) + " · " + t("lon") + " " + c.lon.toFixed(2) + "° " + t("lat") + " " + c.lat.toFixed(2) + "°" +
        (c.usedDefault ? ' <span style="color:var(--gold)">' + t("defaultBeijing") + "</span>" : "") +
        (g.americas ? " · " + t("americas") : "") + (c.lat < 0 ? " · " + t("south") : "")) +
      kv(t("standPlace"), (liveStandName(c) || t("sameBirth")) + " · " + t("lon") + " " + liveStandLon(c).toFixed(2) + "° " + t("lat") + " " + liveStandLat(c).toFixed(2) + "°") +
      threeStepHTML() +
      kv(t("sync"), (function () {
        var ls = ensureLayer();
        var fl = atSolarLunar(ls.y, ls.mo, ls.d, ls.h, ls.mi);
        return ls.y + "-" + z2(ls.mo) + "-" + z2(ls.d) + " " + z2(ls.h) + ":" + z2(ls.mi) +
          " · " + liveStandName(c) + " " + t("wallClock") + " · " + t("flowHour") + " " + (fl.gz.year + " " + fl.gz.month + " " + fl.gz.day + " " + fl.gz.time);
      })()));
    s += sec(t("secStrength"),
      kv(t("zhengcong"), term(z0.pattern || "—") + " · " + term(z0.strength || "—") + " · " + t("wo") + " " + (z0.wo != null ? z0.wo.toFixed(2) : "—") + " ∶ " + t("ta") + " " + (z0.ta != null ? z0.ta.toFixed(2) : "—")) +
      kv(t("rowKong"), safe(function () { return t("dayXun") + " " + ec.getDayXunKong() + " · " + t("timeXun") + " " + ec.getTimeXunKong(); })) +
      kv(t("dayYi"), safe(function () { return (lunar.getDayYi && lunar.getDayYi().join("、")) || "—"; })) +
      kv(t("dayJi"), safe(function () { return (lunar.getDayJi && lunar.getDayJi().join("、")) || "—"; })));
    var fw = (c.A.geo && c.A.geo.fireWaterBirth) || { water: 0, fire: 0 };
    s += sec(t("waterFire") + " " + t("wfBirthLat"), '<div id="wfInfo" class="wfbars"></div><div class="verdict" style="color:var(--mist);font-size:12px">' + t("wfNote") + "</div>");
    $("p-info").innerHTML = s;
    renderWF("wfInfo", { water: fw.water, fire: fw.fire });
  }
  function fmtDT(d) {
    function z(n) { return (n < 10 ? "0" : "") + n; }
    try { return d.getFullYear() + "-" + z(d.getMonth() + 1) + "-" + z(d.getDate()) + " " + z(d.getHours()) + ":" + z(d.getMinutes()); } catch (e) { return "—"; }
  }

  // ② 基础排盘（问真式全盘）
  function renderChart(c) {
    var ec = c.ec, p = c.pillars, dm = p.day[0];
    var cols = [["年", p.year, "Year"], ["月", p.month, "Month"], ["日", p.day, "Day"], ["时", p.time, "Time"]];
    function ssGan(gz, getter) { return safe(function () { return ec[getter](); }) || "—"; }
    var mainStar = [ss(dm, p.year[0]), ss(dm, p.month[0]), t("dayMaster"), ss(dm, p.time[0])];
    function hideCell(zhi) {
      var hs = (window.LunarUtil && window.LunarUtil.ZHI_HIDE_GAN && window.LunarUtil.ZHI_HIDE_GAN[zhi]) || [];
      return hs.map(function (x) { return '<span class="' + wxc(x) + '">' + x + '</span><small>' + ss(dm, x) + '</small>'; }).join("<br>");
    }
    var rows = "";
    rows += trh(["", t("yearP"), t("monthP"), t("dayP"), t("timeP")]);
    rows += tr(t("rowStar"), mainStar.map(function (x) { return '<span>' + x + '</span>'; }));
    rows += tr(t("rowGan"), cols.map(function (col) { return '<span class="big ' + wxc(col[1][0]) + '">' + col[1][0] + '</span>'; }));
    rows += tr(t("rowZhi"), cols.map(function (col) { return '<span class="big ' + wxc(col[1][1]) + '">' + col[1][1] + '</span>'; }));
    rows += tr(t("rowHide"), cols.map(function (col) { return '<div class="hide">' + hideCell(col[1][1]) + '</div>'; }));
    rows += tr(t("rowYun"), cols.map(function (col) { return changSheng(dm, col[1][1]); }));
    rows += tr(t("rowSit"), cols.map(function (col) { return changSheng(col[1][0], col[1][1]); }));
    rows += tr(t("rowKong"), cols.map(function (col, i) { return safe(function () { return [ec.getYearXunKong(), ec.getMonthXunKong(), ec.getDayXunKong(), ec.getTimeXunKong()][i]; }) || "—"; }));
    rows += tr(t("rowNayin"), cols.map(function (col, i) { return safe(function () { return [ec.getYearNaYin(), ec.getMonthNaYin(), ec.getDayNaYin(), ec.getTimeNaYin()][i]; }) || "—"; }));
    var jiShen = safe(function () { var a = c.lunar.getDayJiShen && c.lunar.getDayJiShen(); return Array.isArray(a) ? a.slice(0, 6).join(" ") : (a || "—"); });
    var xiong = safe(function () { var a = c.lunar.getDayXiongSha && c.lunar.getDayXiongSha(); return Array.isArray(a) ? a.slice(0, 4).join(" ") : (a || "—"); });
    var s = sec(t("secPan"), '<table class="pan">' + rows + '</table>' +
      '<div class="verdict" style="font-size:12px"><b>' + t("shensha") + "</b> " + t("lucky") + "：" + jiShen + " · " + t("unlucky") + "：" + xiong + "</div>" +
      '<div class="verdict" style="font-size:11.5px;color:var(--mist)">' + t("panNote") + "</div>");

    // 大运
    var lsY = ensureLayer().y, age = lsY - c.y;
    var dyHit = daYunAt(c, lsY);
    var dy = c.daYun.filter(function (x) { return x.gz && x.gz.length >= 2; }).map(function (x) {
      var cur = dyHit ? x === dyHit : (age >= x.startAge && age < x.startAge + 10);
      return '<div class="dycell' + (cur ? " cur" : "") + '"><div class="age">' + x.startAge + t("age") + (x.startYear ? "·" + x.startYear : "") + '</div><div class="gz"><span class="' + wxc(x.gz[0]) + '">' + x.gz[0] + '</span><span class="' + wxc(x.gz[1]) + '">' + x.gz[1] + '</span></div></div>';
    }).join("");
    s += sec(t("secDayun"), '<div class="dywrap"><div class="dyrow">' + (dy || "—") + "</div></div><div class=\"verdict\">" + t("dayunNote", { age: age }) + "</div>");

    // 基础初断（令地后·第一次冻结）
    // 参数条图：①作用前 ②运前 ③流层后
    var z0 = c.A.zhengDiHou, el = c.A.energy.diHou;
    var liveA = null;
    try { liveA = computeProA(c, proState || { lon: c.standLon || c.lon, lat: c.standLat || c.lat, dt: { y: lsY, mo: ensureLayer().mo, d: ensureLayer().d, h: ensureLayer().h, mi: ensureLayer().mi } }); } catch (e) { }
    var stA = (liveA && liveA.energy && liveA.energy.stages) ? liveA : c.A;
    s += sec(t("secIndex"), threeStageHTML(stagesOf(stA), { metalWood: false, page: "base" }) +
      '<div class="verdict" style="font-size:11.5px;color:var(--mist)">' + t("woTaHint") + " " + t("neiWaiHint") + "</div>", "tier-base");
    var lingEl = (c.A.energy && (c.A.energy.lingDi || c.A.energy.diHou)) || el;
    var lingZh = lingEl
      ? "木" + (lingEl.木 || 0).toFixed(2) + " 火" + (lingEl.火 || 0).toFixed(2) + " 土" + (lingEl.土 || 0).toFixed(2) + " 金" + (lingEl.金 || 0).toFixed(2) + " 水" + (lingEl.水 || 0).toFixed(2)
      : "";
    s += sec(t("secYong"),
      kv(t("dingTi"), term(z0.pattern) + " · " + term(z0.strength)) + kv(t("bodyGuest"), t("wo") + " " + z0.wo.toFixed(2) + " ∶ " + t("ta") + " " + z0.ta.toFixed(2)) +
      (lingZh ? kv(t("lingDiEl"), lingZh) : "") +
      '<div class="verdict" style="font-size:12px;color:var(--mist)">' + t("lingTwoHint") + "</div>" +
      kv(t("yong1"), (window.KRLang ? KRLang.termsJoin(z0.yongShen.slice(0, 4), "、") : z0.yongShen.slice(0, 4).join("、"))) +
      kv(t("ji1"), (window.KRLang ? KRLang.termsJoin(z0.jiShen.slice(0, 4), "、") : z0.jiShen.slice(0, 4).join("、"))) +
      '<div class="verdict" style="font-size:12px;color:var(--mist)">' + t("yong1Hint") + "</div>");
    s += sec(t("secChu"),
      '<div class="verdict">' + term(z0.pattern) + " · " + term(z0.strength) + (z0.yongJiSoft ? " · " + term("用忌轻") : "") + " · " + t("wo") + z0.wo.toFixed(2) + "∶" + t("ta") + z0.ta.toFixed(2) + " · " + term(z0.orient) + "</div>" +
      '<div class="verdict" style="color:var(--mist);font-size:12px">' + (z0.notes || []).slice(0, 2).map(function (n) { return n; }).join("；") + "</div>");
    var ls = ensureLayer();
    var xy = xiaoYunGz(c, ls.y);
    var fl = atSolarLunar(ls.y, ls.mo, ls.d, ls.h, ls.mi);
    s += sec(t("secLiu"),
      kv(t("liunian"), fl.gz.year + " · " + ls.y) + kv(t("xiaoyun"), xy || "—") +
      kv(t("liuYmd"), fl.gz.month + " / " + fl.gz.day + " / " + fl.gz.time) +
      '<div class="verdict" style="font-size:12px;color:var(--mist)">' + t("onionFollow") + "</div>");
    s += sec(t("secTeachBase"), '<div class="verdict">' + t("teachBaseBlurb") + '</div><a href="./teach.html?unlock=master" style="display:inline-block;background:var(--jade);color:#0c120e;padding:8px 14px;border-radius:10px;margin-top:6px;font-weight:700">' + t("openTeach") + "</a>");
    $("p-chart").innerHTML = tierBanner("base") + s;
  }

  // ③ 专业细断（排盘同问真，精确同步到流时；条图①作用前②运前③流层后）
  function computeProA(c, ps) {
    var flowPillars = null;
    try {
      var s2 = window.Solar.fromYmdHms(ps.dt.y, ps.dt.mo, ps.dt.d, ps.dt.h, ps.dt.mi, 0);
      var ec2 = s2.getLunar().getEightChar();
      flowPillars = { year: ec2.getYear(), month: ec2.getMonth(), day: ec2.getDay(), time: ec2.getTime() };
    } catch (e) { }
    return window.KREngine.analyze({
      pillars: c.rawPillars, birthLat: c.lat, birthLon: c.lon, params: window.KRParams,
      localHour: c.birthH, localMinute: c.birthMi,
      standLat: ps.lat, standLon: ps.lon, flowPillars: flowPillars, flowHour: ps.dt.h, flowMinute: ps.dt.mi,
      extras: layerExtras(c, ps.dt),
      dayun: c.daYun, sex: c.sexNum ? "M" : "F",
    });
  }
  function lbl(x) { return '<div class="verdict" style="font-size:12px;color:var(--mist);margin-top:6px">' + x + '</div>'; }
  function renderPro(c) {
    if (!proState) proState = { city: "", lon: c.lon, lat: c.lat, dt: nowDT() };
    var ps = proState, dt = ps.dt, dm = c.pillars.day[0];
    var A2 = computeProA(c, ps);
    var g = A2.geo;
    // 控件：现时 + 现地 + 定位键
    var cityOpts = cityOptsHTML(ps.city);
    var ctrl = sec("现地 · 流时（精确同步）",
      threeStepHTML() +
      '<div class="row"><label>现时</label><span style="display:flex;gap:6px"><input id="proDate" value="' + dt.y + '-' + z2(dt.mo) + '-' + z2(dt.d) + '" style="flex:1"><input id="proTime" value="' + z2(dt.h) + ':' + z2(dt.mi) + '" style="width:74px"></span></div>' +
      '<div class="row"><label>现地</label><select id="proCity">' + cityOpts + '</select></div>' +
      '<div class="row"><label>经纬</label><span style="display:flex;gap:6px"><input id="proLon" value="' + ps.lon + '" style="flex:1"><input id="proLat" value="' + ps.lat + '" style="flex:1"></span></div>' +
      '<div style="display:flex;gap:8px"><button id="proApply" style="flex:1;background:var(--jade);color:#0c120e;border:0;border-radius:9px;padding:8px;font-weight:700">' + t("syncNow") + '</button>' +
      '<button id="proLocate" class="btn-ghost">' + t("locateNow") + '</button></div>' +
      '<div class="verdict" style="font-size:11.5px;color:var(--mist);margin-top:4px">' + t("locateHint") + '</div>' +
      '<div class="verdict" style="font-size:12px;color:var(--mist);margin-top:6px">流时四柱：<b>' + pillarStr(A2.layers.stand.pillars || { year: "", month: "", day: "", time: "" }) + '</b>' + (g.metalWoodStand ? "" : "") + '</div>');

    var led = A2.xinGong && A2.xinGong.ledger;
    var V = (A2.vitality) || (led && led.vitality) || {};
    var st = stagesOf(A2);
    var s0 = st.beforeWork || {}, s1 = st.beforeYun || {}, s2 = st.afterFlow || {};
    var stageG = sec(t("secIndex"),
      threeStageHTML(st, { metalWood: true, page: "pro" }) +
      vitalityNote(V) +
      godsLiBaiHTML(V.gods) +
      '<div class="verdict" style="font-size:11.5px;color:var(--mist)">' +
        t("woTaHint") + " " + t("neiWaiHint") +
        (hasPro() ? " · ①基础→②专业 家内外 " + signedDelta((s1.nei || 0) - (s0.nei || 0)) + "/" + signedDelta((s1.wai || 0) - (s0.wai || 0)) : "") +
        (hasResonance() ? " · ②专业→③键振 家外 " + signedDelta((s2.wai || 0) - (s1.wai || 0)) : "") +
      "</div>", "tier-pro");

    var z = A2.zhengZhuan, di = A2.zhengDiHou || z, bi = A2.bi;
    var gongDeLi = false;
    // 免费预览：定体身份；做功只改用忌
    var teaser = sec(t("teaserPro"),
      '<div class="verdict">' + t("teaserProLine") + "<b>" + t("dingTi") + " " + di.pattern + "·" + di.strength + "</b> · " +
        t("gongNeng") + " " + (gongDeLi ? t("gongNengDeLi") : (z.orient || "")) + " · " + t("yong2") + " " + z.orient + "</div>" +
      '<div class="verdict" style="font-size:12px;color:var(--mist)">' + t("yong2Hint") + "</div>" +
      (hasPro() ? "" : '<div class="verdict" style="color:var(--gold);font-size:12px">' + t("lockPro") + "</div>"), "tier-pro");
    // 字能 HTML：五行·十神·我客·用忌·内外 + 运年消长（地理不进字条）
    var ce = charEnergyLive(c, layerExtras(c, ps.dt));
    if (window.KRXinGong && KRXinGong.tagCharRows) {
      ce = KRXinGong.tagCharRows(ce, dm, { useGuest: !!z.useGuest, balanced: !!z.balanced, orient: z.orient });
    }
    var el0 = (A2.energy && (A2.energy.lingDi || A2.energy.diHou)) || {};
    var el1 = (A2.energy && (A2.energy.shengKeZi || A2.energy.shengKe)) || el0;
    var natalKeys = { year: 1, month: 1, day: 1, time: 1 };
    function gongE(e, wx, natal) {
      if (!natal) return { e0: e, e1: e, d: 0, yun: true };
      var b = el0[wx] || 0, a = el1[wx] || 0;
      var e1 = b > 1e-12 ? e * (a / b) : e;
      return { e0: e, e1: e1, d: e1 - e, yun: false };
    }
    ce.forEach(function (p) {
      var natal = !!natalKeys[p.key];
      var g = gongE(p.gan.e, p.gan.wx || STEM_WX[p.gan.ch], natal);
      p.gan.e0 = g.e0; p.gan.e1 = g.e1; p.gan.d = g.d; p.gan.yun = g.yun;
      (p.hides || []).forEach(function (h) {
        var hg = gongE(h.e, h.wx || STEM_WX[h.ch], natal);
        h.e0 = hg.e0; h.e1 = hg.e1; h.d = hg.d; h.yun = hg.yun;
      });
    });
    var cemax = 0;
    ce.forEach(function (p) {
      cemax = Math.max(cemax, p.gan.e1 != null ? p.gan.e1 : p.gan.e);
      (p.hides || []).forEach(function (h) { cemax = Math.max(cemax, h.e1 != null ? h.e1 : h.e); });
    });
    cemax = cemax || 1;
    function ebarTagged(u) {
      var eShow = u.e1 != null ? u.e1 : u.e;
      var col = wxCol(u.wx || STEM_WX[u.ch] || "土");
      var meta = [u.wx, u.ss, u.camp, u.yongJi, u.domain].filter(Boolean).join("·");
      var num = u.yun
        ? eShow.toFixed(2) + "·运年层"
        : (u.e0 != null ? u.e0.toFixed(2) + "→" + u.e1.toFixed(2) + "（" + signedDelta(u.d) + "）" : eShow.toFixed(2));
      return '<div class="bar"><span class="n ' + wxc(u.ch) + '">' + u.ch + '</span><span class="track"><span class="fill" style="width:' + Math.round(eShow / cemax * 100) + '%;background:' + col + '"></span></span><span style="min-width:132px;text-align:right;color:var(--mist);font-size:11px">' + num + '</span></div>' +
        (meta ? '<div class="verdict" style="font-size:11px;color:var(--mist);margin:-2px 0 4px 22px">' + meta + "</div>" : "");
    }
    var ceHTML = '<div class="verdict" style="font-size:12px;color:var(--mist)">' + t("ziNengHint") + "</div>" +
      ce.map(function (p) {
        var seat = p.domain ? (" · " + (p.domain === "内" ? "家内" : "家外")) : "";
        return '<div style="margin:5px 0"><div class="verdict" style="font-size:12px;color:var(--bronze)">' + p.lab + (natalKeys[p.key] ? "柱 " : " ") + p.gan.ch + p.zhi + seat + "</div>" +
          ebarTagged(p.gan) + (p.hides || []).map(function (h) { return ebarTagged(h); }).join("") + "</div>";
      }).join("");
    // 续收费内容（出生地→大运前 · 生克后运前）· 专业板
    var author = hasPro();
    function subH(x) { return '<div class="verdict" style="color:var(--bronze);font-weight:700;margin-top:12px;border-top:1px solid var(--line);padding-top:8px">' + x + '</div>'; }
    var geju = subH(t("subGeju")) +
      '<div class="verdict">' + t("dingTi") + " " + di.pattern + "·" + di.strength + "（" + t("dingTiId") + "） · " +
        t("gongNeng") + " " + (gongDeLi ? t("gongNengDeLi") : (z.strength || "—")) + " · 做功：" + ((A2.works || []).map(function (w) { return w.name; }).join("、") || "扶抑常规") + '</div>' +
      '<div class="verdict" style="color:var(--mist);font-size:12px">' + t("gongNengHint") + " " + (A2.works || []).map(function (w) { return w.name + "→" + w.jiXiong; }).join("；") + '</div>';
    var yongji2 = subH(t("subYong2")) +
      kv(t("dingTi"), di.pattern + "·" + di.strength) +
      kv(t("gongNeng"), gongDeLi ? t("gongNengDeLi") : (z.strength || "—")) +
      kv(t("yong2"), z.yongShen.slice(0, 4).join("、")) +
      kv(t("ji2"), z.jiShen.slice(0, 4).join("、")) + kv(t("orient2"), z.orient) + kv("BI", bi.bi.toFixed(2) + "（" + bi.label + "）") +
      '<div class="verdict" style="font-size:12px;color:var(--mist)">' + t("yong2Hint") + "</div>";
    var xiduan = subH(t("subXiduan")) + '<div class="verdict">' + ((A2.verdicts.lines || []).join("<br>") || "—") + '</div>' +
      '<div class="verdict" style="font-size:12px;color:var(--mist)">' + t("scaleHint") + " " + t("neiSanHint") + " " + t("geoRemedyHint") + "</div>" +
      '<div class="verdict" style="font-size:12px;color:var(--mist)">' + t("hecanHint") + ' <a href="./hecan.html?unlock=master">合参 →</a></div>' +
      '<div class="verdict" style="font-size:12px;color:var(--mist)">' + t("tianYiHint") + "</div>";
    var ziNeng = subH(t("ziNengTitle")) + ceHTML;
    var tj = A2.xinGong && A2.xinGong.taiji;
    var taijiHTML = "";
    if (tj && tj.ok) {
      taijiHTML = '<div class="verdict" style="font-size:12px;color:var(--mist)">' + t("taijiHint") + "</div>" +
        (tj.rows || []).map(function (r) {
          var o = r.origin;
          var head = r.name + " · " + (r.starZh || (o ? r.ss + o.ch + "（" + o.seat + "·" + o.source + "）" : r.ss));
          return '<div style="margin:8px 0;padding:8px 10px;border-left:3px solid var(--bronze)">' +
            '<div class="verdict" style="font-weight:700;color:var(--bronze)">' + head + (r.palaceZh ? " · " + r.palaceZh : "") + "</div>" +
            (r.jingZh ? '<div class="verdict" style="font-size:12.5px">环境：' + r.jingZh + "</div>" : "") +
            (r.xiaoZh ? '<div class="verdict" style="font-size:12.5px">消长：' + r.xiaoZh + "</div>" : "") +
            (r.dayAs ? '<div class="verdict" style="font-size:11.5px;color:var(--mist)">原日主在其环境为' + r.dayAs + (r.note ? " · " + r.note : "") + "。不改写本盘正从。</div>" : "") +
            "</div>";
        }).join("");
    }
    var taijiBlk = subH(t("taijiTitle")) + (taijiHTML || '<div class="verdict">未见六亲可转。</div>');
    var jiaoc = subH(t("subTeachBP")) +
      '<div style="display:flex;gap:8px;margin-top:6px"><a href="./teach.html?unlock=master" style="flex:1;text-align:center;background:var(--jade);color:#0c120e;padding:8px;border-radius:9px;font-weight:700">' + t("teachBaseBtn") + "</a>" +
      '<a href="./teach.html?unlock=master#pro" style="flex:1;text-align:center;background:var(--bronze);color:#0c120e;padding:8px;border-radius:9px;font-weight:700">' + t("teachProBtn") + "</a></div>";
    var paidInner = geju + yongji2 + xiduan + ziNeng + taijiBlk + jiaoc;
    var paidG = sec(t("paidProTitle"),
      author ? paidInner
        : '<div class="lock">' + paidInner + '</div><div class="verdict" style="color:var(--gold)">' + t("paidProLock") + "</div>", "tier-pro");

    $("p-pro").innerHTML = tierBanner("pro") + ctrl + stageG + teaser + paidG + slot("专业细致盘——格局命名/取用成句/寒暖调候等成句口径请说明；付费字能随流时的生克联动规则也可给我。");

    // wire
    $("proApply").onclick = function () {
      var d = $("proDate").value.trim().split(/[-/]/).map(Number), tt = $("proTime").value.trim().split(":").map(Number);
      commitLive(c, {
        city: $("proCity").value, lon: parseFloat($("proLon").value), lat: parseFloat($("proLat").value),
        dt: { y: d[0], mo: d[1], d: d[2], h: tt[0] || 0, mi: tt[1] || 0 }, source: "typed"
      });
    };
    $("proCity").onchange = function () { applyCityLocalNow(c, $("proCity").value); };
    $("proLon").onchange = $("proLat").onchange = function () { applyCoordsLocalNow(c, $("proLon").value, $("proLat").value); };
    $("proLocate").onclick = function () { applyShiDiShiKe(c, $("proLocate")); };
  }

  // ④ 键振详断（二级收费：键共振随「时间→流时」变化的能量+烈度条图·综合表·详断·全部教程）
  function keyState() { if (!keyFlow) keyFlow = { city: "", lon: lastCtx.lon, lat: lastCtx.lat, dt: nowDT() }; return keyFlow; }
  function curDaYun(c, ps) { return daYunAt(c, ps.dt.y); }
  function dynExtra(c, ps) { return layerExtras(c, ps.dt); }
  function isDynKey(k) { return /大运|小运|流年|流月|流日|流时|无桥/.test((k.layerZh || "") + (k.note || "")); }
  function renderKey(c) {
    var ps = keyState(), A = c.A, dt = ps.dt;
    var natalEl = A.energy.diHou, KR = window.KRRelations;
    var layers = dynExtra(c, ps);
    var A2 = computeProA(c, ps);
    var pipe = (KR && KR.pipeline) ? KR.pipeline(c.rawPillars, natalEl, window.KRParams, layers) : null;
    var natalKeys = (A.natalKeys || A.keys || []).map(function (k) { k._layer = "原局"; return k; });
    var liveKeys = (pipe && pipe.keys) ? pipe.keys : (A2.keys || []);
    var dynKeyList = liveKeys.filter(isDynKey).map(function (k) { k._layer = (k.layerZh || "").split("↔")[0] || "流"; return k; });
    var keys = liveKeys.slice().sort(function (a, b) { return b.intensity - a.intensity; });
    var dynKeys = dynKeyList.slice().sort(function (a, b) { return b.intensity - a.intensity; });
    var maxI = keys.reduce(function (m, k) { return Math.max(m, k.intensity); }, 0);
    var manifestN = keys.filter(function (k) { return k.manifest; }).length;
    var scaleMax = Math.max(2.5, maxI);
    var curDy = curDaYun(c, ps);

    // 控件：流时同步（时间→流时）
    var cityOpts = cityOptsHTML(ps.city);
    var flowLbl = layers.filter(function (e) { return e.label !== "大运"; }).map(function (e) { return e.label + e.gz; }).join(" · ");
    var ctrl = sec("键共振 · 流时同步（时间 → 流时）",
      threeStepHTML() +
      '<div class="row"><label>现时</label><span style="display:flex;gap:6px"><input id="keyDate" value="' + dt.y + '-' + z2(dt.mo) + '-' + z2(dt.d) + '" style="flex:1"><input id="keyTime" value="' + z2(dt.h) + ':' + z2(dt.mi) + '" style="width:74px"></span></div>' +
      '<div class="row"><label>现地</label><select id="keyCity">' + cityOpts + '</select></div>' +
      '<div class="row"><label>经纬</label><span style="display:flex;gap:6px"><input id="keyLon" value="' + ps.lon + '" style="flex:1"><input id="keyLat" value="' + ps.lat + '" style="flex:1"></span></div>' +
      '<div style="display:flex;gap:8px"><button id="keyApply" style="flex:1;background:var(--jade);color:#0c120e;border:0;border-radius:9px;padding:8px;font-weight:700">' + t("syncNow") + '</button>' +
      '<button id="keyLocate" class="btn-ghost">' + t("locateNow") + '</button></div>' +
      '<div class="verdict" style="font-size:11.5px;color:var(--mist);margin-top:4px">' + t("locateHint") + '</div>' +
      '<div class="verdict" style="font-size:12px;color:var(--mist);margin-top:6px">柱序 ' + ((pipe && pipe.orderZh) || "年→时→运→流时") + ' · ' + (curDy ? "大运<b>" + curDy.gz + "</b> · " : "") + flowLbl + (xiaoYunGz(c, dt.y) ? " · 小运<b>" + xiaoYunGz(c, dt.y) + "</b>×1/8" : "") + '</div>' +
      '<div class="verdict" style="font-size:12px;color:var(--gold)">O5：流层对原局分隔不生克，只经纠缠／单字共振入局；流年对运 a=0 可生克。单字共振不流动。烈度≠五行能量。</div>');

    // 预览（免费）
    var xgPeek = (A2 && A2.xinGong) ? A2.xinGong : ((window.KRXinGong && lastCtx) ? window.KRXinGong.analyze({ pillars: c.rawPillars, rel: pipe || A, dayun: c.daYun }) : null);
    var teaser = sec(t("teaserKey"),
      '<div class="verdict">原局键 <b>' + natalKeys.length + '</b> · 现时动态触发 <b>' + dynKeys.length + '</b> 键 · 峰烈度 <b>' + maxI.toFixed(2) + '</b>（事显≥2.0）· 显键 <b>' + manifestN + '</b>。</div>' +
      (xgPeek && xgPeek.systems && (xgPeek.systems.A || xgPeek.systems.B || xgPeek.systems.C)
        ? '<div class="verdict" style="font-size:12.5px">双系统：' +
          (xgPeek.systems.A ? "A " + xgPeek.systems.A.chain + " · " : "") +
          (xgPeek.systems.B ? "B " + xgPeek.systems.B.chain + " · " : "") +
          (xgPeek.systems.C ? "C " + xgPeek.systems.C.chain : "") + "</div>"
        : "") +
      (hasResonance() ? "" : '<div class="verdict" style="color:var(--gold);font-size:12px">' + t("lockKey") + "</div>"), "tier-key");

    // 付费主体 · 键共振版
    var author = hasResonance();
    function subH(x) { return '<div class="verdict" style="color:var(--bronze);font-weight:700;margin-top:12px;border-top:1px solid var(--line);padding-top:8px">' + x + '</div>'; }
    var energyBlk = subH(t("energy1")) +
      threeStageHTML(stagesOf(A2), { metalWood: true, page: "key" });
    // ② 烈度条图
    function keyBar(k) {
      var pct = Math.round(k.intensity / scaleMax * 100);
      var col = k.manifest ? "var(--gold)" : (isDynKey(k) ? "var(--water)" : "var(--mist)");
      return '<div class="bar"><span class="n" style="width:112px;text-align:left;font-size:12px">' + k.cls + " " + (k.chars || []).join("") + (k.hua ? "→" + k.hua : "") + '</span><span class="track"><span class="fill" style="width:' + pct + '%;background:' + col + '"></span></span><span style="width:40px;text-align:right;color:var(--mist)">' + k.intensity.toFixed(2) + '</span>' + (isDynKey(k) ? '<span class="badge b-gold">流</span>' : "") + (k.manifest ? '<span class="badge b-gold">显</span>' : "") + '</div>';
    }
    var intenBlk = subH(t("inten2")) +
      (keys.length ? keys.slice(0, 16).map(keyBar).join("") : '<div class="verdict">未检出键</div>');
    // ③ 综合表格（原局 + 动态柱，按烈度排序，显前 28）
    var shown = keys.slice(0, 28);
    var rows = shown.length ? shown.map(function (k) {
      var layer = k._layer && k._layer !== "原局" ? '<span class="badge b-gold">' + k._layer + '</span>' : '原局';
      return '<tr><td>' + k.cls + (k.doubleKey ? "·双" : "") + '</td><td>' + (k.chars || []).join("") + (k.hua ? "→" + k.hua : "") + '</td><td class="rowlab">' + layer + " " + (k.layerZh || k.note || "") + '</td><td>' + (k.a != null ? k.a : 0) + '</td><td>' + k.intensity.toFixed(2) + '</td><td>' + (k.manifest ? '<span class="badge b-gold">显</span>' : "—") + '</td></tr>';
    }).join("") : '<tr><td colspan="6">未检出键</td></tr>';
    var tableBlk = subH(t("table3")) +
      '<table class="pan" style="text-align:left"><thead><tr><th>键类</th><th>双方</th><th>层</th><th>a</th><th>烈度</th><th>事显</th></tr></thead><tbody>' + rows + '</tbody></table>' +
      (keys.length > 28 ? '<div class="verdict" style="font-size:11.5px;color:var(--mist)">共 ' + keys.length + ' 键，按烈度显示前 28。</div>' : "") +
      ((A.tonggen && A.tonggen.length) ? '<div class="verdict"><b>通根：</b>' + A.tonggen.map(function (t) { return t.note; }).join("；") + '</div>' : "") +
      (pipe && pipe.windows ? '<div class="verdict" style="font-size:12px;color:var(--mist)">成局窗：' + pipe.windows.filter(function (w) { return w.n >= 2; }).map(function (w) { return (w.chars || []).join("") + w.cls + " A+B=" + w.ab + (w.formed ? " 成键" : " 退回两字"); }).join(" · ") + '</div>' : "");
    // ④ 心性 vs 功用（铁律：隔位归零 · 根气共振 · 库中不透）
    var xg = (A2 && A2.xinGong) ? A2.xinGong : ((window.KRXinGong) ? window.KRXinGong.analyze({
      pillars: c.rawPillars,
      rel: pipe || A,
      dayun: c.daYun,
      liveGz: layers.map(function (e) { return { label: e.label, gz: e.gz }; }),
    }) : null);
    var xgBlk = subH(t("xgTitle")) +
      (xg && window.KRXinGong.html ? window.KRXinGong.html(xg, function (k) {
        var map = { xinXing: "先天心性：", gongYong: "实际功用：", xianHua: "最终显化：", gongJie: "功用结论：", vaultBan: "库中不透：", yunStep: "第", yunCuts: "大运变局（关键截断点）", liveCuts: "当时当地 · 截断点", yearBrief: "流年做功简析", sop: "断盘五步（样办）", taiji: "太极六亲", taijiJing: "环境：", taijiXiao: "消长：" };
        return (window.KRLang && KRLang.t && KRLang.t("xg_" + k) !== "xg_" + k) ? KRLang.t("xg_" + k) : (map[k] || k);
      }) : '<div class="verdict">未检出心性／功用结构。</div>');
    // ⑤ 详断（键共振·流时应期）
    var dynLines = dynKeys.slice(0, 8).map(function (k) {
      return (k.manifest ? "【事显】" : "【潜】") + (k._layer || "") + " · " + (k.layerZh || k.note || "") + " " + (k.chars || []).join("") + (k.hua ? "→" + k.hua : "") + "·" + k.cls + " 烈度" + k.intensity.toFixed(2) + (k.manifest ? "（≥2.0 · 主应期）" : "（未及显）");
    });
    var xiduanBlk = subH(t("detail4")) +
      '<div class="verdict">' + ((A2.verdicts && A2.verdicts.lines) || (A.verdicts && A.verdicts.lines) || []).join("<br>") + '</div>' +
      (dynLines.length
        ? '<div class="verdict" style="margin-top:6px;color:var(--bronze)">流时联动应期：</div><div class="verdict">' + dynLines.join("<br>") + '</div>'
        : '<div class="verdict" style="color:var(--mist)">当前时地未触发动态显键。</div>');
    // ⑥ 全部教程
    var teachBlk = subH(t("subTeachAll")) +
      '<div style="display:flex;gap:8px;margin-top:6px;flex-wrap:wrap"><a href="./teach.html?unlock=master#base" style="flex:1;min-width:96px;text-align:center;background:var(--jade);color:#0c120e;padding:8px;border-radius:9px;font-weight:700">' + t("teachBaseBtn") + "</a>" +
      '<a href="./teach.html?unlock=master#pro" style="flex:1;min-width:96px;text-align:center;background:var(--bronze);color:#0c120e;padding:8px;border-radius:9px;font-weight:700">' + t("teachProBtn") + "</a>" +
      '<a href="./teach.html?unlock=master#key" style="flex:1;min-width:96px;text-align:center;background:var(--gold);color:#0c120e;padding:8px;border-radius:9px;font-weight:700">' + t("teachKeyBtn") + "</a></div>";
    var shifuBlk = subH(t("subShifu")) +
      (xg && window.KRShifu && window.KRShifu.html ? window.KRShifu.html(xg) : '<div class="verdict">师傅未加载。</div>');

    var paidInner = energyBlk + intenBlk + tableBlk + xgBlk + xiduanBlk + teachBlk + shifuBlk;
    var paidG = sec(t("paidKeyTitle"),
      author ? paidInner
        : '<div class="lock">' + paidInner + '</div><div class="verdict" style="color:var(--gold)">' + t("paidKeyLock") + "</div>", "tier-key");

    $("p-key").innerHTML = tierBanner("key") + ctrl + teaser + paidG + slot("键振详断——键→象义→吉凶/应期判据（如：忌神成局冲提纲＝大凶应期）可继续补充成句库。");
    if (xg && window.KRShifu && window.KRShifu.bind) window.KRShifu.bind(xg);

    // wire
    $("keyApply").onclick = function () {
      var d = $("keyDate").value.trim().split(/[-/]/).map(Number), tt = $("keyTime").value.trim().split(":").map(Number);
      commitLive(c, {
        city: $("keyCity").value, lon: parseFloat($("keyLon").value), lat: parseFloat($("keyLat").value),
        dt: { y: d[0], mo: d[1], d: d[2], h: tt[0] || 0, mi: tt[1] || 0 }, source: "typed"
      });
    };
    $("keyCity").onchange = function () { applyCityLocalNow(c, $("keyCity").value); };
    $("keyLon").onchange = $("keyLat").onchange = function () { applyCoordsLocalNow(c, $("keyLon").value, $("keyLat").value); };
    $("keyLocate").onclick = function () { applyShiDiShiKe(c, $("keyLocate")); };
  }

  // ⑤ 会员收藏
  function esc(x) { return String(x == null ? "" : x).replace(/[&<>"]/g, function (ch) { return { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" }[ch]; }); }
  function loadCases() { try { return JSON.parse(localStorage.getItem("zeisen_cases") || "[]"); } catch (e) { return []; } }
  function saveCases(arr) { try { localStorage.setItem("zeisen_cases", JSON.stringify(arr)); } catch (e) { alert("保存失败（浏览器存储可能已满）：" + e.message); } }
  function caseFromCtx(c, note) {
    var A = c.A, z = A.zhengZhuan || {};
    return {
      id: Date.now(), name: c.name || "命主", gender: c.sexNum ? "M" : "F", cal: state.cal,
      date: { y: c.y, mo: c.mo, da: c.da }, time: { h: c.birthH, mi: c.birthMi },
      city: c.cityName || "", lon: c.lon, lat: c.lat,
      standCity: (liveAnchors.stand && liveAnchors.stand.city) || c.standId || "",
      standName: liveStandName(c) || c.standName || "",
      standLon: liveStandLon(c), standLat: liveStandLat(c),
      pillars: pillarStr(c.pillars), dayMaster: c.pillars.day[0],
      pattern: (function () {
        var di0 = A.zhengDiHou || z;
        return (di0.pattern || "") + (di0.strength ? "·" + di0.strength : "") + "（定体）";
      })(),
      yong: (z.yongShen || []).slice(0, 3).join("、"), ji: (z.jiShen || []).slice(0, 3).join("、"),
      intensity: (A.intensity || 0).toFixed(2), note: note || "", events: [],
      createdAt: Date.now(), updatedAt: Date.now(),
    };
  }
  var pendingStandRestore = null;
  function loadCaseToForm(cs) {
    if (!cs) return;
    pendingStandRestore = {
      city: cs.standCity || "",
      lon: cs.standLon,
      lat: cs.standLat
    };
    $("name").value = cs.name || "";
    $("date").value = cs.date.y + "-" + z2(cs.date.mo) + "-" + z2(cs.date.da);
    $("time").value = z2(cs.time.h) + ":" + z2(cs.time.mi);
    state.gender = cs.gender === "M" ? "M" : "F";
    if ($("gM")) { $("gM").classList.toggle("on", state.gender === "M"); $("gF").classList.toggle("on", state.gender === "F"); }
    state.cal = cs.cal === "L" ? "L" : "S";
    if ($("cS")) { $("cS").classList.toggle("on", state.cal === "S"); $("cL").classList.toggle("on", state.cal === "L"); }
    var CT = window.KRCities, cid = "";
    if (CT && CT.byId) Object.keys(CT.byId).forEach(function (k) { if (CT.byId[k].name === cs.city) cid = k; });
    if ($("city")) $("city").value = cid;
    if ($("standCity")) $("standCity").value = cs.standCity || "";
    $("lon").value = cs.lon; $("lat").value = cs.lat;
    paipan();
  }
  function vipPolicy() {
    var P = window.KRPricing;
    if (P && P.casePolicy) return P.casePolicy();
    return { quota: isAuthor() ? 300 : 3, note: isAuthor(), events: isAuthor(), showPattern: hasPro(), showIntensity: hasResonance(), exportJson: hasPro(), importJson: hasPro(), exportJudgment: hasResonance(), save: true, load: true, tier: hasResonance() ? "resonance" : (hasPro() ? "pro" : "free"), single: false };
  }
  function vipEnt() {
    var P = window.KRPricing;
    if (P && P.entitlement) return P.entitlement();
    var t = vipPolicy().tier;
    return { author: isAuthor(), preview: null, purchased: "free", effective: t, effectiveZh: t, subscriptionExpires: null, singleExpires: null, singleActive: false, policy: vipPolicy() };
  }
  function caseCard(cs, i, pol) {
    pol = pol || vipPolicy();
    var ev = cs.events || [];
    var evHTML = "";
    if (pol.events && ev.length) {
      evHTML = '<div class="verdict" style="font-size:12px;color:var(--bronze);margin-top:4px">事件（' + ev.length + '）</div>' +
        ev.map(function (e, j) { return '<div class="verdict" style="font-size:12px">· <b>' + esc(e.date || "") + '</b> ' + esc(e.text || "") + ' <a href="#" class="evdel" data-i="' + i + '" data-j="' + j + '" style="color:var(--hot)">×</a></div>'; }).join("");
    } else if (!pol.events && ev.length) {
      evHTML = '<div class="verdict" style="font-size:12px;color:var(--gold)">🔒 事件追踪 ' + ev.length + ' 条（键共振档可见）</div>';
    }
    var meta = "";
    if (pol.showPattern) {
      meta += '<div class="verdict" style="font-size:13px">四柱 <b>' + esc(cs.pillars) + '</b> · 日主 <b class="' + wxc(cs.dayMaster) + '">' + esc(cs.dayMaster) + '</b> · ' + esc(cs.pattern || "—") + '</div>';
      meta += '<div class="verdict" style="font-size:12px;color:var(--mist)">用 ' + esc(cs.yong || "—") + ' · 忌 ' + esc(cs.ji || "—") +
        (pol.showIntensity ? ' · 峰烈度 ' + esc(cs.intensity || "—") : " · 🔒 烈度（键共振）") + '</div>';
    } else {
      meta += '<div class="verdict" style="font-size:13px">四柱 <b>' + esc(cs.pillars) + '</b> · 日主 <b class="' + wxc(cs.dayMaster) + '">' + esc(cs.dayMaster) + '</b></div>';
      meta += '<div class="verdict" style="font-size:12px;color:var(--gold)">🔒 格局／用忌为专业板；烈度／结论为键共振</div>';
    }
    var noteHTML = "";
    if (pol.note && cs.note) noteHTML = '<div class="verdict" style="font-size:12.5px">案情：' + esc(cs.note) + '</div>';
    else if (!pol.note && cs.note) noteHTML = '<div class="verdict" style="font-size:12px;color:var(--gold)">🔒 案情已存（专业板起可见）</div>';
    var btns = '<button class="cLoad" data-i="' + i + '">载入排盘</button>';
    if (pol.events) btns += '<button class="cEvent" data-i="' + i + '">加事件</button>';
    else btns += '<button class="btn-off" type="button" disabled>🔒 事件</button>';
    if (pol.note) btns += '<button class="cNote" data-i="' + i + '">改案情</button>';
    else btns += '<button class="btn-off" type="button" disabled>🔒 案情</button>';
    btns += '<button class="cDel" data-i="' + i + '">删</button>';
    return '<div class="vip-card">' +
      '<div style="display:flex;justify-content:space-between;align-items:baseline;gap:8px">' +
      '<div><b>' + esc(cs.name || "命主") + '</b> · ' + (cs.gender === "M" ? t("sexMale") : t("sexFemale")) + ' <span style="color:var(--mist);font-size:12px">' + cs.date.y + "-" + z2(cs.date.mo) + "-" + z2(cs.date.da) + " " + z2(cs.time.h) + ":" + z2(cs.time.mi) + (cs.cal === "L" ? "（农）" : "") + '</span></div>' +
      '<span style="color:var(--mist);font-size:12px">' + esc(cs.city || ("经" + (cs.lon != null ? (+cs.lon).toFixed(1) : "?"))) + '</span></div>' +
      meta + noteHTML + evHTML +
      '<div class="vip-btns">' + btns + '</div></div>';
  }
  function vipSkuCards(P, ent) {
    var keys = ["free", "pro", "resonance", "resonanceMo", "resonanceYr", "single", "upgrade"];
    return '<div class="vip-sku">' + keys.map(function (k) {
      var s = (P.skus && P.skus[k]) || {};
      var on = (k === ent.effective) || (k === "resonance" && ent.effective === "resonance") || (k === "single" && ent.singleActive);
      var price = P.labelZh ? P.labelZh(k) : (s.usd ? "$" + s.usd : "免费");
      var cta = k === "free" ? '<span class="btn-off">当前可试用</span>' : '<button type="button" class="payOff" data-sku="' + k + '">结账链接未接通</button>';
      return '<div class="vip-card' + (on ? " on" : "") + '"><h4>' + esc(s.label || k) + (on ? ' <span class="badge b-gold">当前</span>' : "") + '</h4>' +
        '<div class="verdict" style="margin-top:0"><b>' + esc(price) + '</b></div>' +
        '<div class="verdict" style="font-size:12px;color:var(--mist)">' + esc(s.see || "") + '<br/>SKU <code>' + esc(s.id || "") + '</code>' +
        (s.stripePrice ? '<br/>Stripe <code>' + esc(s.stripePrice) + '</code>' : "") + '</div>' +
        '<div class="vip-btns" style="margin-top:8px">' + cta + '</div></div>';
    }).join("") + '</div>';
  }
  function vipMatrix() {
    var rows = [
      ["①基础【初断】", "✓", "✓", "✓", "—"],
      ["②专业【细断】", "🔒", "✓", "✓", "—"],
      ["③键振【详断】", "🔒", "🔒", "✓", "—"],
      ["④结论／导出", "🔒", "🔒", "✓", "24h 临时"],
      ["基础知识教程", "✓", "✓", "✓", "—"],
      ["专业教程", "🔒", "✓", "✓", "—"],
      ["键共振教程", "🔒", "🔒", "✓", "—"],
      ["案件条数", "3", "30", "300", "不扩容"],
      ["案情备注", "🔒", "✓", "✓", "—"],
      ["事件追踪", "🔒", "🔒", "✓", "—"],
      ["案件 JSON 导出", "🔒", "脱敏", "全文", "—"],
    ];
    return '<table class="pan" style="text-align:center"><thead><tr><th>规定</th><th>免费</th><th>专业</th><th>键共振</th><th>单次24h</th></tr></thead><tbody>' +
      rows.map(function (r) { return "<tr>" + r.map(function (c, i) { return "<td" + (i === 0 ? ' class="rowlab"' : "") + ">" + c + "</td>"; }).join("") + "</tr>"; }).join("") +
      "</tbody></table>";
  }
  function judgmentMarkdown(c) {
    var z = (c.A && c.A.zhengZhuan) || {};
    var di = (c.A && c.A.zhengDiHou) || z;
    var lines = (c.A && c.A.verdicts && c.A.verdicts.lines) || [];
    return [
      "# 泽森排盘 · 判局",
      "生成：" + new Date().toISOString(),
      "档位：" + (vipEnt().effectiveZh || ""),
      "",
      "命主：" + (c.name || "—") + " · " + (c.sexNum ? t("sexMale") : t("sexFemale")),
      "出生：" + c.y + "-" + z2(c.mo) + "-" + z2(c.da) + " " + z2(c.birthH) + ":" + z2(c.birthMi) + " · " + (c.cityName || ""),
      "现住：" + (liveStandName(c) || c.standName || "—"),
      "四柱：" + pillarStr(c.pillars),
      "日主：" + (c.pillars.day[0] || ""),
      "定体正从（身份）：" + (di.pattern || "—") + " · " + (di.strength || ""),
      "第二次用忌：" + (z.orient || "—") + (z.yongFlipped ? "（相对定境已变）" : "（用忌未变）"),
      "第二次用神：" + ((z.yongShen || []).slice(0, 6).join("、") || "—"),
      "第二次忌神：" + ((z.jiShen || []).slice(0, 6).join("、") || "—"),
      "峰烈度：" + ((c.A && c.A.intensity) != null ? Number(c.A.intensity).toFixed(2) : "—"),
      "",
      "## 断语",
      lines.length ? lines.join("\n") : "—",
      "",
      "（单次 24h 只开结论导出，不升档、不扩案件库。）",
    ].join("\n");
  }
  function renderVip(c) {
    var P = window.KRPricing || {};
    var ent = vipEnt();
    var pol = ent.policy || vipPolicy();
    var cases = loadCases();
    var used = cases.length;
    var left = Math.max(0, pol.quota - used);
    var status = '<div class="vip-now">' +
      '<div class="verdict" style="margin-top:0">有效档：<b>' + esc(ent.effectiveZh || ent.effective) + '</b>' +
      (ent.author ? ' · 作者解锁' : "") +
      (ent.preview ? ' · <span style="color:var(--gold)">作者预览（非付款）</span>' : "") +
      (ent.singleActive ? ' · <span class="badge b-gold">单次24h 开</span>' : "") +
      '</div>' +
      '<div class="verdict" style="font-size:12px;color:var(--mist)">本机购买档：' + esc((P.tierZh && P.tierZh(ent.purchased)) || ent.purchased || "免费") +
      ' · 订阅到期：' + (ent.subscriptionExpires && P.fmtTime ? P.fmtTime(ent.subscriptionExpires) : "无") +
      ' · 单次到期：' + (ent.singleExpires && P.fmtTime ? P.fmtTime(ent.singleExpires) : (ent.previewSingle ? "预览中" : "无")) +
      '</div>' +
      '<div class="verdict" style="font-size:12px">案件配额 <b>' + used + "/" + pol.quota + '</b> · 还可存 ' + left + ' 条 · <span style="color:var(--gold)">结账链接未接通</span></div>' +
      '<div class="verdict" style="font-size:12px;color:var(--mist)">' + esc(P.pageArrowZh || "") + '</div></div>';
    if (ent.author) {
      var prev = ent.preview || "";
      status += '<div class="verdict" style="font-size:12px">作者预览档（只改本页门控，不写付款成功）</div>' +
        '<div style="display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin-top:6px">' +
        '<select id="vipPreview">' +
        '<option value=""' + (!prev ? " selected" : "") + '>跟随作者全开</option>' +
        '<option value="free"' + (prev === "free" ? " selected" : "") + '>预览免费</option>' +
        '<option value="pro"' + (prev === "pro" ? " selected" : "") + '>预览专业板</option>' +
        '<option value="resonance"' + (prev === "resonance" ? " selected" : "") + '>预览键共振</option>' +
        '</select>' +
        '<label style="color:var(--mist);font-size:12.5px;white-space:nowrap"><input type="checkbox" id="vipPreviewSingle"' + (ent.previewSingle ? " checked" : "") + '> 预览单次24h</label></div>';
    }
    var s = sec("会员设置（2026-07-31 价目 · 2026-08-14 案例规定）", status);
    s += sec("价目全表（SKU）", vipSkuCards(P, ent) +
      '<a href="./pricing.html" style="display:inline-block;margin-top:8px;background:var(--bronze);color:#0c120e;padding:7px 12px;border-radius:9px;font-weight:700">打开价目说明页 →</a>');
    s += sec("档位规定", vipMatrix() +
      '<div class="verdict" style="font-size:12px;color:var(--mist)">月订／年订权益＝键共振全功能，到期回本机购买档。单次 24h 只开④结论导出，不升档、不扩永久库。云端同步／标签检索未做。</div>');
    var noteBox = pol.note
      ? '<textarea id="caseNote" placeholder="案情/背景（可选）：地域·职业·家境，及要追踪的事项…"></textarea>'
      : '<div class="verdict" style="color:var(--gold)">🔒 案情备注为专业板起。免费档只存姓名＋四柱摘要。</div><textarea id="caseNote" style="display:none"></textarea>';
    var saveLbl = left > 0 ? "收藏本盘为案件（余 " + left + "）" : "已达本档配额（" + pol.quota + "）";
    s += sec("记录本盘为案件",
      '<div class="verdict" style="font-size:12px;color:var(--mist)">本盘：<b>' + esc(c.name) + '</b> · ' + esc(pillarStr(c.pillars)) +
      (pol.showPattern ? ' · ' + esc((c.A.zhengZhuan.pattern || "") + "·" + (c.A.zhengZhuan.strength || "")) : " · 🔒 格局") + '</div>' +
      noteBox +
      '<button class="go" id="caseSave" style="margin:0"' + (left > 0 ? "" : " disabled") + '>' + saveLbl + '</button>' +
      (pol.exportJudgment ? '<button id="caseMd" style="margin-top:8px;width:100%;background:var(--bronze);color:#0c120e;border:0;border-radius:9px;padding:8px;font-weight:700">导出本盘判局（Markdown）</button>' : '<div class="verdict" style="color:var(--gold);font-size:12px">🔒 判局 Markdown 导出：键共振档，或单次 24h 通行证。</div>'));
    var listHTML = cases.length ? cases.map(function (cs, i) { return caseCard(cs, i, pol); }).join("") : '<div class="verdict">案件库为空。上方「收藏本盘为案件」即可开始记录。</div>';
    s += sec("我的案件库（" + used + "/" + pol.quota + "）", listHTML);
    var bak = "";
    if (pol.exportJson) bak += '<button id="caseExport">导出 JSON' + (pol.showIntensity ? "（全文）" : "（脱敏）") + '</button>';
    else bak += '<button class="btn-off" type="button" disabled>🔒 导出 JSON</button>';
    if (pol.importJson) bak += '<label class="vip-import">导入 JSON<input id="caseImport" type="file" accept="application/json,.json"></label>';
    else bak += '<button class="btn-off" type="button" disabled>🔒 导入</button>';
    s += sec("案件库 · 备份",
      '<div class="vip-btns">' + bak + '</div>' +
      '<div class="verdict" style="font-size:12px;color:var(--mist)">存于本机浏览器（localStorage）· 私密不上传。专业板导出不含烈度／事件；键共振导出全文。换机前请先备份。</div>');
    s += slot("真结账 Checkout／IAP 仍未接通——按钮不会写成付款成功。云端同步／标签检索可另补。");
    $("p-vip").innerHTML = s;
    wireVip(c, cases, pol);
  }
  function wireVip(c, cases, pol) {
    pol = pol || vipPolicy();
    var P = window.KRPricing || {};
    var each = function (sel, fn) { Array.prototype.forEach.call($("p-vip").querySelectorAll(sel), fn); };
    var prev = $("vipPreview");
    if (prev) prev.onchange = function () {
      if (P.setPreviewTier) P.setPreviewTier(prev.value || null);
      if (lastCtx) render(lastCtx);
    };
    var ps = $("vipPreviewSingle");
    if (ps) ps.onchange = function () {
      if (P.setPreviewSingle) P.setPreviewSingle(ps.checked);
      if (lastCtx) render(lastCtx);
      else renderVip(c);
    };
    each(".payOff", function (b) {
      b.onclick = function () {
        var r = P.checkout ? P.checkout(b.getAttribute("data-sku")) : { message: "结账链接未接通" };
        alert(r.message || "结账链接未接通");
      };
    });
    var save = $("caseSave");
    if (save) save.onclick = function () {
      if (cases.length >= pol.quota) { alert("已达本档案件配额（" + pol.quota + " 条）。升级档位或删除旧案后再收藏。"); return; }
      var note = pol.note ? (($("caseNote") && $("caseNote").value.trim()) || "") : "";
      cases.unshift(caseFromCtx(c, note));
      saveCases(cases.slice(0, pol.quota));
      renderVip(c);
    };
    each(".cDel", function (b) { b.onclick = function () { var i = +b.getAttribute("data-i"); if (confirm("删除该案件？")) { cases.splice(i, 1); saveCases(cases); renderVip(c); } }; });
    each(".cLoad", function (b) { b.onclick = function () { loadCaseToForm(cases[+b.getAttribute("data-i")]); }; });
    each(".cEvent", function (b) {
      b.onclick = function () {
        if (!pol.events) { alert("事件追踪为键共振档功能。"); return; }
        var i = +b.getAttribute("data-i");
        var d = prompt("事件日期（如 2024-05 或 2024-05-12）："); if (d === null) return;
        var tx = prompt("事件内容（如：升职 / 婚变 / 病灾 / 破财 / 添丁…）："); if (tx === null) return;
        cases[i].events = cases[i].events || []; cases[i].events.push({ date: d.trim(), text: tx.trim() });
        cases[i].updatedAt = Date.now(); saveCases(cases); renderVip(c);
      };
    });
    each(".cNote", function (b) {
      b.onclick = function () {
        if (!pol.note) { alert("案情备注为专业板起。"); return; }
        var i = +b.getAttribute("data-i"); var nt = prompt("案情 / 背景：", cases[i].note || ""); if (nt === null) return; cases[i].note = nt.trim(); cases[i].updatedAt = Date.now(); saveCases(cases); renderVip(c);
      };
    });
    each(".evdel", function (a) { a.onclick = function (e) { e.preventDefault(); if (!pol.events) return; var i = +a.getAttribute("data-i"), j = +a.getAttribute("data-j"); cases[i].events.splice(j, 1); cases[i].updatedAt = Date.now(); saveCases(cases); renderVip(c); }; });
    var ex = $("caseExport");
    if (ex) ex.onclick = function () {
      if (!pol.exportJson) { alert("JSON 导出为专业板起。"); return; }
      var payload = pol.showIntensity ? cases : cases.map(function (cs) { return (P.sanitizeCase ? P.sanitizeCase(cs, false) : cs); });
      var blob = new Blob([JSON.stringify(payload, null, 2)], { type: "application/json" });
      var url = URL.createObjectURL(blob), a = document.createElement("a");
      a.href = url; a.download = "zeisen_cases_" + new Date().toISOString().slice(0, 10) + ".json"; a.click();
      setTimeout(function () { URL.revokeObjectURL(url); }, 1000);
    };
    var md = $("caseMd");
    if (md) md.onclick = function () {
      if (!pol.exportJudgment) { alert("判局导出为键共振档或单次 24h。"); return; }
      var blob = new Blob([judgmentMarkdown(c)], { type: "text/markdown" });
      var url = URL.createObjectURL(blob), a = document.createElement("a");
      a.href = url; a.download = "zeisen_judgment_" + (c.name || "chart") + ".md"; a.click();
      setTimeout(function () { URL.revokeObjectURL(url); }, 1000);
    };
    var im = $("caseImport");
    if (im) im.onchange = function () {
      if (!pol.importJson) { alert("导入为专业板起。"); return; }
      var f = im.files[0]; if (!f) return; var r = new FileReader();
      r.onload = function () {
        try {
          var arr = JSON.parse(r.result); if (!Array.isArray(arr)) throw new Error("格式应为案件数组");
          var next = arr.concat(cases).slice(0, pol.quota);
          saveCases(next); renderVip(c);
          alert("已导入 " + arr.length + " 条（本档上限 " + pol.quota + "，现库 " + next.length + "）。");
        } catch (e) { alert("导入失败：" + e.message); }
      };
      r.readAsText(f);
    };
  }

  // helpers
  function ss(dayGan, other) {
    var SS = window.LunarUtil && window.LunarUtil.SHI_SHEN;
    var raw = (SS && SS[dayGan + other]) || (dayGan === other ? "比肩" : "—");
    return window.KRLang ? KRLang.ss(raw) : raw;
  }
  function sec(t, body, cls) { return '<div class="sec' + (cls ? " " + cls : "") + '"><h3>' + t + '</h3>' + body + '</div>'; }
  function kv(k, v) { return '<div class="kv"><div class="k">' + k + '</div><div>' + (v == null ? "—" : v) + '</div></div>'; }
  function slot(t) { return '<div class="slot">🖊 ' + t + '</div>'; }
  function tr(lab, cells) { return '<tr><td class="rowlab">' + lab + '</td>' + cells.map(function (c) { return '<td>' + c + '</td>'; }).join("") + '</tr>'; }
  function trh(cells) { return '<tr>' + cells.map(function (c) { return '<th>' + c + '</th>'; }).join("") + '</tr>'; }
  function pillarStr(p) { return [p.year, p.month, p.day, p.time].join(" "); }
  function pc(v) { return Math.round((v || 0) * 100) + "%"; }
  function safe(fn) { try { return fn(); } catch (e) { return "—"; } }

  // —— 作者最高管理：仅苹果账号 + 密码可登录（不接受 URL／旧口令） ——
  var ADMIN_APPLE = "jasonli1203@icloud.com";
  var ADMIN_PW = "Zeisen2026";
  var adminTab = "tools";
  function adminAuthed() { try { return sessionStorage.getItem("zeisen_admin_ok") === "1"; } catch (e) { return false; } }
  function setAdminAuthed(on) { try { if (on) sessionStorage.setItem("zeisen_admin_ok", "1"); else sessionStorage.removeItem("zeisen_admin_ok"); } catch (e) { } }
  function checkAppleLogin(email, pw) {
    var e = String(email || "").trim().toLowerCase();
    var p = String(pw || "").replace(/\s/g, "");
    if (e === ADMIN_APPLE && p === ADMIN_PW) return true;
    if (p === ADMIN_APPLE + ADMIN_PW) return true;
    if ((e + p) === ADMIN_APPLE + ADMIN_PW) return true;
    return false;
  }
  function adminUnlockAuthor() { try { localStorage.setItem("kr_author_unlock", "master"); sessionStorage.setItem("kr_author_unlock", "master"); } catch (e) { } if (window.KRAuthor) { window.KRAuthor.unlocked = true; window.KRAuthor.level = "master"; } }
  function adminLock() { try { localStorage.removeItem("kr_author_unlock"); sessionStorage.removeItem("kr_author_unlock"); } catch (e) { } setAdminAuthed(false); if (window.KRAuthor) { window.KRAuthor.unlocked = false; window.KRAuthor.level = "guest"; } }

  function initAdmin() {
    var taps = 0, timer = null;
    function hit() { taps++; clearTimeout(timer); timer = setTimeout(function () { taps = 0; }, 2000); if (taps >= 5) { taps = 0; openAdmin(); } }
    var logo = $("logo"); if (logo) logo.addEventListener("click", hit);
    var dot = $("secretDot"); if (dot) { var d = 0, dt = null; dot.addEventListener("click", function () { d++; clearTimeout(dt); dt = setTimeout(function () { d = 0; }, 1500); if (d >= 3) { d = 0; openAdmin(); } }); }
    var close = $("aClose"); if (close) close.onclick = closeAdmin;
    var modal = $("adminModal"); if (modal) modal.addEventListener("click", function (e) { if (e.target === modal) closeAdmin(); });
    try { localStorage.removeItem("zeisen_admin_pw"); } catch (e) { }
    try { if (new URLSearchParams(location.search).get("admin") === "1") openAdmin(); } catch (e) { }
  }
  function openAdmin() { var m = $("adminModal"); if (!m) return; m.style.display = "flex"; renderAdmin(); }
  function closeAdmin() { var m = $("adminModal"); if (m) m.style.display = "none"; }
  function renderAdmin() {
    var body = $("aBody"); if (!body) return;
    if (!adminAuthed()) { body.innerHTML = adminLogin(); wireAdminLogin(); return; }
    body.innerHTML = '<div class="atabs">' + tabBtn("tools", "管理工具") + tabBtn("cases", "案例收藏") + tabBtn("verdict", "断语库") + tabBtn("params", "参数表") + '</div><div id="aContent"></div>';
    Array.prototype.forEach.call(body.querySelectorAll(".atabs button"), function (b) { b.onclick = function () { adminTab = b.getAttribute("data-t"); renderAdmin(); }; });
    var ct = $("aContent");
    ct.innerHTML = adminTab === "cases" ? adminCases() : adminTab === "verdict" ? adminVerdicts() : adminTab === "params" ? adminParams() : adminTools();
    if (adminTab === "tools") wireAdminTools();
    if (adminTab === "cases") wireAdminCases();
  }
  function tabBtn(id, label) { return '<button data-t="' + id + '" class="' + (adminTab === id ? "on" : "") + '">' + label + '</button>'; }
  function adminLogin() {
    return '<div class="abox"><h5>最高管理入口</h5><div class="msg" style="color:var(--mist)">须同时键入苹果账号与密码。仅此一组可登录；URL 解锁不能进本台。</div>' +
      '<input id="aApple" type="email" placeholder="苹果账号（iCloud）" autocomplete="username"/>' +
      '<input id="aPw" type="password" placeholder="密码" autocomplete="current-password"/>' +
      '<button class="abtn" id="aLogin">进入</button> <button class="abtn alt" id="aClose2">取消</button>' +
      '<div class="msg" id="aMsg"></div></div>';
  }
  function wireAdminLogin() {
    var go = $("aLogin");
    if (go) go.onclick = function () {
      var em = ($("aApple") && $("aApple").value) || "", pw = ($("aPw") && $("aPw").value) || "";
      if (checkAppleLogin(em, pw)) { setAdminAuthed(true); adminUnlockAuthor(); if (lastCtx) render(lastCtx); renderAdmin(); }
      else { var m = $("aMsg"); if (m) { m.style.color = "var(--hot)"; m.textContent = "账号或密码错误。须苹果账号 + 密码同时正确。"; } }
    };
    var pw = $("aPw"), em = $("aApple");
    function ent(e) { if (e.key === "Enter" && go) go.click(); }
    if (pw) pw.addEventListener("keydown", ent);
    if (em) em.addEventListener("keydown", ent);
    var c2 = $("aClose2"); if (c2) c2.onclick = closeAdmin;
  }
  function adminLink(href, label) { return '<a href="' + href + '?unlock=master" class="ghost-link">' + label + ' →</a>'; }
  function adminTools() {
    var cases = loadCases(), size = 0; try { size = (localStorage.getItem("zeisen_cases") || "").length; } catch (e) { }
    var P = window.KRPricing || {};
    var prev = P.previewTier ? (P.previewTier() || "") : "";
    var singleOn = P.singleJudgmentActive ? P.singleJudgmentActive() && (P.entitlement && P.entitlement().previewSingle) : false;
    try { if (P.entitlement) singleOn = !!P.entitlement().previewSingle; } catch (e) { }
    return '<div class="abox"><h5>权限</h5><div class="msg" style="color:var(--ok)">已用苹果账号登录作者台（全站付费内容已开）。</div><button class="abtn alt" id="atLock" style="margin-top:6px">锁定（登出作者）</button></div>' +
      '<div class="abox"><h5>会员档预览（非付款）</h5><div class="msg" style="color:var(--mist)">只改本机会员门控，不写购买成功。结账仍未接通。</div>' +
      '<select id="atPreview" class="ghost-select">' +
      '<option value=""' + (!prev ? " selected" : "") + '>跟随作者全开</option>' +
      '<option value="free"' + (prev === "free" ? " selected" : "") + '>预览免费</option>' +
      '<option value="pro"' + (prev === "pro" ? " selected" : "") + '>预览专业板</option>' +
      '<option value="resonance"' + (prev === "resonance" ? " selected" : "") + '>预览键共振</option></select>' +
      '<label style="display:block;margin-top:8px;color:var(--mist)"><input type="checkbox" id="atPreviewSingle"' + (singleOn ? " checked" : "") + '> 预览单次判局 24h（不升档）</label></div>' +
      '<div class="abox"><h5>登录规则</h5><div class="msg" style="color:var(--mist)">仅苹果账号 + 密码可进本台。不可在此改口令。结账仍未接通。</div></div>' +
      '<div class="abox"><h5>存储</h5><div class="msg">案例 <b>' + cases.length + '</b> 条 · 约 ' + size + ' 字符。</div><button class="abtn alt" id="atClearCases" style="margin-top:6px">清空案例库</button></div>' +
      '<div class="abox"><h5>管理页面 · 直达</h5><div class="msg" style="line-height:2">' +
      adminLink("../settings.html", "设置/解锁") + adminLink("./pricing.html", "价目") + adminLink("../model_rules_params.html", "参数表") + adminLink("./canon.html", "典章") + adminLink("./verdict_bank.html", "断语库") + adminLink("./o_program.html", "O程序") + adminLink("./p_program.html", "P程序") + adminLink("./kanju.html", "看局") + adminLink("./kanpan_report.html", "看盘报表") + adminLink("./case_analysis.html", "案例回测") + adminLink("./layout_calc.html", "布局场强") + adminLink("./hecan.html", "合参") + adminLink("./teach.html", "教程") +
      '</div></div>';
  }
  function wireAdminTools() {
    var lock = $("atLock"); if (lock) lock.onclick = function () { if (confirm("锁定作者权限？付费内容将重新上锁。")) { adminLock(); if (lastCtx) render(lastCtx); renderAdmin(); } };
    var clr = $("atClearCases"); if (clr) clr.onclick = function () { if (confirm("确定清空全部案例？此操作不可恢复（建议先在会员收藏导出备份）。")) { try { localStorage.removeItem("zeisen_cases"); } catch (e) { } renderAdmin(); if (lastCtx) renderVip(lastCtx); } };
    var pv = $("atPreview");
    if (pv) pv.onchange = function () {
      if (window.KRPricing && window.KRPricing.setPreviewTier) window.KRPricing.setPreviewTier(pv.value || null);
      if (lastCtx) render(lastCtx);
    };
    var ps = $("atPreviewSingle");
    if (ps) ps.onchange = function () {
      if (window.KRPricing && window.KRPricing.setPreviewSingle) window.KRPricing.setPreviewSingle(ps.checked);
      if (lastCtx) render(lastCtx);
    };
  }
  function adminCases() {
    var cases = loadCases();
    var rows = cases.length ? cases.map(function (cs) { return '<tr><td>' + esc(cs.name || "—") + '</td><td>' + esc(cs.pillars || "") + '</td><td>' + esc(cs.pattern || "") + '</td><td>' + ((cs.events || []).length) + '</td></tr>'; }).join("") : '<tr><td colspan="4">案例库为空</td></tr>';
    return '<div class="abox"><h5>案例收藏（' + cases.length + '）</h5>' +
      '<table class="aparam"><thead><tr><th>命主</th><th>四柱</th><th>格局</th><th>事件</th></tr></thead><tbody>' + rows + '</tbody></table>' +
      '<div style="margin-top:8px"><button class="abtn" id="acExport">导出全部</button> <label class="abtn alt" style="cursor:pointer">导入<input id="acImport" type="file" accept=".json,application/json" style="display:none"></label></div>' +
      '<div class="msg" style="color:var(--mist)">案例来自「会员收藏」的本机案件库；在会员收藏页可逐条编辑/加事件/载入排盘。</div>' +
      '<a href="./case_analysis.html?unlock=master" style="display:inline-block;margin-top:8px;background:var(--jade);color:#0c120e;padding:7px 12px;border-radius:9px;font-weight:700">打开案例回测（7 事件）→</a></div>';
  }
  function wireAdminCases() {
    var ex = $("acExport"); if (ex) ex.onclick = function () { var cases = loadCases(); var blob = new Blob([JSON.stringify(cases, null, 2)], { type: "application/json" }); var url = URL.createObjectURL(blob), a = document.createElement("a"); a.href = url; a.download = "zeisen_cases_" + new Date().toISOString().slice(0, 10) + ".json"; a.click(); setTimeout(function () { URL.revokeObjectURL(url); }, 800); };
    var im = $("acImport"); if (im) im.onchange = function () { var f = im.files[0]; if (!f) return; var r = new FileReader(); r.onload = function () { try { var arr = JSON.parse(r.result); if (!Array.isArray(arr)) throw new Error("应为数组"); saveCases(arr.concat(loadCases()).slice(0, 600)); renderAdmin(); alert("导入 " + arr.length + " 条。"); } catch (e) { alert("导入失败：" + e.message); } }; r.readAsText(f); };
  }
  function adminVerdicts() {
    var live = (lastCtx && lastCtx.A && lastCtx.A.verdicts && lastCtx.A.verdicts.lines) ? lastCtx.A.verdicts.lines : [];
    var cats = [
      ["配对指数", "BI 分档（身强/身弱/均衡）→ 配对强弱一句"],
      ["格局做功", "杀印相生/食伤生财/财官相生/比劫夺财/扶抑常规 → 吉凶"],
      ["用忌取向", "生克后第二次冻结的用神/忌神 → 宜忌"],
      ["键·事显", "烈度 ≥ 2.0 的键 → 应期显象；< 2.0 为潜"],
      ["通根", "天干通根地支藏干 → 力量与去留"],
      ["身强极·身弱极", "BI 极端的专属断语"],
    ];
    return '<div class="abox"><h5>断语库 · 分类</h5><table class="aparam"><tbody>' + cats.map(function (x) { return '<tr><th style="white-space:nowrap">' + x[0] + '</th><td>' + x[1] + '</td></tr>'; }).join("") + '</tbody></table></div>' +
      '<div class="abox"><h5>本盘自动断语（示例）</h5>' + (live.length ? '<div class="msg">' + live.map(esc).join("<br>") + '</div>' : '<div class="msg" style="color:var(--mist)">先排一个盘，这里显示该盘自动断语。</div>') + '</div>' +
      '<div class="abox"><h5>完整断语库</h5><a href="./verdict_bank.html?unlock=master" style="display:inline-block;background:var(--bronze);color:#0c120e;padding:7px 12px;border-radius:9px;font-weight:700">断语库全书 →</a> <a href="./canon.html?unlock=master" class="ghost-link" style="padding:7px 12px;border-radius:9px">典章 →</a></div>';
  }
  function objTable(o) { return '<table class="aparam"><tbody>' + Object.keys(o).map(function (k) { return '<tr><th>' + esc(k) + '</th><td>' + esc(o[k]) + '</td></tr>'; }).join("") + '</tbody></table>'; }
  function kvRows(pairs) { return '<table class="aparam"><tbody>' + pairs.map(function (p) { return '<tr><th style="white-space:nowrap">' + esc(p[0]) + '</th><td>' + esc(p[1] == null ? "—" : p[1]) + '</td></tr>'; }).join("") + '</tbody></table>'; }
  function skZh(k) { return { he: "合", chong: "冲", sheng: "生", ke: "克", xing: "刑", hai: "害", poSheng: "破(A生B)" }[k] || k; }
  function adminParams() {
    var P = window.KRParams || {};
    var html = '<div class="abox"><h5>核心口径</h5>' + kvRows([["版本", P.version], ["定稿", P.labelZh], ["地相对令", P.diRelativeToLing]]) + (P.oneLinerZh ? '<div class="msg" style="color:var(--mist);margin-top:6px">' + esc(P.oneLinerZh) + '</div>' : '') + '</div>';
    if (P.ling) html += '<div class="abox"><h5>令（旺相休囚死）</h5>' + objTable(P.ling) + '</div>';
    if (P.yun) html += '<div class="abox"><h5>运（大运/小运）</h5>' + objTable(P.yun) + '</div>';
    if (P.shengKe) { var sk = P.shengKe; var rows = Object.keys(sk).map(function (k) { var v = sk[k]; var val = (v && typeof v === "object") ? Object.keys(v).map(function (kk) { return kk + "=" + v[kk]; }).join(" · ") : v; return '<tr><th>' + skZh(k) + '</th><td>' + esc(val) + '</td></tr>'; }).join(""); html += '<div class="abox"><h5>生克（合冲生克刑害破）</h5><table class="aparam"><tbody>' + rows + '</tbody></table></div>'; }
    html += '<div class="abox"><a href="../model_rules_params.html?unlock=master" style="display:inline-block;background:var(--jade);color:#0c120e;padding:7px 12px;border-radius:9px;font-weight:700">参数表全页 →</a> <a href="../settings.html?unlock=master" class="ghost-link" style="padding:7px 12px;border-radius:9px">原始 JSON →</a></div>';
    return html;
  }

  if (document.readyState !== "loading") initHome();
  else document.addEventListener("DOMContentLoaded", initHome);
  if (typeof navigator !== "undefined" && navigator.serviceWorker) {
    navigator.serviceWorker.register("./sw.js").catch(function () {});
  }
})();
