dsh-metaphysics 0.61.0 → 0.61.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-metaphysics",
3
- "version": "0.61.0",
3
+ "version": "0.61.2",
4
4
  "type": "module",
5
5
  "description": "命理排盘(玄学 xuanxue)for DeepSeek Harness: nineteen systems and eleven quick references (almanac, solar–lunar conversion, …), with climate adjustment (tiaohou), the 120 Qiongtong Baojian entries, 1,062-char name library with per-character pinyin, Kangxi stroke counts, five-grid (五格/三才) analysis and mechanical phonetics checks, a 78-card tarot library on the Rider-Waite-Smith base (per-card English name, image description, core meaning and astrological correspondence, cross-checked against three published sources; court cards carry no zodiac by design and the chart says so), classical quotes (Ziping Zhenquan / Di Tian Sui / San Ming Tong Hui), hexagram body relations (六冲/六合/伏吟/反吟/化进神/化退神) plus yongshen placement (用神落位) with month/day strength (月建日辰旺衰) and whole-hexagram per-line strength (全卦逐爻旺衰) and response-time clues (应期线索:逢值/逢冲/逢合 + 月建五态快慢) for liuyao, plus an MBTI-style 16-type questionnaire and exact dayun handover dates (交运日) with annual suiyun hints (岁运并临/伏吟反吟), palace conditions (击刑/入墓), month states (旺相休囚死) and stem–palace relations (天盘干与宫生克) for qimen, flying four-transformations with chained flights (四化飞星串连), three-way-four-corner composite flights (三方四正复合飞化), switchable leap-month policy (闰月按日切分/整月归次月/整月归本月) eleven school switches (亮度/庚干四化/天马/天空/截空旬空/魁钺/天伤天使/长生/晚子时/身主/杂曜全表) for ziwei, the last one opting in to a 61-star minor-star (杂曜) table where each of the twelve extra stars carries its placement verse and the palace it landed in (verses accepted only when at least two independent published editions give the same table, homonymous stars never merged), caliber diffs for bazi/ziwei/qimen (same inputs, one switch, root-field changes), shadow-chart IDs (same normalized params → same ID; version bumps auto-diff chart fields), step-by-step deduction traces (liuyao najia→liuqin→shiying→fushen→strength; bazi strength score and pattern/yongshen; ziwei pattern conditions), and a bundled offline single-file page (命理排盘.html), a panel-to-chat handoff that asks the Harness about a finished chart, per-tab lazy engine chunks so the first-screen client bundle stays under 100KB gzip, and a public verification report (128 classical hexagram cases, 312 Zi Wei charts × 34 patterns, 12 dayun handover cases) generated from the same source that gates CI.",
6
6
  "keywords": [
@@ -47,6 +47,10 @@ header{display:flex;flex-direction:column;align-items:stretch;gap:12px;margin-bo
47
47
  .brand-txt small{display:block;font-size:12.5px;letter-spacing:.02em;color:var(--dim);font-weight:400;margin-top:3px}
48
48
  .ver{display:inline-block;margin-left:8px;vertical-align:3px;padding:2px 8px;border:1px solid var(--line);border-radius:999px;background:var(--panel2);
49
49
  color:var(--dim);font:600 11px/1.5 system-ui,sans-serif;letter-spacing:.04em}
50
+ /* 未发版改动小牌:紧跟版本徽标,鼠标悬停看逐条。字色用 --ink2 而不是 --dim——
51
+ 这行是 11px 小字,压在 --panel2 上,用 --dim 浅色档只有 4.41:1,够不着 AA 的 4.5。 */
52
+ .ver-note{display:inline-block;margin-left:6px;vertical-align:3px;padding:2px 8px;border:1px solid var(--line);border-radius:999px;background:var(--panel2);
53
+ color:var(--ink2);font:400 11px/1.5 "Microsoft YaHei","PingFang SC",system-ui,sans-serif;letter-spacing:0;cursor:help}
50
54
  /* 等宽网格铺满一行:每格同宽、末行左对齐,不会出现右对齐造成的锯齿缺口 */
51
55
  .tabs{display:grid;grid-template-columns:repeat(auto-fit,minmax(74px,1fr));grid-auto-rows:minmax(36px,auto);gap:6px}
52
56
  .tab{display:flex;align-items:center;justify-content:center;text-align:center;padding:7px 10px;border-radius:999px;border:1px solid transparent;background:color-mix(in srgb,var(--panel) 62%,transparent);color:var(--dim);cursor:pointer;font-size:13.5px;font-family:inherit;white-space:nowrap;
@@ -105,6 +109,8 @@ body.view-flat .start{display:none}
105
109
  #mh-num input{width:124px}
106
110
  #changing{width:132px}
107
111
  .field input:focus,.field select:focus,.field textarea:focus{border-color:var(--gold);box-shadow:var(--ring);background:var(--panel)}
112
+ /* 日期格式写错:输入过程中就转红,失焦会退回最近一次有效值 */
113
+ .field input[aria-invalid="true"]{border-color:var(--bad);box-shadow:0 0 0 2px rgba(167,44,35,.12)}
108
114
  .field.check{flex-direction:row;align-items:center;gap:8px;padding-bottom:8px}
109
115
  /* 所问之事:多行文本,样式与输入框一致;整组可伸缩,空间不足时自动换行独占一行 */
110
116
  .field textarea{background:var(--panel2);border:1px solid var(--line);color:var(--ink);border-radius:8px;padding:8px 12px;font-size:15px;font-family:inherit;line-height:1.5;outline:none;width:100%;min-width:0;resize:vertical}
@@ -247,6 +253,8 @@ h3::after{content:"";flex:1;height:1px;background:linear-gradient(90deg,var(--li
247
253
  .view-switch{flex:none}
248
254
  .view-switch .seg button{white-space:nowrap}
249
255
  .brand .seal{width:34px;height:34px;border-radius:9px;font-size:17px}
256
+ /* 窄屏标题一行都紧张,未发版小牌让位给「方法与验证」里的完整清单,不在这里挤 */
257
+ .ver-note{display:none}
250
258
  /* 一行副标题 + 省略号:窄屏下让它换行会把「分组/平铺」顶到下一排,整块头重脚轻 */
251
259
  .brand-txt{min-width:0;overflow:hidden}
252
260
  .brand-txt small{font-size:11.5px;line-height:1.5;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
@@ -354,7 +362,7 @@ h3::after{content:"";flex:1;height:1px;background:linear-gradient(90deg,var(--li
354
362
  <div class="wrap">
355
363
  <header>
356
364
  <div class="brand-row">
357
- <div class="brand"><span class="seal" aria-hidden="true">命</span><span class="brand-txt">命理排盘<span class="ver">v0.61.0</span><small>八字 · 紫微 · 六爻 · 梅花 · 六壬 · 奇门 · 合盘 · 塔罗 全部在本机计算</small></span></div>
365
+ <div class="brand"><span class="seal" aria-hidden="true">命</span><span class="brand-txt">命理排盘<span class="ver">v0.61.2</span><small>八字 · 紫微 · 六爻 · 梅花 · 六壬 · 奇门 · 合盘 · 塔罗 全部在本机计算</small></span></div>
358
366
  <div class="view-switch">
359
367
  <span class="hint">熟练了可以换回平铺</span>
360
368
  <span class="seg" role="group" aria-label="首屏样式">
@@ -440,7 +448,7 @@ h3::after{content:"";flex:1;height:1px;background:linear-gradient(90deg,var(--li
440
448
  <div class="bar">
441
449
  <!-- 初始值是「命盘类(紫微/八字)」的示例默认:出生时间必然是过去,不取当前时刻。
442
450
  切到六爻/梅花/六壬等起卦类 tab 时,web-ui.js 会自动把这两个框换成你的本机当前时刻。 -->
443
- <label class="field" id="for-date"><span id="lbl-date">出生日期(公历)</span><input id="date" type="date" value="1990-05-12"></label>
451
+ <label class="field" id="for-date"><span id="lbl-date">出生日期(公历)</span><input id="date" type="text" inputmode="numeric" autocomplete="off" placeholder="年-月-日,如 1990-05-12" value="1990-05-12"></label>
444
452
  <label class="field" id="for-time"><span id="lbl-time">出生时刻</span><input id="time" type="time" value="14:30"></label>
445
453
  <!-- 时辰未知:只在八字/人生K线出现。勾上=不排时柱、起运降到按日,结果首行固定提示。
446
454
  紫微/合盘不提供这个勾选——它们没有时支就整盘不成立,给了勾选等于诱导出一个假盘。 -->
@@ -458,7 +466,7 @@ h3::after{content:"";flex:1;height:1px;background:linear-gradient(90deg,var(--li
458
466
  <label class="field" id="for-liuyao" style="display:none">六爻<input id="lines" placeholder="9 7 8 8 7 6,空则随机"></label>
459
467
  <!-- 合盘:第二个人的出生信息;合盘方式决定用八字还是紫微两盘对照 -->
460
468
  <div class="grp" id="for-hepan" style="display:none">
461
- <label class="field">对方出生日期<input id="pdate" type="date" value="1992-08-15"></label>
469
+ <label class="field">对方出生日期<input id="pdate" type="text" inputmode="numeric" autocomplete="off" placeholder="年-月-日,如 1992-08-15" value="1992-08-15"></label>
462
470
  <label class="field">对方出生时刻<input id="ptime" type="time" value="10:00"></label>
463
471
  <label class="field">对方性别
464
472
  <select id="pgender"><option value="female">女</option><option value="male">男</option></select>
@@ -474,7 +482,7 @@ h3::after{content:"";flex:1;height:1px;background:linear-gradient(90deg,var(--li
474
482
  <input id="h3on" type="checkbox"> 三方合盘(加入第三人)
475
483
  </label>
476
484
  <div class="grp" id="for-hepan3" style="display:none">
477
- <label class="field">第三方出生日期<input id="p2date" type="date" value="1990-01-01"></label>
485
+ <label class="field">第三方出生日期<input id="p2date" type="text" inputmode="numeric" autocomplete="off" placeholder="年-月-日,如 1990-01-01" value="1990-01-01"></label>
478
486
  <label class="field">第三方出生时刻<input id="p2time" type="time" value="12:00"></label>
479
487
  <label class="field">第三方性别
480
488
  <select id="p2gender"><option value="female">女</option><option value="male">男</option></select>
@@ -595,7 +603,7 @@ h3::after{content:"";flex:1;height:1px;background:linear-gradient(90deg,var(--li
595
603
  <label class="field" id="fs-advise2" style="display:none">朝向<input id="facing" placeholder="如 午 / 巽" value="午"></label>
596
604
  <label class="field" id="fs-period" style="display:none">运(1–9)<input id="fsperiod" type="number" min="1" max="9" value="9"></label>
597
605
  <label class="field" id="fs-offset" style="display:none">兼向度数<input id="fsoffset" type="number" min="-7.5" max="7.5" step="0.5" placeholder="0 = 正向;±4.5 以上用替卦"></label>
598
- <label class="field" id="fs-date" style="display:none">飞星日期<input id="fsdate" type="date"></label>
606
+ <label class="field" id="fs-date" style="display:none">飞星日期<input id="fsdate" type="text" inputmode="numeric" autocomplete="off" placeholder="年-月-日,如 2026-11-03"></label>
599
607
  <label class="field" id="fs-time" style="display:none">时刻<input id="fstime" type="time" value="12:00"></label>
600
608
  </div>
601
609
  <!-- 人格问卷:4 题一页、1–5 打分。题目与 1–5 的说明由 web-ui 从引擎取,
@@ -645,7 +653,8 @@ h3::after{content:"";flex:1;height:1px;background:linear-gradient(90deg,var(--li
645
653
  <h3>对拍:把典籍当期望值</h3>
646
654
  <ul><li>六爻:京房八宫卦序与世应,64 卦全表;旬空六旬表</li><li>六壬:日干寄宫、中气换将;紫微:纳音定五行局、紫微天府相照</li><li>卦例:《增删卜易》9 条 +《卜筮正宗》128 条(93 成对 + 35 静卦),核本变卦 / 动爻数 / 卦宫 / 缺爻 / 旬空</li><li>奇门:20 个时辰 × 9 宫,核四柱 / 局数 / 值符 / 值使 / 星门神 / 天盘干</li></ul>
647
655
  <h3>工程验证</h3>
648
- <ul><li>测试:152 个测试文件 / 7518 条断言;七道闸(回归 · 断言基线 · 测试可失败自检 · HTML 注入面 · 工程约定 · 产物宿主形态 · 随机扫射)</li><li>可复现:seed / asOf 显式;同一输入同一输出</li><li>口径:晚子时、真太阳时、奇门三流派、闰月归月都可切换,并有字段级差异对照</li></ul>
656
+ <ul><li>测试:154 个测试文件 / 7637 条断言;七道闸(回归 · 断言基线 · 测试可失败自检 · HTML 注入面 · 工程约定 · 产物宿主形态 · 随机扫射)</li><li>可复现:seed / asOf 显式;同一输入同一输出</li><li>口径:晚子时、真太阳时、奇门三流派、闰月归月都可切换,并有字段级差异对照</li></ul>
657
+
649
658
  <h3>已知边界(不猜)</h3>
650
659
  <ul><li>六爻应期(具体哪天应验)不做;卦例只录动爻个数,不录爻位</li><li>财富/健康只给结构判断与档位,不给金额区间、不作诊断</li><li>紫微飞星已做「单一支化逐宫追到成环」与「三方四正 16 条按落宫归堆」,但都未叠大限流年四化、不折吉凶;八字交运日已能算到日/时,但未与第三方排盘案例批量对拍</li></ul>
651
660
  <p class="note">内容用于传统文化学习与自我探索,不构成医疗、投资、法律或人事决策依据。</p>
@@ -26311,7 +26320,7 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
26311
26320
  })();
26312
26321
 
26313
26322
  window.Engine = __mods.index;
26314
- window.__MP_VER = "0.61.0";
26323
+ window.__MP_VER = "0.61.2";
26315
26324
  </script>
26316
26325
  <script>
26317
26326
  // 两个 UI 共用的表单规则:宿主面板(src/app.js)与独立页(src/web-ui.js)。
@@ -29796,6 +29805,9 @@ window.__MP_VER = "0.61.0";
29796
29805
  hourUnknown: hu,
29797
29806
  gender: String(src.gender || ''),
29798
29807
  address: String(src.address || ''),
29808
+ // 两个开关也换一张盘:不随档案存,载入时就会跟着表单此刻的勾选排
29809
+ trueSolar: src.trueSolar !== false,
29810
+ lateZi: src.lateZi !== false,
29799
29811
  pinned: false,
29800
29812
  createdAt: Date.now(),
29801
29813
  lastUsedAt: 0,
@@ -30266,6 +30278,86 @@ document.head.appendChild(Object.assign(document.createElement('style'), { textC
30266
30278
 
30267
30279
  const el = (id) => document.getElementById(id);
30268
30280
  const pad = (n) => String(n).padStart(2, '0');
30281
+
30282
+ // ── 公历日期输入:固定「年-月-日」 ─────────────────────────────
30283
+ // 原生 date 控件的顺序跟随浏览器区域,容易出现月/日/年或日/月/年。
30284
+ // 改成文本输入 + 数字掩码:显示始终是 YYYY-MM-DD,也认 1990/5/12、1990年5月12日。
30285
+ function isoDateOk(s) {
30286
+ const ymd = /^(\d{4})-(\d{2})-(\d{2})$/.exec(String(s || ''));
30287
+ if (!ymd) return false;
30288
+ const y = Number(ymd[1]);
30289
+ const mo = Number(ymd[2]);
30290
+ const d = Number(ymd[3]);
30291
+ if (mo < 1 || mo > 12 || d < 1) return false;
30292
+ const leap = (y % 4 === 0 && y % 100 !== 0) || y % 400 === 0;
30293
+ const days = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
30294
+ return d <= days[mo - 1];
30295
+ }
30296
+ function normalizeDateText(raw) {
30297
+ const ymd = /^(\d{4})\s*[年./-]\s*(\d{1,2})\s*[月./-]\s*(\d{1,2})\s*日?$/.exec(String(raw || '').trim());
30298
+ if (!ymd) return '';
30299
+ const iso = `${ymd[1]}-${pad(ymd[2])}-${pad(ymd[3])}`;
30300
+ return isoDateOk(iso) ? iso : '';
30301
+ }
30302
+ function maskDateText(raw) {
30303
+ const s = String(raw || '').trim();
30304
+ // 连字符日期在输入过程中原样保留(含只打出「1990-5-」的中间态):刚敲下「1」
30305
+ // 就补成「01」,再敲「2」会变成 012 被截掉。等失焦时再统一补零;
30306
+ // 斜杠/中文/点号视为粘贴,直接归一。
30307
+ if (/^\d{0,4}(-\d{0,2}){0,2}$/.test(s)) return s;
30308
+ if (/[年月日/.]/.test(s)) {
30309
+ const full = normalizeDateText(s);
30310
+ if (full) return full;
30311
+ }
30312
+ const digits = s.replace(/\D/g, '').slice(0, 8);
30313
+ if (digits.length <= 4) return digits;
30314
+ if (digits.length <= 6) return `${digits.slice(0, 4)}-${digits.slice(4)}`;
30315
+ return `${digits.slice(0, 4)}-${digits.slice(4, 6)}-${digits.slice(6)}`;
30316
+ }
30317
+ /** 程序填值(示例 / 切 tab):同步掩码与「最近一次有效值」 */
30318
+ function setDateField(id, iso) {
30319
+ const input = el(id);
30320
+ if (!input) return;
30321
+ const v = normalizeDateText(iso) || '';
30322
+ input.value = v;
30323
+ input.dataset.last = v;
30324
+ input.setAttribute('aria-invalid', 'false');
30325
+ }
30326
+ function bindDateInput(id) {
30327
+ const input = el(id);
30328
+ if (!input) return;
30329
+ const initial = normalizeDateText(input.value);
30330
+ if (initial) input.value = initial;
30331
+ input.dataset.last = initial || '';
30332
+ input.setAttribute('type', 'text');
30333
+ input.setAttribute('inputmode', 'numeric');
30334
+ input.setAttribute('autocomplete', 'off');
30335
+ input.setAttribute('placeholder', '年-月-日,如 1990-05-12');
30336
+ input.addEventListener('input', () => {
30337
+ const next = maskDateText(input.value);
30338
+ if (input.value !== next) input.value = next;
30339
+ const iso = normalizeDateText(next);
30340
+ if (iso) input.dataset.last = iso;
30341
+ input.setAttribute('aria-invalid', iso || !next.trim() ? 'false' : 'true');
30342
+ });
30343
+ input.addEventListener('blur', () => {
30344
+ const raw = input.value.trim();
30345
+ if (!raw) { input.setAttribute('aria-invalid', 'false'); return; }
30346
+ const iso = normalizeDateText(raw);
30347
+ if (iso) {
30348
+ input.value = iso;
30349
+ input.dataset.last = iso;
30350
+ input.setAttribute('aria-invalid', 'false');
30351
+ return;
30352
+ }
30353
+ // 写错了就退回最近一次有效值,别把一个非法日期留在框里等着排盘
30354
+ input.value = input.dataset.last || '';
30355
+ input.setAttribute('aria-invalid', 'false');
30356
+ });
30357
+ }
30358
+ function bindDateInputs() {
30359
+ ['date', 'pdate', 'p2date', 'fsdate'].forEach(bindDateInput);
30360
+ }
30269
30361
  // 首屏耗时只在本机显示,不上报
30270
30362
  const mpNow = () => (typeof performance !== 'undefined' && performance.now ? performance.now() : Date.now());
30271
30363
 
@@ -30375,10 +30467,16 @@ document.head.appendChild(Object.assign(document.createElement('style'), { textC
30375
30467
  const puts = [['date', p.date], ['time', p.hourUnknown ? '' : p.time], ['gender', p.gender], ['addr', p.address]];
30376
30468
  for (const pair of puts) {
30377
30469
  const node = el(pair[0]);
30378
- if (node && pair[1]) node.value = pair[1];
30470
+ // 空出生地也要写回去:留着上一个地址,这张盘的真太阳时就会按别人的地方算
30471
+ if (node) node.value = pair[1] || '';
30379
30472
  }
30380
30473
  const hu = el('hourunknown');
30381
30474
  if (hu) hu.checked = !!p.hourUnknown;
30475
+ // 旧档案没存开关:按默认(都开)处理,别沿用表单此刻的勾选
30476
+ const tsBox = el('ts');
30477
+ if (tsBox) tsBox.checked = p.trueSolar !== false;
30478
+ const lzBox = el('latezi');
30479
+ if (lzBox) lzBox.checked = p.lateZi !== false;
30382
30480
  // 把这次回填当成「用户填的」:否则切走再切回出生类体系时会被上一次的自动值顶掉
30383
30481
  onManualEdit();
30384
30482
  run();
@@ -30391,9 +30489,14 @@ document.head.appendChild(Object.assign(document.createElement('style'), { textC
30391
30489
  const hu = el('hourunknown');
30392
30490
  const box = el('profwrap');
30393
30491
  const nameEl = box ? box.querySelector('.mp-prof-name') : null;
30492
+ const tsBox = el('ts');
30493
+ const lzBox = el('latezi');
30394
30494
  return PP.profFromForm({
30395
30495
  kind: tab, date: v('date'), time: v('time'), gender: v('gender'), address: v('addr'),
30396
30496
  hourUnknown: !!(hu && hu.checked),
30497
+ // 两个开关决定时柱:随档案存下来,载入才能回到当初那张盘
30498
+ trueSolar: !(tsBox && !tsBox.checked),
30499
+ lateZi: !(lzBox && !lzBox.checked),
30397
30500
  }, nameEl ? nameEl.value : '', profEditing || undefined);
30398
30501
  }
30399
30502
 
@@ -30424,7 +30527,8 @@ document.head.appendChild(Object.assign(document.createElement('style'), { textC
30424
30527
  const puts = [['date', p.date], ['time', p.hourUnknown ? '' : p.time], ['gender', p.gender], ['addr', p.address]];
30425
30528
  for (const pair of puts) {
30426
30529
  const node = el(pair[0]);
30427
- if (node && pair[1]) node.value = pair[1];
30530
+ // 同上:这张档案没存地址就把框清空,别让「更新」把别人的地址存进来
30531
+ if (node) node.value = pair[1] || '';
30428
30532
  }
30429
30533
  const hu = el('hourunknown');
30430
30534
  if (hu) hu.checked = !!p.hourUnknown;
@@ -30562,7 +30666,7 @@ document.head.appendChild(Object.assign(document.createElement('style'), { textC
30562
30666
 
30563
30667
  /** 程序填值:写入 input 并记下「这是程序填的」,不置手动标记 */
30564
30668
  function fillDateTime(v) {
30565
- el('date').value = v.date;
30669
+ setDateField('date', v.date);
30566
30670
  el('time').value = v.time;
30567
30671
  autoDate = v.date;
30568
30672
  autoTime = v.time;
@@ -30576,9 +30680,10 @@ document.head.appendChild(Object.assign(document.createElement('style'), { textC
30576
30680
  fillDateTime(manual[k] || defaultFor(k));
30577
30681
  }
30578
30682
 
30579
- /** 当前 tab 是否真的用到日期/时刻(梅花「数字起卦」不用) */
30683
+ /** 当前 tab 是否真的用到日期/时刻(塔罗、人格不看时间;梅花「数字起卦」也不用)
30684
+ * 与 syncMhMode 里的显隐共用这一处判断:字段既然藏起来了,run() 就不能拿它拦人。 */
30580
30685
  function usesTimeInput() {
30581
- return !(tab === 'meihua' && mhMode === 'number');
30686
+ return tab !== 'tarot' && tab !== 'mbti' && !(tab === 'meihua' && mhMode === 'number');
30582
30687
  }
30583
30688
 
30584
30689
  /** 用户手动改了日期或时刻 */
@@ -30865,11 +30970,11 @@ document.head.appendChild(Object.assign(document.createElement('style'), { textC
30865
30970
  day: Number(last.date.slice(8, 10)),
30866
30971
  minute: Number(last.time.slice(3, 5)) || 0,
30867
30972
  },
30868
- gender: el('gender').value,
30973
+ gender: last.gender,
30869
30974
  events,
30870
- lateZi: el('latezi') ? el('latezi').checked : true,
30871
- trueSolar: el('ts') ? el('ts').checked : true,
30872
- address: el('addr') && el('addr').value.trim() ? el('addr').value.trim() : undefined,
30975
+ lateZi: last.lateZi !== false,
30976
+ trueSolar: last.trueSolar !== false,
30977
+ address: last.address || undefined,
30873
30978
  }) : { error: '当前页面未加载生时校正模块' };
30874
30979
  } catch (e) { rectOut = { error: e && e.message ? e.message : String(e) }; }
30875
30980
  const out = el('rectout');
@@ -30939,6 +31044,21 @@ document.head.appendChild(Object.assign(document.createElement('style'), { textC
30939
31044
  const myRun = ++runSeq;
30940
31045
  const startTab = tab;
30941
31046
  const date = el('date').value;
31047
+ if (usesTimeInput() && !isoDateOk(date)) {
31048
+ last = null;
31049
+ el('out').innerHTML = '<div class="err">请填写公历日期,按「年-月-日」输入,例如 1990-05-12。</div>';
31050
+ return;
31051
+ }
31052
+ if (tab === 'hepan' && !isoDateOk(el('pdate').value)) {
31053
+ last = null;
31054
+ el('out').innerHTML = '<div class="err">请填写对方的公历出生日期,按「年-月-日」输入,例如 1992-08-15。</div>';
31055
+ return;
31056
+ }
31057
+ if (tab === 'hepan' && el('h3on') && el('h3on').checked && !isoDateOk(el('p2date').value)) {
31058
+ last = null;
31059
+ el('out').innerHTML = '<div class="err">请填写第三方的公历出生日期,按「年-月-日」输入,例如 1990-01-01。</div>';
31060
+ return;
31061
+ }
30942
31062
  // 勾了「时辰未知」就不读钟点:读出来的只是占位值,用它排出来的时柱是编的
30943
31063
  const hourUnknown = !!(el('hourunknown') && el('hourunknown').checked) && (tab === 'bazi' || tab === 'trend');
30944
31064
  const time = hourUnknown ? '12:00' : el('time').value;
@@ -30958,7 +31078,8 @@ document.head.appendChild(Object.assign(document.createElement('style'), { textC
30958
31078
  // 推导链只覆盖六爻 / 八字 / 紫微
30959
31079
  args.trace = tab === 'liuyao' || tab === 'bazi' || tab === 'ziwei' || tab === 'qimen' || tab === 'daliuren';
30960
31080
  if (tab !== 'tarot') args.datetime = `${date}T${time}`;
30961
- if (tab === 'bazi' || tab === 'ziwei' || tab === 'hepan') {
31081
+ // 人生K线也是出生盘:出生地决定真太阳时,落进时柱后会改变整条运程
31082
+ if (tab === 'bazi' || tab === 'ziwei' || tab === 'hepan' || tab === 'trend') {
30962
31083
  // 合盘主方同样要性别与出生地:引擎里合盘的性别是必填,地址决定真太阳时
30963
31084
  args.gender = el('gender').value;
30964
31085
  if (hourUnknown) args.hourUnknown = true;
@@ -31040,8 +31161,9 @@ document.head.appendChild(Object.assign(document.createElement('style'), { textC
31040
31161
  args.num3 = Number(el('num3').value) || undefined;
31041
31162
  } else {
31042
31163
  args.date = date;
31043
- const hh = Number(String(time).split(':')[0]);
31044
- args.hour = Number.isFinite(hh) ? hh : 12;
31164
+ // Number('') 是 0:时刻框被清空时不能悄悄按子时起课,退回正午
31165
+ const hm = /^(\d{1,2}):/.exec(String(time));
31166
+ args.hour = hm ? Number(hm[1]) : 12;
31045
31167
  }
31046
31168
  }
31047
31169
  if (tab === 'convert') {
@@ -31060,7 +31182,8 @@ document.head.appendChild(Object.assign(document.createElement('style'), { textC
31060
31182
  if (nums.length) args.answers = nums;
31061
31183
  }
31062
31184
  if (tab === 'trend' && hourUnknown) args.hourUnknown = true;
31063
- if (tab === 'trend') { args.gender = el('gender').value; args.toAge = Number(el('toage').value) || 60; args.trueSolar = el('ts').checked; args.lateZi = el('latezi') ? el('latezi').checked : true; }
31185
+ // 性别 / 出生地 / 两个开关已在上面的出生盘分支里给齐,这里只补 K 线自己的参数
31186
+ if (tab === 'trend') args.toAge = Number(el('toage').value) || 60;
31064
31187
  if (tab === 'fengshui') {
31065
31188
  args.action = fsMode;
31066
31189
  if (fsMode === 'natal') { args.gender = el('gender').value; }
@@ -31073,6 +31196,11 @@ document.head.appendChild(Object.assign(document.createElement('style'), { textC
31073
31196
  const off = el('fsoffset') ? el('fsoffset').value.trim() : '';
31074
31197
  if (off !== '') args.sittingOffset = Number(off);
31075
31198
  const fd = el('fsdate') ? el('fsdate').value.trim() : '';
31199
+ if (fd && !isoDateOk(fd)) {
31200
+ last = null;
31201
+ el('out').innerHTML = '<div class="err">飞星日期请按「年-月-日」填写,例如 2026-11-03。</div>';
31202
+ return;
31203
+ }
31076
31204
  if (fd) args.fortuneDate = fd;
31077
31205
  const ft = el('fstime') ? el('fstime').value.trim() : '';
31078
31206
  if (ft) args.fortuneTime = ft;
@@ -31141,7 +31269,16 @@ document.head.appendChild(Object.assign(document.createElement('style'), { textC
31141
31269
  : tab === 'mbti' ? window.__PP.mbtiHTML(r)
31142
31270
  : tab === 'fengshui' ? window.__PP.fengshuiHTML(r)
31143
31271
  : window.__PP.quickHTML(r));
31144
- last = { r, html, tab, date, time, timed, trend, caliber, shadowDiff };
31272
+ // 新盘就是新的一轮:上一次的自评、贴合度、候选时辰不能跟着贴到这张盘上
31273
+ // (它们只在模块里存着,重绘不会自己清)。
31274
+ pastMarks = {}; pastFit = null; rectOut = null;
31275
+ // 生时校正要按「这张盘自己的输入」反推:档案/表单在排完之后还能改,
31276
+ // 只读 last.date 而性别、出生地读表单,候选时辰就会贴到另一张盘上
31277
+ last = { r, html, tab, date, time, timed, trend, caliber, shadowDiff,
31278
+ gender: args.gender || el('gender').value,
31279
+ address: typeof args.address === 'string' ? args.address : '',
31280
+ trueSolar: typeof args.trueSolar === 'boolean' ? args.trueSolar : true,
31281
+ lateZi: typeof args.lateZi === 'boolean' ? args.lateZi : true };
31145
31282
  const paintStart = mpNow();
31146
31283
  paint();
31147
31284
  // 渲染=组 HTML + 写入 DOM 的同步时间;浏览器真正绘制在下一帧,这里不假装量到
@@ -31264,7 +31401,7 @@ document.head.appendChild(Object.assign(document.createElement('style'), { textC
31264
31401
  const mhNum = tab === 'meihua' && mhMode === 'number';
31265
31402
  el('mh-num').style.display = mhNum ? '' : 'none';
31266
31403
  // 梅花「数字起卦」不参与时间计算,日期/时刻在此模式下隐藏,避免误导;人格问卷同样与日期无关
31267
- const showDT = tab !== 'tarot' && tab !== 'mbti' && !mhNum;
31404
+ const showDT = usesTimeInput();
31268
31405
  el('for-date').style.display = showDT ? '' : 'none';
31269
31406
  // 「时辰未知」只对八字与人生K线开放:紫微/合盘没有时支就没有盘,不提供这个勾选
31270
31407
  const canUnknown = tab === 'bazi' || tab === 'trend';
@@ -31464,6 +31601,7 @@ document.head.appendChild(Object.assign(document.createElement('style'), { textC
31464
31601
  }
31465
31602
 
31466
31603
  window.addEventListener('DOMContentLoaded', () => {
31604
+ bindDateInputs();
31467
31605
  // 两套入口(平铺 tab 与分组卡片)走同一条激活路径,避免行为分叉
31468
31606
  document.querySelectorAll('.tab, .tool').forEach((b) => b.addEventListener('click', () => activate(b.dataset.tab)));
31469
31607
  document.querySelectorAll('.view-switch [data-view]').forEach((b) => b.addEventListener('click', () => {
@@ -31523,7 +31661,7 @@ document.head.appendChild(Object.assign(document.createElement('style'), { textC
31523
31661
  // 看示例:填固定示例盘并切到八字,走与真实排盘完全相同的渲染路径
31524
31662
  const sampleBtn = el('sample');
31525
31663
  if (sampleBtn) sampleBtn.addEventListener('click', () => {
31526
- el('date').value = SAMPLE.date;
31664
+ setDateField('date', SAMPLE.date);
31527
31665
  el('time').value = SAMPLE.time;
31528
31666
  el('gender').value = 'male';
31529
31667
  el('addr').value = '河北石家庄';