dsh-metaphysics 0.17.1 → 0.18.1

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/client.js CHANGED
@@ -746,7 +746,7 @@ function resolveAddress(input) {
746
746
 
747
747
  const DETAIL = /(区|县|镇|乡|村|小区|街道|路|号|巷|弄|组|队)/;
748
748
 
749
- const USER_AGENT = 'dsh-metaphysics/0.17.1 (+https://github.com/xuanfu-web/dsh-metaphysics)';
749
+ const USER_AGENT = 'dsh-metaphysics/0.18.1 (+https://github.com/xuanfu-web/dsh-metaphysics)';
750
750
 
751
751
  async function onlineLookup(text, { timeoutMs = 2500 } = {}) {
752
752
  if (typeof fetch !== 'function') return null;
@@ -821,6 +821,63 @@ function isSeed(v) {
821
821
  return { mulberry32, randomOf, isSeed };
822
822
  })();
823
823
 
824
+ __mods.dst = (function () {
825
+ const { daysInMonth } = __mods.validate;
826
+
827
+ const DST_SPANS = {
828
+ 1986: ['1986-05-04', '1986-09-14'],
829
+ 1987: ['1987-04-12', '1987-09-13'],
830
+ 1988: ['1988-04-10', '1988-09-11'],
831
+ 1989: ['1989-04-16', '1989-09-17'],
832
+ 1990: ['1990-04-15', '1990-09-16'],
833
+ 1991: ['1991-04-14', '1991-09-15'],
834
+ };
835
+
836
+ const ORD = (y, m, d) => y * 10000 + m * 100 + d;
837
+
838
+ function inDstSpan(t) {
839
+ const span = DST_SPANS[t.year];
840
+ if (!span) return false;
841
+ const [sy, sm, sd] = span[0].split('-').map(Number);
842
+ const [ey, em, ed] = span[1].split('-').map(Number);
843
+ const n = ORD(t.year, t.month, t.day);
844
+ if (n < ORD(sy, sm, sd) || n > ORD(ey, em, ed)) return false;
845
+ const minutes = t.hour * 60 + (t.minute || 0);
846
+ if (n === ORD(sy, sm, sd) && minutes < 120) return false;
847
+ if (n === ORD(ey, em, ed) && minutes >= 120) return false;
848
+ return true;
849
+ }
850
+
851
+ function applyDst(t) {
852
+ const minute = t.minute || 0;
853
+ if (!inDstSpan(t)) return { year: t.year, month: t.month, day: t.day, hour: t.hour, minute, applied: false, note: null };
854
+ let { year, month, day, hour } = t;
855
+ hour -= 1;
856
+ if (hour < 0) {
857
+ hour += 24;
858
+ day -= 1;
859
+ if (day < 1) {
860
+ month -= 1;
861
+ if (month < 1) { month = 12; year -= 1; }
862
+ day = daysInMonth(year, month);
863
+ }
864
+ }
865
+ return {
866
+ year, month, day, hour, minute,
867
+ applied: true,
868
+ note: `检出自 ${DST_SPANS[t.year][0]} 至 ${DST_SPANS[t.year][1]} 的中国夏令时:出生钟面时间已减 1 小时校正`,
869
+ };
870
+ }
871
+
872
+ function withDstNote(notes, dst) {
873
+ const out = Array.isArray(notes) ? notes.slice() : [];
874
+ if (dst && dst.applied && dst.note) out.push(dst.note);
875
+ return out;
876
+ }
877
+
878
+ return { DST_SPANS, inDstSpan, applyDst, withDstNote };
879
+ })();
880
+
824
881
  __mods.shensha = (function () {
825
882
  const { BRANCHES } = __mods.calendar;
826
883
 
@@ -1532,6 +1589,7 @@ const { STEMS, BRANCHES, TERM_NAMES, ganzhiName, dayGanzhiIndex, jdToParts, part
1532
1589
  const { resolveAddress } = __mods.geo;
1533
1590
  const { computeBaziShensha } = __mods.shensha;
1534
1591
  const { assertCivil } = __mods.validate;
1592
+ const { applyDst } = __mods.dst;
1535
1593
  const { nayinOf } = __mods.nayin;
1536
1594
  const { SANHE } = __mods.sanhe;
1537
1595
  const { analyzeBaziFrame, scoreLuck } = __mods.geju;
@@ -1716,9 +1774,13 @@ function applyTrueSolar(jd, longitude) {
1716
1774
 
1717
1775
  function buildBazi(input) {
1718
1776
  assertCivil(input);
1719
- const { year, month, day, hour = 0, minute = 0, gender = 'male' } = input;
1777
+ let { year, month, day, hour = 0, minute = 0, gender = 'male' } = input;
1720
1778
  const lateZi = input.lateZi !== false;
1721
1779
  const useTrueSolar = input.trueSolar !== false;
1780
+ const dst = input.dst === false ? { applied: false, note: null } : applyDst({ year, month, day, hour, minute });
1781
+ if (dst.applied) {
1782
+ year = dst.year; month = dst.month; day = dst.day; hour = dst.hour; minute = dst.minute;
1783
+ }
1722
1784
 
1723
1785
  const civilJd = partsToJd({ year, month, day, hour, minute });
1724
1786
  const geo = input.geo || (input.address ? resolveAddress(input.address) : null);
@@ -1952,12 +2014,13 @@ function buildBazi(input) {
1952
2014
  geo,
1953
2015
  lateZi,
1954
2016
  trueSolar: useTrueSolar,
2017
+ dst: { applied: !!dst.applied, note: dst.note || null },
1955
2018
  },
1956
2019
  solarTime: {
1957
2020
  civilText: `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')} ${String(hour).padStart(2, '0')}:${String(minute).padStart(2, '0')}`,
1958
2021
  trueSolarText: formatJd(jd),
1959
2022
  offsetMinutes: solar.offsetMinutes,
1960
- note: solar.note,
2023
+ note: dst.applied ? `${dst.note};${solar.note}` : solar.note,
1961
2024
  lunarYearForPillar: solarYear,
1962
2025
  },
1963
2026
  jieqi: {
@@ -2216,6 +2279,7 @@ __mods.ziwei = (function () {
2216
2279
  const { STEMS, BRANCHES, ganzhiName, sexagenaryIndex, jdToParts, partsToJd, formatJd } = __mods.calendar;
2217
2280
  const { solarToLunar } = __mods.lunar;
2218
2281
  const { applyTrueSolar } = __mods.bazi;
2282
+ const { applyDst } = __mods.dst;
2219
2283
  const { assertCivil } = __mods.validate;
2220
2284
  const { nayinOf } = __mods.nayin;
2221
2285
  const { resolveAddress } = __mods.geo;
@@ -2390,7 +2454,9 @@ function buildZiwei(input) {
2390
2454
  const longitude = typeof input.longitude === 'number'
2391
2455
  ? input.longitude
2392
2456
  : (geo && typeof geo.lon === 'number' ? geo.lon : undefined);
2393
- const civilJd = partsToJd({ year: cy, month: cm, day: cd, hour: ch, minute: cmin });
2457
+ const civilJdRaw = partsToJd({ year: cy, month: cm, day: cd, hour: ch, minute: cmin });
2458
+ const dst = input.dst === false ? { applied: false, note: null } : applyDst({ year: cy, month: cm, day: cd, hour: ch, minute: cmin });
2459
+ const civilJd = dst.applied ? partsToJd({ year: dst.year, month: dst.month, day: dst.day, hour: dst.hour, minute: dst.minute }) : civilJdRaw;
2394
2460
  const solar = useTrueSolar
2395
2461
  ? applyTrueSolar(civilJd, longitude)
2396
2462
  : { offsetMinutes: 0, jd: civilJd, note: '未启用真太阳时' };
@@ -2627,12 +2693,13 @@ function buildZiwei(input) {
2627
2693
  address: input.address || null,
2628
2694
  geo,
2629
2695
  trueSolar: useTrueSolar,
2696
+ dst: { applied: !!dst.applied, note: dst.note || null },
2630
2697
  },
2631
2698
  solarTime: {
2632
2699
  civilText: `${cy}-${String(cm).padStart(2, '0')}-${String(cd).padStart(2, '0')} ${String(ch).padStart(2, '0')}:${String(cmin).padStart(2, '0')}`,
2633
2700
  trueSolarText: formatJd(solar.jd),
2634
2701
  offsetMinutes: solar.offsetMinutes,
2635
- note: solar.note,
2702
+ note: dst.applied ? `${dst.note};${solar.note}` : solar.note,
2636
2703
  },
2637
2704
  lunar: {
2638
2705
  year: lunarRaw.lunarYear,
@@ -3792,7 +3859,7 @@ return { ZHI, GAN, TIAN_JIANG, JIANG_FULL, JIEQI_YUE_JIANG, GAN_JI_GONG, calcShe
3792
3859
  })();
3793
3860
 
3794
3861
  __mods.hepan = (function () {
3795
- const { buildBazi } = __mods.bazi;
3862
+ const { buildBazi, tenGod } = __mods.bazi;
3796
3863
  const { STEMS, BRANCHES } = __mods.calendar;
3797
3864
  const { assertCivil } = __mods.validate;
3798
3865
  const { SANHE_GROUPS } = __mods.sanhe;
@@ -3835,6 +3902,20 @@ function wxToMe(other, me) {
3835
3902
  return { name: '克我', score: -12, text: '对方克我,压力感偏重,宜把话说开' };
3836
3903
  }
3837
3904
 
3905
+ function yearBranchRelation(a, b) {
3906
+ if (a === b) return { name: '同属相(自刑之一)', score: -3, text: '属相相同,传统称「自刑」,容易互相较劲、谁也不让谁' };
3907
+ if (pairIn(LIU_HE, a, b)) return { name: '六合', score: 3, text: '属相六合,初印象与长辈缘通常不错' };
3908
+ if (SANHE_GROUPS.some((g) => g.branches.includes(a) && g.branches.includes(b))) {
3909
+ return { name: '三合', score: 3, text: '属相三合,容易有共同话题与共同朋友' };
3910
+ }
3911
+ if (pairIn(CHONG, a, b)) return { name: '六冲', score: -3, text: '属相六冲,节奏与立场容易相反,重大决定前宜多对一遍' };
3912
+ if (pairIn(HAI, a, b)) return { name: '相害', score: -2, text: '属相相害,容易因第三方或误会生嫌隙' };
3913
+ for (const g of XING) {
3914
+ if (g.includes(a) && g.includes(b)) return { name: '相刑', score: -2, text: '属相相刑,摩擦多起于相处细节' };
3915
+ }
3916
+ return { name: '无直接关系', score: 0, text: '属相之间没有合冲刑害,表层影响小,以日柱为主' };
3917
+ }
3918
+
3838
3919
  function buildHepan(input) {
3839
3920
  assertCivil(input && input.a, 'a');
3840
3921
  assertCivil(input && input.b, 'b');
@@ -3885,6 +3966,68 @@ function buildHepan(input) {
3885
3966
  const pct = Math.max(0, Math.min(100, Math.round(((raw + 27) / 83) * 100)));
3886
3967
  const level = pct >= 75 ? '契合度较高' : pct >= 55 ? '中等偏顺' : pct >= 40 ? '需要磨合' : '差异较大';
3887
3968
 
3969
+ const aYear = A.pillars.year;
3970
+ const bYear = B.pillars.year;
3971
+ const yearRel = yearBranchRelation(aYear.branch, bYear.branch);
3972
+ const HE5 = [['甲', '己'], ['乙', '庚'], ['丙', '辛'], ['丁', '壬'], ['戊', '癸']];
3973
+ const he5 = HE5.some((p) => p.includes(aStem) && p.includes(bStem) && aStem !== bStem);
3974
+ const godA = tenGod(aStem, bStem);
3975
+ const godB = tenGod(bStem, aStem);
3976
+ const A_BR = ['year', 'month', 'day', 'hour'].map((k) => A.pillars[k].branch);
3977
+ const B_BR = ['year', 'month', 'day', 'hour'].map((k) => B.pillars[k].branch);
3978
+ const cross = [];
3979
+ for (const x of A_BR) {
3980
+ for (const y of B_BR) {
3981
+ if (pairIn(LIU_HE, x, y)) cross.push({ a: x, b: y, name: '六合', score: 1, text: '跨盘六合,易有自然的亲近与默契' });
3982
+ else if (SANHE_GROUPS.some((g) => g.branches.includes(x) && g.branches.includes(y))) {
3983
+ cross.push({ a: x, b: y, name: '半合', score: 1, text: '跨盘半合,某些事上容易一拍即合' });
3984
+ } else if (pairIn(CHONG, x, y)) cross.push({ a: x, b: y, name: '相冲', score: -1, text: '跨盘相冲,某些领域节奏相反,需要分工' });
3985
+ else if (pairIn(HAI, x, y)) cross.push({ a: x, b: y, name: '相害', score: -1, text: '跨盘相害,容易在第三方或琐事上起误会' });
3986
+ }
3987
+ }
3988
+ const uniqCross = [];
3989
+ const seen = new Set();
3990
+ for (const c of cross) {
3991
+ const k = `${c.name}${[c.a, c.b].sort().join('')}`;
3992
+ if (seen.has(k)) continue;
3993
+ seen.add(k);
3994
+ uniqCross.push(c);
3995
+ }
3996
+ const nyA = aYear.nayin;
3997
+ const nyB = bYear.nayin;
3998
+ const naEl = nyA[nyA.length - 1];
3999
+ const nbEl = nyB[nyB.length - 1];
4000
+ const nayinRel = naEl === nbEl
4001
+ ? { name: '纳音比和', score: 0, text: '两人年命纳音同五行,底色相近' }
4002
+ : SHENG[naEl] === nbEl
4003
+ ? { name: '纳音相生', score: 1, text: `你的年命纳音属${naEl}生对方${nbEl},你多是先付出的一方` }
4004
+ : SHENG[nbEl] === naEl
4005
+ ? { name: '纳音相生', score: 1, text: `对方年命纳音属${nbEl}生你${naEl},你容易得到照应` }
4006
+ : KE[naEl] === nbEl
4007
+ ? { name: '纳音相克', score: -1, text: `你的年命纳音属${naEl}克对方${nbEl},主导感偏强` }
4008
+ : { name: '纳音相克', score: -1, text: `对方年命纳音属${nbEl}克你${naEl},压力感偏重` };
4009
+ const good = (yearRel.score > 0 ? 1 : 0) + (he5 ? 1 : 0) + (gong.score > 5 ? 1 : 0)
4010
+ + uniqCross.filter((c) => c.score > 0).length + (nayinRel.score > 0 ? 1 : 0);
4011
+ const bad = (yearRel.score < 0 ? 1 : 0) + (gong.score < 0 ? 1 : 0)
4012
+ + uniqCross.filter((c) => c.score < 0).length + (nayinRel.score < 0 ? 1 : 0);
4013
+ const lean = good - bad >= 3 ? '吉象偏多' : good - bad >= 1 ? '吉略多于凶' : good - bad === 0 ? '吉凶相当' : '凶象偏多';
4014
+
4015
+ const detail = {
4016
+ yearRelation: { label: yearRel.name, text: `属相:你${aYear.branch}(${aYear.ganzhi}),对方${bYear.branch}(${bYear.ganzhi})。${yearRel.text}。` },
4017
+ stems: {
4018
+ he5,
4019
+ tenGodToB: godA,
4020
+ tenGodToA: godB,
4021
+ text: he5
4022
+ ? `日干五合(${aStem}${bStem}),传统称「天干五合」,主彼此吸引强,也容易黏在一起分不开。`
4023
+ : `日干无五合:你在对方盘里是「${godA}」,对方在你盘里是「${godB}」。`,
4024
+ },
4025
+ crossBranches: uniqCross.slice(0, 8),
4026
+ nayin: { a: nyA, b: nyB, label: nayinRel.name, text: nayinRel.text },
4027
+ tally: { good, bad, lean },
4028
+ framing: '属相(年支)只影响表层印象,传统合婚以日柱为主;分档只说明吉凶象多少,不作「必配/必离」断言。',
4029
+ };
4030
+
3888
4031
  const dim = (key, label, score, text, extra) => ({ key, label, score: Math.round(score), text, ...(extra || {}) });
3889
4032
 
3890
4033
  return {
@@ -3916,6 +4059,7 @@ function buildHepan(input) {
3916
4059
  summary: B.summary,
3917
4060
  },
3918
4061
  score: { total: pct, level },
4062
+ detail,
3919
4063
  dimensions: [
3920
4064
  dim('stem', '日主关系', stemScore, `你的日主 ${aStem}${aWx},对方 ${bStem}${bWx}。${aToB.text};${bToA.text}。`),
3921
4065
  dim('gong', '夫妻宫(日支)', gong.score, `${gong.name}:${gong.text}。你日支 ${aZhi},对方日支 ${bZhi}。`),
@@ -3931,6 +4075,731 @@ function buildHepan(input) {
3931
4075
  return { buildHepan };
3932
4076
  })();
3933
4077
 
4078
+ __mods.trend = (function () {
4079
+ const { buildBazi, HIDDEN_STEMS, tenGod } = __mods.bazi;
4080
+ const { ganzhiName, sexagenaryIndex, STEMS, BRANCHES } = __mods.calendar;
4081
+
4082
+ const SHISHEN_WEAK = {
4083
+ 正印: 2, 偏印: 2, 比肩: 2, 劫财: 1,
4084
+ 食神: -1, 伤官: -2, 偏财: -2, 正财: -2,
4085
+ 正官: -1, 七杀: -3,
4086
+ };
4087
+ const SHISHEN_STRONG = {
4088
+ 正印: -2, 偏印: -2, 比肩: -2, 劫财: -1,
4089
+ 食神: 1, 伤官: 1, 偏财: 2, 正财: 2,
4090
+ 正官: 2, 七杀: 1,
4091
+ };
4092
+ const SHISHEN_NEUTRAL = {
4093
+ 正印: 1, 偏印: 0, 比肩: 0, 劫财: -1,
4094
+ 食神: 1, 伤官: -2, 偏财: 1, 正财: 2,
4095
+ 正官: 2, 七杀: -2,
4096
+ };
4097
+
4098
+ const FAVOR = { 身强: 'strong', 偏强: 'strong', 中和: 'neutral', 偏弱: 'weak', 身弱: 'weak' };
4099
+ const TABLE_OF = { strong: SHISHEN_STRONG, neutral: SHISHEN_NEUTRAL, weak: SHISHEN_WEAK };
4100
+
4101
+ const LEVEL_CUTS = [0.5, 1.5, 2.5, 4];
4102
+ const BOUNDARY_EPS = 0.3;
4103
+
4104
+ const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
4105
+
4106
+ function tenGodOfZhi(dayStem, branch) {
4107
+ const main = HIDDEN_STEMS[branch] && HIDDEN_STEMS[branch][0];
4108
+ return main ? tenGod(dayStem, main) : null;
4109
+ }
4110
+
4111
+ const yearGanzhi = (year) => ganzhiName(((year - 4) % 60 + 60) % 60);
4112
+
4113
+ function extendDayun(list, direction, toAge) {
4114
+ const out = list.slice();
4115
+ const step = direction === '逆行' ? -1 : 1;
4116
+ let guard = 0;
4117
+ while (out.length && out[out.length - 1].startAge <= toAge && guard < 24) {
4118
+ const last = out[out.length - 1];
4119
+ const lastIdx = sexagenaryIndex(STEMS.indexOf(last.ganzhi[0]), BRANCHES.indexOf(last.ganzhi[1]));
4120
+ const nextIdx = (lastIdx + step + 60) % 60;
4121
+ const g = ganzhiName(nextIdx);
4122
+ out.push({ step: last.step + 1, startAge: last.startAge + 10, ganzhi: g.name, stem: g.stem, branch: g.branch, stemTenGod: last.stemTenGod });
4123
+ guard++;
4124
+ }
4125
+ return out;
4126
+ }
4127
+
4128
+ function buildTrend(input) {
4129
+ const gender = input.gender;
4130
+ if (gender !== 'male' && gender !== 'female') {
4131
+ throw new Error('人生 K 线需要性别:大运顺逆由性别决定,不可缺省');
4132
+ }
4133
+ const toAge = clamp(Number.isInteger(input.toAge) ? input.toAge : 100, 10, 100);
4134
+ const r = buildBazi({
4135
+ year: input.year,
4136
+ month: input.month,
4137
+ day: input.day,
4138
+ hour: Number.isInteger(input.hour) ? input.hour : 12,
4139
+ minute: Number.isInteger(input.minute) ? input.minute : 0,
4140
+ gender,
4141
+ lateZi: input.lateZi !== false,
4142
+ trueSolar: input.trueSolar !== false,
4143
+ dst: input.dst !== false,
4144
+ address: typeof input.address === 'string' ? input.address : undefined,
4145
+ longitude: typeof input.longitude === 'number' ? input.longitude : undefined,
4146
+ geo: input.geo && typeof input.geo === 'object' ? input.geo : undefined,
4147
+ });
4148
+
4149
+ const dayStem = r.pillars.day.stem;
4150
+ const table = TABLE_OF[FAVOR[r.strength.level] || 'neutral'];
4151
+ const rawBound = 5 * Math.max(...Object.values(table).map(Math.abs));
4152
+ const scoreOf = (god) => table[god] ?? 0;
4153
+ const boundary = LEVEL_CUTS.some((c) => Math.abs(r.strength.score - c) <= BOUNDARY_EPS);
4154
+
4155
+ const dayun = extendDayun(r.dayun.list, r.dayun.direction, toAge);
4156
+ const segments = dayun
4157
+ .map((d, i) => ({
4158
+ ganZhi: d.ganzhi,
4159
+ startAge: d.startAge,
4160
+ endAge: (dayun[i + 1] ? dayun[i + 1].startAge : d.startAge + 10) - 1,
4161
+ }))
4162
+ .filter((s) => s.startAge <= toAge);
4163
+
4164
+ const birthYear = Number(String(r.solarTime.civilText).slice(0, 4));
4165
+ const points = [];
4166
+ let prev = 50;
4167
+ for (let age = 1; age <= toAge; age++) {
4168
+ const year = birthYear + age - 1;
4169
+ const gz = yearGanzhi(year);
4170
+ const seg = segments.find((s) => age >= s.startAge && age <= s.endAge) || null;
4171
+ const godGan = tenGod(dayStem, gz.stem);
4172
+ const godZhi = tenGodOfZhi(dayStem, gz.branch);
4173
+ const dyGodGan = seg ? tenGod(dayStem, seg.ganZhi[0]) : null;
4174
+ const dyGodZhi = seg ? tenGodOfZhi(dayStem, seg.ganZhi[1]) : null;
4175
+
4176
+ const raw = scoreOf(godGan) + scoreOf(godZhi)
4177
+ + (seg ? (scoreOf(dyGodGan) + scoreOf(dyGodZhi)) * 1.5 : 0);
4178
+ const score = clamp(Math.round(((raw + rawBound) / (rawBound * 2)) * 100), 2, 99);
4179
+ points.push({
4180
+ age,
4181
+ year,
4182
+ ganZhi: gz.name,
4183
+ dayunGanZhi: seg ? seg.ganZhi : null,
4184
+ shiShenGan: godGan,
4185
+ shiShenZhi: godZhi,
4186
+ score,
4187
+ kline: {
4188
+ open: prev,
4189
+ close: score,
4190
+ high: clamp(score + 4, 0, 100),
4191
+ low: clamp(score - 4, 0, 100),
4192
+ },
4193
+ });
4194
+ prev = score;
4195
+ }
4196
+
4197
+ const sorted = points.slice().sort((a, b) => b.score - a.score);
4198
+ return {
4199
+ system: 'trend',
4200
+ meta: {
4201
+ birth: String(r.solarTime.civilText).slice(0, 10),
4202
+ gender,
4203
+ dayMaster: `${dayStem}(${r.dayMaster.yinYang}${r.dayMaster.element})`,
4204
+ strength: { level: r.strength.level, score: r.strength.score, boundary },
4205
+ toAge,
4206
+ ...(boundary
4207
+ ? {
4208
+ boundaryNote:
4209
+ `旺衰分 ${r.strength.score} 正落在换档阈值附近(阈值 ${LEVEL_CUTS.join(' / ')},容差 ${BOUNDARY_EPS}):`
4210
+ + '多一个同类干支或少一个就会换计分表,整套正负号随之翻转——因此这里不出「最好 / 最差年份」的排序结论。',
4211
+ }
4212
+ : {}),
4213
+ },
4214
+ segments,
4215
+ points,
4216
+ highlights: boundary
4217
+ ? { suppressed: '旺衰落在换档边界,年份排序不可靠,不给顺位 / 谨慎年份', best: [], worst: [] }
4218
+ : {
4219
+ best: sorted.slice(0, 3).map((p) => `${p.age}岁(${p.year},${p.ganZhi},${p.score}分)`),
4220
+ worst: sorted.slice(-3).reverse().map((p) => `${p.age}岁(${p.year},${p.ganZhi},${p.score}分)`),
4221
+ },
4222
+ sparkline: asciiSparkline(points),
4223
+ sources: [
4224
+ '运势分:流年 / 大运干支十神对日主的简化启发式计分(旺衰方向决定正负号)',
4225
+ 'K 线形态:收盘 = 当年分,开盘 = 上年分,高低各 ±4(确定性生成)',
4226
+ ],
4227
+ disclaimer: boundary
4228
+ ? '人生 K 线为简化启发式的可视化;本盘旺衰判定处于换档边界,趋势与年份排序均不可靠,仅供展示,不构成任何现实决策依据。'
4229
+ : '人生 K 线为简化启发式的可视化,趋势形态仅供娱乐参考,不构成任何现实决策依据;十年大运内每年分值不等于真实吉凶。',
4230
+ };
4231
+ }
4232
+
4233
+ function asciiSparkline(points) {
4234
+ const BLOCKS = ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'];
4235
+ const lines = [];
4236
+ const groups = [];
4237
+ for (const p of points) {
4238
+ const key = p.dayunGanZhi || '起运前';
4239
+ const last = groups[groups.length - 1];
4240
+ if (!last || last.key !== key) groups.push({ key, points: [] });
4241
+ groups[groups.length - 1].points.push(p);
4242
+ }
4243
+ for (const g of groups) {
4244
+ const bars = g.points
4245
+ .map((p) => BLOCKS[clamp(Math.floor((p.score / 100) * BLOCKS.length), 0, BLOCKS.length - 1)])
4246
+ .join('');
4247
+ lines.push(`${g.key}(${g.points[0].age}–${g.points[g.points.length - 1].age}岁):${bars}`);
4248
+ }
4249
+ return lines;
4250
+ }
4251
+
4252
+ return { buildTrend };
4253
+ })();
4254
+
4255
+ __mods.fengshui = (function () {
4256
+ const { partsToJd, termInstantJd } = __mods.calendar;
4257
+ const { applyDst } = __mods.dst;
4258
+
4259
+ const NINE_STARS = {
4260
+ 1: { name: '一白', color: '白', element: '水', star: '贪狼', lucky: true },
4261
+ 2: { name: '二黑', color: '黑', element: '土', star: '巨门', lucky: false },
4262
+ 3: { name: '三碧', color: '碧', element: '木', star: '禄存', lucky: false },
4263
+ 4: { name: '四绿', color: '绿', element: '木', star: '文曲', lucky: true },
4264
+ 5: { name: '五黄', color: '黄', element: '土', star: '廉贞', lucky: false },
4265
+ 6: { name: '六白', color: '白', element: '金', star: '武曲', lucky: true },
4266
+ 7: { name: '七赤', color: '赤', element: '金', star: '破军', lucky: false },
4267
+ 8: { name: '八白', color: '白', element: '土', star: '左辅', lucky: true },
4268
+ 9: { name: '九紫', color: '紫', element: '火', star: '右弼', lucky: true },
4269
+ };
4270
+
4271
+ const PALACE_DIRECTION = {
4272
+ 坎: '正北', 艮: '东北', 震: '正东', 巽: '东南',
4273
+ 离: '正南', 坤: '西南', 兑: '正西', 乾: '西北',
4274
+ };
4275
+
4276
+ const YOUNIAN_ORDER = ['坎', '艮', '震', '巽', '离', '坤', '兑', '乾'];
4277
+ const DAYOUTIAN = {
4278
+ 乾: ['六煞', '天医', '五鬼', '祸害', '绝命', '延年', '生气'],
4279
+ 坎: ['五鬼', '天医', '生气', '延年', '绝命', '祸害', '六煞'],
4280
+ 艮: ['六煞', '绝命', '祸害', '生气', '延年', '天医', '五鬼'],
4281
+ 震: ['延年', '生气', '祸害', '绝命', '五鬼', '天医', '六煞'],
4282
+ 巽: ['天医', '五鬼', '六煞', '祸害', '生气', '绝命', '延年'],
4283
+ 离: ['六煞', '五鬼', '绝命', '延年', '祸害', '生气', '天医'],
4284
+ 坤: ['天医', '延年', '绝命', '生气', '祸害', '五鬼', '六煞'],
4285
+ 兑: ['生气', '祸害', '延年', '绝命', '六煞', '五鬼', '天医'],
4286
+ };
4287
+
4288
+ const LUCKY_STARS = ['生气', '延年', '天医', '伏位'];
4289
+ const UNLUCKY_STARS = ['绝命', '五鬼', '六煞', '祸害'];
4290
+
4291
+ const MOUNTAINS_24 = [
4292
+ { m: '壬', gua: '坎', deg: 345 }, { m: '子', gua: '坎', deg: 0 }, { m: '癸', gua: '坎', deg: 15 },
4293
+ { m: '丑', gua: '艮', deg: 30 }, { m: '艮', gua: '艮', deg: 45 }, { m: '寅', gua: '艮', deg: 60 },
4294
+ { m: '甲', gua: '震', deg: 75 }, { m: '卯', gua: '震', deg: 90 }, { m: '乙', gua: '震', deg: 105 },
4295
+ { m: '辰', gua: '巽', deg: 120 }, { m: '巽', gua: '巽', deg: 135 }, { m: '巳', gua: '巽', deg: 150 },
4296
+ { m: '丙', gua: '离', deg: 165 }, { m: '午', gua: '离', deg: 180 }, { m: '丁', gua: '离', deg: 195 },
4297
+ { m: '未', gua: '坤', deg: 210 }, { m: '坤', gua: '坤', deg: 225 }, { m: '申', gua: '坤', deg: 240 },
4298
+ { m: '庚', gua: '兑', deg: 255 }, { m: '酉', gua: '兑', deg: 270 }, { m: '辛', gua: '兑', deg: 285 },
4299
+ { m: '戌', gua: '乾', deg: 300 }, { m: '乾', gua: '乾', deg: 315 }, { m: '亥', gua: '乾', deg: 330 },
4300
+ ];
4301
+
4302
+ const MOUNTAIN_INDEX = new Map(MOUNTAINS_24.map((x, i) => [x.m, i]));
4303
+
4304
+ function mountainInfo(name) {
4305
+ const i = MOUNTAIN_INDEX.get(name);
4306
+ if (i === undefined) {
4307
+ throw new Error(`坐向必须是 24 山之一(${MOUNTAINS_24.map((x) => x.m).join('')}),收到「${name}」——请按罗盘 24 山报坐或报向,不要自造方位词。`);
4308
+ }
4309
+ return { ...MOUNTAINS_24[i], index: i };
4310
+ }
4311
+
4312
+ function sittingFromFacing(facing) {
4313
+ const f = mountainInfo(facing);
4314
+ const i = (f.index + 12) % 24;
4315
+ return { ...MOUNTAINS_24[i], index: i };
4316
+ }
4317
+
4318
+ const guaGroup = (gua) => (['坎', '离', '震', '巽'].includes(gua) ? '东四' : '西四');
4319
+
4320
+ const STAR_LAYOUT_ROLE = {
4321
+ 生气: '大吉:最宜开门、主卧、灶口朝向',
4322
+ 延年: '大吉:宜开门、主卧(利感情与人缘)',
4323
+ 天医: '次吉:宜主卧、书房、老人房(利健康)',
4324
+ 伏位: '次吉:宜卧室、神位(主平稳)',
4325
+ 祸害: '小凶:宜厕所、储物间(宜静不宜动)',
4326
+ 六煞: '次凶:宜厕所、储物间',
4327
+ 五鬼: '大凶:宜厕所,或以灶坐压之(传统「火烧五鬼」说)',
4328
+ 绝命: '最凶:宜灶坐压之、储物,忌门床',
4329
+ };
4330
+
4331
+ const GUA_BY_NUM = ['坎', '坤', '震', '巽', '中', '乾', '兑', '艮', '离'];
4332
+
4333
+ function mingGua(birthYear, gender) {
4334
+ const tail = ((birthYear % 100) + 100) % 100;
4335
+ const century = birthYear >= 2000 ? { male: 99, female: 3 } : { male: 100, female: 4 };
4336
+ const mod9 = (n) => { const r = ((n % 9) + 9) % 9; return r === 0 ? 9 : r; };
4337
+ let num = gender === 'male' ? mod9(century.male - tail) : mod9(tail - century.female);
4338
+ if (num === 5) num = gender === 'male' ? 2 : 8;
4339
+ const gua = GUA_BY_NUM[num - 1];
4340
+ if (gua === '中') throw new Error('命卦计算异常:落中宫');
4341
+ return { num, gua, direction: PALACE_DIRECTION[gua] };
4342
+ }
4343
+
4344
+ function eightStars(gua) {
4345
+ const result = { 伏位: { palace: gua, direction: PALACE_DIRECTION[gua] } };
4346
+ const start = YOUNIAN_ORDER.indexOf(gua);
4347
+ for (let step = 1; step <= 7; step++) {
4348
+ const palace = YOUNIAN_ORDER[(start + step) % 8];
4349
+ result[DAYOUTIAN[gua][step - 1]] = { palace, direction: PALACE_DIRECTION[palace] };
4350
+ }
4351
+ return result;
4352
+ }
4353
+
4354
+ function guaYearOf(year, month, day) {
4355
+ const birthJd = partsToJd({ year, month, day, hour: 12, minute: 0 });
4356
+ return birthJd < termInstantJd(year, 2) ? year - 1 : year;
4357
+ }
4358
+
4359
+ function yearZibaiStar(year) {
4360
+ const n = (1 + (2026 - year)) % 9;
4361
+ return n <= 0 ? n + 9 : n;
4362
+ }
4363
+
4364
+ const PALACE_ORDER = ['坎', '坤', '震', '巽', '中', '乾', '兑', '艮', '离']; // 洛书数 1–9 对应宫
4365
+
4366
+ function yearlyFlying(year) {
4367
+ const center = yearZibaiStar(year);
4368
+ const palaces = [];
4369
+ for (const palace of PALACE_ORDER) {
4370
+ const k = PALACE_ORDER.indexOf(palace) + 1;
4371
+ const num = ((((k - 5 + center - 1) % 9) + 9) % 9) + 1;
4372
+ const s = NINE_STARS[num];
4373
+ palaces.push({ palace, direction: palace === '中' ? '中宫' : PALACE_DIRECTION[palace], num, name: s.name, star: s.star, element: s.element, lucky: s.lucky });
4374
+ }
4375
+ return { center, centerName: NINE_STARS[center].name, palaces };
4376
+ }
4377
+
4378
+ const pad2 = (n) => String(n).padStart(2, '0');
4379
+
4380
+ function civilDate(input) {
4381
+ const dst = input.dst === false
4382
+ ? { year: input.year, month: input.month, day: input.day, hour: 0, minute: 0, applied: false, note: null }
4383
+ : applyDst({ year: input.year, month: input.month, day: input.day, hour: 0, minute: 0 });
4384
+ return { year: dst.year, month: dst.month, day: dst.day, dst };
4385
+ }
4386
+
4387
+ const SOURCES = [
4388
+ '大游年歌(八宅通行口径)',
4389
+ '年家紫白飞星(纯公式:以 2026 丙午一白入中、逐年退一位、九年一轮;按立春换年)',
4390
+ '24 山归属与度数(八宅通行口径,每山 15°)',
4391
+ ];
4392
+
4393
+ const DISCLAIMER = '八宅与飞星为传统通行口径的本地计算,属民俗参考,不构成现场勘舆结论,仅供娱乐;实际户型以现场为准。';
4394
+
4395
+ function buildFengshui(input) {
4396
+ if (input.action === 'advise') return buildAdvise(input);
4397
+ const gender = input.gender;
4398
+ if (gender !== 'male' && gender !== 'female') {
4399
+ throw new Error('风水命卦需要性别:男女命卦算法不同,不可缺省');
4400
+ }
4401
+ if (!Number.isInteger(input.year) || !Number.isInteger(input.month) || !Number.isInteger(input.day)) {
4402
+ throw new Error('风水命卦需要完整出生年月日(公历)——只知道年份不够:命卦按立春界划分出生年');
4403
+ }
4404
+ const civil = civilDate(input);
4405
+ const guaYear = guaYearOf(civil.year, civil.month, civil.day);
4406
+ const { num, gua, direction } = mingGua(guaYear, gender);
4407
+ const stars = eightStars(gua);
4408
+ const eastFour = ['坎', '离', '震', '巽'].includes(gua);
4409
+ const fortuneYear = Number.isInteger(input.fortuneYear) ? input.fortuneYear : null;
4410
+
4411
+ return {
4412
+ system: 'fengshui',
4413
+ meta: {
4414
+ birth: `${civil.year}-${pad2(civil.month)}-${pad2(civil.day)}`,
4415
+ guaYear,
4416
+ gender: gender === 'male' ? '男' : '女',
4417
+ fortuneYear,
4418
+ ...(civil.dst.applied ? { dst: civil.dst.note } : {}),
4419
+ ...(fortuneYear === null ? { note: '未给流年:未算年紫白飞星(需要时补 fortuneYear)。' } : {}),
4420
+ scope: '八宅命卦 + 大游年八星 + 年紫白;不含玄空飞星各运盘、不含形峦与现场勘舆。',
4421
+ },
4422
+ mingGua: {
4423
+ num,
4424
+ gua,
4425
+ direction,
4426
+ group: eastFour ? '东四命' : '西四命',
4427
+ compatible: eastFour ? '宜东四宅(坐坎离震巽)' : '宜西四宅(坐乾坤艮兑)',
4428
+ },
4429
+ luckyDirections: LUCKY_STARS.map((s) => ({ star: s, ...stars[s] })),
4430
+ unluckyDirections: UNLUCKY_STARS.map((s) => ({ star: s, ...stars[s] })),
4431
+ ...(fortuneYear === null ? {} : { yearlyFlying: yearlyFlying(fortuneYear) }),
4432
+ sources: SOURCES,
4433
+ disclaimer: DISCLAIMER,
4434
+ };
4435
+ }
4436
+
4437
+ function buildAdvise(input) {
4438
+ const hasSitting = typeof input.sitting === 'string' && input.sitting.trim() !== '';
4439
+ const hasFacing = typeof input.facing === 'string' && input.facing.trim() !== '';
4440
+ if (!hasSitting && !hasFacing) {
4441
+ throw new Error('缺少坐向:sitting(坐山)或 facing(向山)至少给一个——按罗盘 24 山报坐或报向,例如「坐北朝南」对应坐子向午');
4442
+ }
4443
+ if (hasSitting && hasFacing) {
4444
+ const s = mountainInfo(input.sitting.trim());
4445
+ const f = mountainInfo(input.facing.trim());
4446
+ if ((s.index + 12) % 24 !== f.index) {
4447
+ throw new Error(`坐向不自洽:坐${s.m}(${s.deg}°)与向${f.m}(${f.deg}°)不是对宫(相差应 180°,实测 ${((f.deg - s.deg) + 360) % 360}°)——请核对罗盘读数。`);
4448
+ }
4449
+ }
4450
+ const sitting = hasSitting ? mountainInfo(input.sitting.trim()) : sittingFromFacing(input.facing.trim());
4451
+ const facing = MOUNTAINS_24[(sitting.index + 12) % 24];
4452
+ const houseGua = sitting.gua;
4453
+ const stars = eightStars(houseGua);
4454
+ const lucky = LUCKY_STARS.map((s) => stars[s]);
4455
+ const unlucky = UNLUCKY_STARS.map((s) => stars[s]);
4456
+ const dir = (x) => `${x.direction}(${x.palace}宫)`;
4457
+
4458
+ const starChart = [...LUCKY_STARS, ...UNLUCKY_STARS].map((s) => ({
4459
+ star: s,
4460
+ lucky: LUCKY_STARS.includes(s),
4461
+ ...stars[s],
4462
+ role: STAR_LAYOUT_ROLE[s],
4463
+ }));
4464
+
4465
+ const layout = {
4466
+ door: `门宜开于吉方:首选生气方 ${dir(lucky[0])},次选延年方 ${dir(lucky[1])}、天医方 ${dir(lucky[2])}。`,
4467
+ stove: `灶宜「坐凶向吉」:灶身压凶方(首选绝命方 ${dir(unlucky[0])}、五鬼方 ${dir(unlucky[1])}),灶口朝吉方。`,
4468
+ bed: `主卧与床头宜安吉方:生气方 ${dir(lucky[0])} 或延年方 ${dir(lucky[1])};床头宜向吉方。`,
4469
+ toilet: `厕所宜压凶方:祸害方 ${dir(unlucky[3])} 或六煞方 ${dir(unlucky[2])}(污去凶气,宜静常闭)。`,
4470
+ };
4471
+
4472
+ let mingMatch = null;
4473
+ const hasBirth = Number.isInteger(input.year) && Number.isInteger(input.month) && Number.isInteger(input.day)
4474
+ && (input.gender === 'male' || input.gender === 'female');
4475
+ if (hasBirth) {
4476
+ const civil = civilDate(input);
4477
+ const guaYear = guaYearOf(civil.year, civil.month, civil.day);
4478
+ const ming = mingGua(guaYear, input.gender);
4479
+ const mGroup = guaGroup(ming.gua);
4480
+ const hGroup = guaGroup(houseGua);
4481
+ const same = mGroup === hGroup;
4482
+ const mingLucky = LUCKY_STARS.map((s) => `${s}位${eightStars(ming.gua)[s].direction}`).join('、');
4483
+ mingMatch = {
4484
+ birth: `${civil.year}-${pad2(civil.month)}-${pad2(civil.day)}`,
4485
+ guaYear,
4486
+ gender: input.gender === 'male' ? '男' : '女',
4487
+ mingGua: { gua: ming.gua, direction: ming.direction, group: `${mGroup}命` },
4488
+ houseGua: { gua: houseGua, group: `${hGroup}宅` },
4489
+ sameGroup: same,
4490
+ text: same
4491
+ ? `命宅相配(${mGroup}命住${hGroup}宅):以宅之八星盘布局即可,个人吉方与宅吉方大体同向。`
4492
+ : `命宅不配(${mGroup}命住${hGroup}宅):传统调停法——门与床以命卦吉方为先(此命四吉方:${mingLucky}),灶仍按宅卦坐凶向吉,两盘各自取用。`,
4493
+ };
4494
+ }
4495
+
4496
+ return {
4497
+ system: 'fengshui',
4498
+ meta: {
4499
+ action: 'advise',
4500
+ ...(hasBirth ? {} : { birthNote: '未提供生辰:未做命宅配;如需按个人命卦调停门床,请补充出生年月日与性别。' }),
4501
+ sitting: `${sitting.m}山(罗盘 ${sitting.deg}°)`,
4502
+ facing: `${facing.m}山(罗盘 ${facing.deg}°)`,
4503
+ houseGua,
4504
+ houseGroup: `${guaGroup(houseGua)}宅`,
4505
+ scope: '门主灶厕走向按八宅通行口径给倾向,不含形峦、不含玄空飞星各运盘、不是现场勘舆结论。',
4506
+ },
4507
+ houseStars: starChart,
4508
+ layout,
4509
+ ...(mingMatch ? { mingMatch } : {}),
4510
+ sources: SOURCES,
4511
+ disclaimer: DISCLAIMER,
4512
+ };
4513
+ }
4514
+
4515
+ return { NINE_STARS, PALACE_DIRECTION, DAYOUTIAN, LUCKY_STARS, UNLUCKY_STARS, MOUNTAINS_24, mountainInfo, sittingFromFacing, STAR_LAYOUT_ROLE, mingGua, eightStars, guaYearOf, yearZibaiStar, yearlyFlying, buildFengshui };
4516
+ })();
4517
+
4518
+ __mods.name = (function () {
4519
+ const { buildBazi } = __mods.bazi;
4520
+ const { assertCivil } = __mods.validate;
4521
+
4522
+ const SHENG = { 木: '火', 火: '土', 土: '金', 金: '水', 水: '木' };
4523
+ const KE = { 木: '土', 土: '水', 水: '火', 火: '金', 金: '木' };
4524
+ const SHENG_BY = { 火: '木', 土: '火', 金: '土', 水: '金', 木: '水' }; // 生我者
4525
+ const KE_BY = { 土: '木', 水: '土', 火: '水', 金: '火', 木: '金' }; // 克我者
4526
+ const ALL = ['木', '火', '土', '金', '水'];
4527
+
4528
+ const CHAR_LIB = [
4529
+ ['梓', '木', '梓树,喻成长、手艺与念乡', 'n'], ['桐', '木', '梧桐,高洁、引来凤凰', 'n'],
4530
+ ['楠', '木', '楠木,坚韧贵重', 'n'], ['楷', '木', '楷模,端正可法', 'm'],
4531
+ ['松', '木', '松柏常青,坚韧长寿', 'm'], ['柏', '木', '柏树,刚直耐寒', 'm'],
4532
+ ['枫', '木', '枫叶,热烈与秋意', 'n'], ['榕', '木', '榕树,荫庇包容', 'n'],
4533
+ ['槿', '木', '木槿,朝开暮落、坚韧', 'f'], ['棠', '木', '海棠,明媚雅致', 'f'],
4534
+ ['楚', '木', '清楚、楚楚有致', 'n'], ['柯', '木', '草木枝干,条理分明', 'm'],
4535
+ ['桓', '木', '桓桓武貌,栋梁', 'm'], ['榆', '木', '榆树,安稳敦厚', 'n'],
4536
+ ['栩', '木', '栩栩生动', 'n'], ['榛', '木', '榛果,丰实', 'n'],
4537
+ ['芸', '木', '芸香,清雅好学', 'f'], ['芷', '木', '白芷,芳洁', 'f'],
4538
+ ['芮', '木', '草初生,柔而韧', 'f'], ['若', '木', '若木,从容', 'f'],
4539
+ ['茜', '木', '茜草,明丽', 'f'], ['茵', '木', '绿茵,葱茏', 'f'],
4540
+ ['茗', '木', '茶芽,清雅', 'f'], ['莎', '木', '莎草,柔韧', 'f'],
4541
+ ['茉', '木', '茉莉,素净芬芳', 'f'], ['筱', '木', '小竹,清秀有节', 'f'],
4542
+ ['蓉', '木', '芙蓉,明艳', 'f'], ['萱', '木', '萱草,忘忧', 'f'],
4543
+ ['蕙', '木', '蕙兰,芳心', 'f'], ['蕊', '木', '花蕊,含秀', 'f'],
4544
+ ['菲', '木', '芳菲,繁茂', 'f'], ['蔓', '木', '藤蔓,绵延', 'f'],
4545
+ ['荣', '木', '繁荣,荣光', 'm'], ['茂', '木', '茂盛,丰隆', 'm'],
4546
+ ['栋', '木', '栋梁', 'm'], ['梁', '木', '桥梁、栋梁', 'm'],
4547
+ ['朴', '木', '质朴', 'm'], ['本', '木', '根本', 'm'],
4548
+ ['炎', '火', '火盛,热烈', 'm'], ['灿', '火', '灿烂', 'n'],
4549
+ ['炫', '火', '光彩炫目', 'm'], ['炜', '火', '光明', 'm'],
4550
+ ['烨', '火', '火光盛明', 'm'], ['煜', '火', '照耀', 'm'],
4551
+ ['熙', '火', '光明和乐', 'n'], ['煊', '火', '温暖显赫', 'm'],
4552
+ ['焕', '火', '焕然一新', 'm'], ['熠', '火', '熠熠生辉', 'n'],
4553
+ ['炅', '火', '日光', 'n'], ['昱', '火', '日光,明亮', 'm'],
4554
+ ['晗', '火', '天将明', 'f'], ['旭', '火', '旭日初升', 'm'],
4555
+ ['昊', '火', '广大如天', 'm'], ['晟', '火', '光明兴盛', 'm'],
4556
+ ['晨', '火', '清晨', 'n'], ['景', '火', '光景、景仰', 'n'],
4557
+ ['曜', '火', '日光照耀', 'm'], ['曦', '火', '晨曦', 'n'],
4558
+ ['朗', '火', '明朗', 'm'], ['昭', '火', '昭明', 'n'],
4559
+ ['昕', '火', '黎明', 'f'], ['昀', '火', '日光', 'f'],
4560
+ ['暖', '火', '温暖', 'f'], ['灵', '火', '灵秀', 'f'],
4561
+ ['丹', '火', '赤诚', 'f'], ['彤', '火', '朱红', 'f'],
4562
+ ['冉', '火', '冉冉上升', 'f'], ['南', '火', '南方,向阳', 'n'],
4563
+ ['坤', '土', '大地,厚德', 'm'], ['垚', '土', '山高', 'm'],
4564
+ ['培', '土', '培育', 'm'], ['城', '土', '城池,坚固', 'm'],
4565
+ ['域', '土', '疆域', 'm'], ['坚', '土', '坚定', 'm'],
4566
+ ['墨', '土', '笔墨,文气', 'n'], ['均', '土', '均衡', 'm'],
4567
+ ['坦', '土', '坦荡', 'm'], ['基', '土', '根基', 'm'],
4568
+ ['堂', '土', '堂堂正正', 'm'], ['圣', '土', '圣明', 'm'],
4569
+ ['在', '土', '存在、自在', 'n'], ['岳', '土', '山岳', 'm'],
4570
+ ['峰', '土', '山峰', 'm'], ['峻', '土', '高峻', 'm'],
4571
+ ['岚', '土', '山间雾气', 'f'], ['岩', '土', '岩石,刚毅', 'm'],
4572
+ ['崇', '土', '崇高', 'm'], ['岐', '土', '岐山,分岔有路', 'm'],
4573
+ ['屿', '土', '小岛', 'n'], ['磊', '土', '磊落', 'm'],
4574
+ ['尧', '土', '古圣王,高远', 'm'], ['圆', '土', '圆满', 'f'],
4575
+ ['恩', '土', '恩厚', 'n'], ['惟', '土', '思惟', 'm'],
4576
+ ['锋', '金', '锋芒', 'm'], ['钢', '金', '刚强', 'm'],
4577
+ ['钧', '金', '权衡、钧衡', 'm'], ['铭', '金', '铭记', 'm'],
4578
+ ['锐', '金', '锐进', 'm'], ['锡', '金', '赐予', 'm'],
4579
+ ['锦', '金', '锦绣', 'n'], ['鑫', '金', '兴盛(三金)', 'm'],
4580
+ ['键', '金', '关键、强健', 'm'], ['鉴', '金', '明鉴', 'm'],
4581
+ ['钦', '金', '钦敬', 'm'], ['镇', '金', '镇守', 'm'],
4582
+ ['铮', '金', '铮铮有声', 'm'], ['钰', '金', '珍宝', 'n'],
4583
+ ['铠', '金', '铠甲', 'm'], ['珊', '金', '珊瑚', 'f'],
4584
+ ['珠', '金', '明珠', 'f'], ['琳', '金', '美玉', 'f'],
4585
+ ['琦', '金', '美玉、奇伟', 'n'], ['琪', '金', '美玉', 'f'],
4586
+ ['瑜', '金', '美玉、瑜亮', 'n'], ['瑞', '金', '祥瑞', 'n'],
4587
+ ['瑄', '金', '璧玉', 'f'], ['璇', '金', '美玉', 'f'],
4588
+ ['璟', '金', '玉光', 'n'], ['璐', '金', '美玉', 'f'],
4589
+ ['瑾', '金', '美玉,谨守', 'f'], ['珏', '金', '双玉', 'n'],
4590
+ ['环', '金', '圆环,周全', 'f'], ['玲', '金', '玲珑', 'f'],
4591
+ ['珑', '金', '玲珑', 'f'], ['瑶', '金', '瑶华', 'f'],
4592
+ ['莹', '金', '晶莹', 'f'], ['铎', '金', '铃铎,传声', 'm'],
4593
+ ['江', '水', '江河,开阔', 'm'], ['河', '水', '河流', 'm'],
4594
+ ['海', '水', '海纳百川', 'm'], ['洋', '水', '海洋,宽广', 'n'],
4595
+ ['波', '水', '波澜', 'm'], ['涛', '水', '波涛', 'm'],
4596
+ ['涵', '水', '包涵、涵养', 'f'], ['清', '水', '清澈', 'n'],
4597
+ ['澈', '水', '澄澈', 'm'], ['澄', '水', '澄明', 'n'],
4598
+ ['润', '水', '温润', 'm'], ['泽', '水', '恩泽', 'm'],
4599
+ ['沛', '水', '充沛', 'm'], ['沁', '水', '沁润', 'f'],
4600
+ ['泓', '水', '水深而广', 'm'], ['泉', '水', '源泉', 'n'],
4601
+ ['汐', '水', '潮汐', 'f'], ['淇', '水', '淇水', 'f'],
4602
+ ['鸿', '水', '鸿鹄、宏大', 'm'], ['滋', '水', '滋润', 'n'],
4603
+ ['淼', '水', '水势浩渺', 'n'], ['雨', '水', '甘霖', 'n'],
4604
+ ['雪', '水', '洁白', 'f'], ['霖', '水', '甘霖', 'm'],
4605
+ ['霏', '水', '雨雪纷飞', 'f'], ['露', '水', '露华', 'f'],
4606
+ ['霆', '水', '雷霆', 'm'], ['雯', '水', '云纹', 'f'],
4607
+ ['霓', '水', '虹霓', 'f'], ['湉', '水', '水流平缓', 'f'],
4608
+ ['沐', '水', '沐浴,受润泽', 'n'], ['宸', '木', '屋宇、北极星所居,喻尊贵', 'm'],
4609
+ ['轩', '土', '高轩,气宇轩昂', 'm'], ['逸', '土', '安逸、超逸', 'm'],
4610
+ ['然', '火', '自然、坦然', 'n'], ['承', '金', '承继担当', 'm'],
4611
+ ['语', '木', '言语,温言', 'f'], ['诺', '火', '诺言,信守', 'n'],
4612
+ ['诗', '金', '诗书,文气', 'f'], ['梦', '木', '梦想', 'f'],
4613
+ ['思', '金', '思虑,才思', 'n'], ['悦', '金', '喜悦', 'f'],
4614
+ ['欣', '木', '欢欣', 'f'], ['嘉', '木', '嘉美', 'n'],
4615
+ ['佳', '木', '佳好', 'f'], ['博', '水', '博学', 'm'],
4616
+ ['文', '水', '文采', 'n'], ['睿', '金', '睿智', 'm'],
4617
+ ['宇', '土', '器宇、宇宙', 'm'], ['亦', '土', '也,从容', 'n'],
4618
+ ['知', '火', '知晓,明理', 'n'], ['星', '火', '星辰', 'n'],
4619
+ ['月', '木', '明月', 'f'], ['言', '木', '言语,信', 'n'],
4620
+ ['和', '水', '和顺', 'n'], ['安', '土', '安宁', 'n'],
4621
+ ['宁', '火', '安宁', 'f'],
4622
+ ];
4623
+
4624
+ const EL_INDEX = new Map();
4625
+ for (const c of CHAR_LIB) {
4626
+ if (!EL_INDEX.has(c[0])) EL_INDEX.set(c[0], { c: c[0], el: c[1], meaning: c[2], g: c[3], order: EL_INDEX.size });
4627
+ }
4628
+
4629
+ const KIND_LABEL = { m: '男', f: '女', n: '中' };
4630
+
4631
+ function favorableElements(strength) {
4632
+ const level = strength && strength.level ? strength.level : null;
4633
+ const dayEl = strength && strength.dayElement ? strength.dayElement : null;
4634
+ if (!dayEl || !level) return { list: ALL.slice(), basis: '未取得旺衰:不筛五行,只做用字与字义建议' };
4635
+ if (level === '身强' || level === '偏强') {
4636
+ return {
4637
+ list: [KE_BY[dayEl], SHENG[dayEl], KE[dayEl]].filter(Boolean),
4638
+ basis: `${level}(${dayEl}):宜克、泄、耗——用${KE_BY[dayEl]}(克我)、${SHENG[dayEl]}(我生)、${KE[dayEl]}(我克)`,
4639
+ };
4640
+ }
4641
+ if (level === '身弱' || level === '偏弱') {
4642
+ return {
4643
+ list: [dayEl, SHENG_BY[dayEl]].filter(Boolean),
4644
+ basis: `${level}(${dayEl}):宜生、扶——用${dayEl}(同我)、${SHENG_BY[dayEl]}(生我)`,
4645
+ };
4646
+ }
4647
+ return { list: ALL.slice(), basis: `${level}:中和不偏,五行皆可用,以字义与音形为先` };
4648
+ }
4649
+
4650
+ function charInfo(ch) {
4651
+ return EL_INDEX.get(ch) || null;
4652
+ }
4653
+
4654
+ function assessName(input) {
4655
+ const surname = String(input.surname || '').trim();
4656
+ if (!surname) throw new Error('缺少姓氏:请给 surname(如「李」)');
4657
+ const given = String(input.givenName || '').trim();
4658
+ if (!given) throw new Error('缺少名字:请给 givenName(1–2 个汉字,如「沐宸」)');
4659
+ if ([...given].length > 2) throw new Error('名请给 1–2 个汉字;复姓请把复姓整体写在 surname,名只留 1–2 字');
4660
+
4661
+ let favorable = { list: [], basis: '' };
4662
+ let strength = null;
4663
+ let baziNote = null;
4664
+ const hasBirth = Number.isInteger(input.year) && Number.isInteger(input.month) && Number.isInteger(input.day);
4665
+ if (hasBirth) {
4666
+ assertCivil(input, 'birth');
4667
+ const r = buildBazi({
4668
+ year: input.year, month: input.month, day: input.day,
4669
+ hour: Number.isInteger(input.hour) ? input.hour : 12,
4670
+ minute: Number.isInteger(input.minute) ? input.minute : 0,
4671
+ gender: input.gender || 'male',
4672
+ lateZi: input.lateZi !== false,
4673
+ trueSolar: input.trueSolar !== false,
4674
+ dst: input.dst !== false,
4675
+ longitude: input.longitude, address: input.address, geo: input.geo,
4676
+ });
4677
+ strength = { level: r.strength.level, score: r.strength.score, dayElement: r.pillars.day.stemElement };
4678
+ favorable = favorableElements(strength);
4679
+ baziNote = { dayMaster: r.dayMaster, solarNote: r.solarTime ? r.solarTime.note : '' };
4680
+ }
4681
+
4682
+ const chars = [...given].map((ch) => {
4683
+ const info = charInfo(ch);
4684
+ const inFav = info ? favorable.list.includes(info.el) : null;
4685
+ return {
4686
+ char: ch,
4687
+ element: info ? info.el : '未收录',
4688
+ meaning: info ? info.meaning : '字库未收录,未推断五行',
4689
+ genderHint: info ? KIND_LABEL[info.g] : null,
4690
+ favorable: inFav,
4691
+ };
4692
+ });
4693
+ const known = chars.filter((c) => c.element !== '未收录').length;
4694
+ const favCount = chars.filter((c) => c.favorable === true).length;
4695
+ let verdict;
4696
+ if (!hasBirth) verdict = '只做了字义与五行归类;给出生年月日才能判断用字是否合喜用。';
4697
+ else if (favCount === chars.length && chars.length > 0) verdict = `用字都落在喜用(${favorable.list.join('、')})上,与命局方向一致。`;
4698
+ else if (favCount > 0) verdict = `部分用字合喜用(${favCount}/${chars.length}),其余可再斟酌;喜用为 ${favorable.list.join('、')}。`;
4699
+ else verdict = `用字未落在喜用(${favorable.list.join('、')})上;不必然是坏名,但若想贴合命局可换字再看。`;
4700
+
4701
+ return {
4702
+ system: 'name',
4703
+ action: 'assess',
4704
+ surname,
4705
+ givenName: given,
4706
+ fullName: surname + given,
4707
+ ...(hasBirth ? {
4708
+ bazi: {
4709
+ ...baziNote,
4710
+ strength,
4711
+ favorable: favorable.list,
4712
+ basis: favorable.basis,
4713
+ },
4714
+ } : {}),
4715
+ chars,
4716
+ stats: { chars: chars.length, known, favorableCount: favCount },
4717
+ verdict,
4718
+ notes: [
4719
+ '字之五行按通行字义/偏旁归类,自建字库有限:未收录的字不猜五行,如实标注。',
4720
+ '不做五格剖象(需康熙字典笔画),也不做音韵格律判断——谐音、拗口请自己读三遍或用方言读一遍。',
4721
+ '起名只做用字参考,不替父母做决定。',
4722
+ ],
4723
+ };
4724
+ }
4725
+
4726
+ function generateNames(input) {
4727
+ const surname = String(input.surname || '').trim();
4728
+ if (!surname) throw new Error('缺少姓氏:请给 surname(如「李」)');
4729
+ if (!Number.isInteger(input.year) || !Number.isInteger(input.month) || !Number.isInteger(input.day)) {
4730
+ throw new Error('生成候选名需要完整出生年月日(公历):不给生辰无法判断喜用五行');
4731
+ }
4732
+ assertCivil(input, 'birth');
4733
+ const gender = input.gender === 'female' ? 'female' : 'male';
4734
+ const limit = Math.max(1, Math.min(20, Number.isInteger(input.limit) ? input.limit : 10));
4735
+ const r = buildBazi({
4736
+ year: input.year, month: input.month, day: input.day,
4737
+ hour: Number.isInteger(input.hour) ? input.hour : 12,
4738
+ minute: Number.isInteger(input.minute) ? input.minute : 0,
4739
+ gender,
4740
+ lateZi: input.lateZi !== false,
4741
+ trueSolar: input.trueSolar !== false,
4742
+ dst: input.dst !== false,
4743
+ longitude: input.longitude, address: input.address, geo: input.geo,
4744
+ });
4745
+ const strength = { level: r.strength.level, score: r.strength.score, dayElement: r.pillars.day.stemElement };
4746
+ const fav = favorableElements(strength);
4747
+ const pool = CHAR_LIB.filter((c) => fav.list.includes(c[1]) && c[0] !== surname);
4748
+ const gPref = gender === 'male' ? 'm' : 'f';
4749
+ const scored = [];
4750
+ for (let i = 0; i < pool.length; i++) {
4751
+ for (let j = 0; j < pool.length; j++) {
4752
+ if (i === j) continue;
4753
+ const a = pool[i];
4754
+ const b = pool[j];
4755
+ const fit = (x) => (x[3] === gPref || x[3] === 'n' ? 1 : 0);
4756
+ const gScore = fit(a) + fit(b) - (a[3] !== gPref && a[3] !== 'n' && b[3] !== gPref && b[3] !== 'n' ? 2 : 0);
4757
+ scored.push({
4758
+ name: a[0] + b[0],
4759
+ chars: [
4760
+ { char: a[0], element: a[1], meaning: a[2] },
4761
+ { char: b[0], element: b[1], meaning: b[2] },
4762
+ ],
4763
+ elements: [a[1], b[1]],
4764
+ allFavorable: fav.list.includes(a[1]) && fav.list.includes(b[1]),
4765
+ genderScore: gScore,
4766
+ order: i * 1000 + j,
4767
+ });
4768
+ }
4769
+ }
4770
+ scored.sort((x, y) => (y.allFavorable - x.allFavorable) || (y.genderScore - x.genderScore) || (x.order - y.order));
4771
+ const out = [];
4772
+ const used = new Set();
4773
+ for (const cand of scored) {
4774
+ if (out.length >= limit) break;
4775
+ const key = [...cand.name].sort().join('');
4776
+ if (used.has(key)) continue;
4777
+ used.add(key);
4778
+ out.push({ rank: out.length + 1, name: surname + cand.name, given: cand.name, chars: cand.chars });
4779
+ }
4780
+ return {
4781
+ system: 'name',
4782
+ action: 'generate',
4783
+ surname,
4784
+ gender: gender === 'male' ? '男' : '女',
4785
+ bazi: { dayMaster: r.dayMaster, strength, favorable: fav.list, basis: fav.basis },
4786
+ candidates: out,
4787
+ notes: [
4788
+ `字库共 ${CHAR_LIB.length} 字,只在喜用五行(${fav.list.join('、')})里选字;未覆盖的生僻好字可手工评估。`,
4789
+ '候选按合喜用与性别倾向确定性排序,不是「吉名」判定;音韵与方言谐音请自行读念检查。',
4790
+ '不做五格剖象(需康熙字典笔画)。',
4791
+ ],
4792
+ };
4793
+ }
4794
+
4795
+ function buildName(input) {
4796
+ if (input.action === 'generate') return generateNames(input);
4797
+ return assessName(input);
4798
+ }
4799
+
4800
+ return { CHAR_LIB, favorableElements, assessName, generateNames, buildName };
4801
+ })();
4802
+
3934
4803
  __mods['ziwei-hepan'] = (function () {
3935
4804
  const { buildZiwei } = __mods.ziwei;
3936
4805
  const { assertCivil } = __mods.validate;
@@ -4861,6 +5730,632 @@ const TAROT_ELEMENT_FOCUS = {
4861
5730
  return { TRIGRAM, YAO_POSITION, MOVING_NATURE, HEX64, HEX_BY_CORE, ZIWEI_STARS, WUXING_INDUSTRY, WUXING_ORGAN, TENGOD_STYLE, LIUREN_JIANG, DISCLAIMER, TAROT_ELEMENT_FOCUS };
4862
5731
  })();
4863
5732
 
5733
+ __mods.almanac = (function () {
5734
+ const { solarToLunar } = __mods.lunar;
5735
+ const { toJDN, fromJDN, dayGanzhiIndex, ganzhiName, termWindow, termInstantJd, partsToJd, STEMS, BRANCHES, TERM_NAMES } = __mods.calendar;
5736
+ const { HEX64 } = __mods['interpret-data'];
5737
+ const { assertCivil, daysInMonth } = __mods.validate;
5738
+
5739
+ const ZHIXING = ['建', '除', '满', '平', '定', '执', '破', '危', '成', '收', '开', '闭'];
5740
+
5741
+ const ZHIXING_YI = {
5742
+ 建: ['出行', '上任', '会友', '上书', '见工'],
5743
+ 除: ['除服', '疗病', '扫舍', '出行'],
5744
+ 满: ['祈福', '祭祀', '结亲', '开市', '交易'],
5745
+ 平: ['修饰垣墙', '平治道涂'],
5746
+ 定: ['嫁娶', '订盟', '纳采', '祭祀', '祈福'],
5747
+ 执: ['祭祀', '祈福', '捕捉', '纳畜'],
5748
+ 破: ['破屋', '坏垣', '求医'],
5749
+ 危: ['祭祀', '祈福', '安床'],
5750
+ 成: ['嫁娶', '开市', '入学', '上任', '修造'],
5751
+ 收: ['纳财', '收账', '进人口'],
5752
+ 开: ['开市', '交易', '嫁娶', '入学', '动土'],
5753
+ 闭: ['筑堤', '埋穴', '补垣'],
5754
+ };
5755
+
5756
+ const ZHIXING_JI = {
5757
+ 建: ['动土', '开仓', '出货财', '安葬'],
5758
+ 除: ['求财', '赴任', '嫁娶'],
5759
+ 满: ['服药', '栽种', '安葬'],
5760
+ 平: ['祈福', '求嗣'],
5761
+ 定: ['诉讼', '出行'],
5762
+ 执: ['开市', '求财', '出行'],
5763
+ 破: ['嫁娶', '开市', '动土'],
5764
+ 危: ['登高', '行船', '出行'],
5765
+ 成: ['诉讼'],
5766
+ 收: ['开市', '出行', '安葬'],
5767
+ 开: ['安葬'],
5768
+ 闭: ['开市', '出行', '求医'],
5769
+ };
5770
+
5771
+ const CAI_SHEN = { 甲: '东北', 乙: '东北', 丙: '西南', 丁: '西南', 戊: '正北', 己: '正北', 庚: '正东', 辛: '正东', 壬: '正南', 癸: '正南' };
5772
+
5773
+ const XI_SHEN = { 甲: '东北', 乙: '西北', 丙: '西南', 丁: '正南', 戊: '东南', 己: '东北', 庚: '西北', 辛: '西南', 壬: '正南', 癸: '东南' };
5774
+
5775
+ const SHENGXIAO = ['鼠', '牛', '虎', '兔', '龙', '蛇', '马', '羊', '猴', '鸡', '狗', '猪'];
5776
+ const SHA_BY_GROUP = { 申子辰: '南', 寅午戌: '北', 巳酉丑: '东', 亥卯未: '西' };
5777
+ function shaOf(branch) {
5778
+ for (const [g, v] of Object.entries(SHA_BY_GROUP)) if (g.includes(branch)) return v;
5779
+ return null;
5780
+ }
5781
+
5782
+ function hourToBranch(hour, minute = 0) {
5783
+ const h = Number.isInteger(hour) ? ((hour % 24) + 24) % 24 : 12;
5784
+ return BRANCHES[Math.floor(((h + 1) % 24) / 2)];
5785
+ }
5786
+
5787
+ function zhixingOf(dayBranch, monthBranch) {
5788
+ const d = BRANCHES.indexOf(dayBranch);
5789
+ const m = BRANCHES.indexOf(monthBranch);
5790
+ return ZHIXING[((d - m) % 12 + 12) % 12];
5791
+ }
5792
+
5793
+ const MONTH_TEXT = ['正', '二', '三', '四', '五', '六', '七', '八', '九', '十', '冬', '腊'];
5794
+ const DAY_TENS = ['初', '十', '廿'];
5795
+ const DAY_UNITS = ['十', '一', '二', '三', '四', '五', '六', '七', '八', '九'];
5796
+ function dayText(day) {
5797
+ if (day === 10) return '初十';
5798
+ if (day === 20) return '二十';
5799
+ if (day === 30) return '三十';
5800
+ return `${DAY_TENS[Math.floor(day / 10)]}${DAY_UNITS[day % 10]}`;
5801
+ }
5802
+ function lunarMonthText(month, isLeap = false) {
5803
+ return `${isLeap ? '闰' : ''}${MONTH_TEXT[month - 1] || month}月`;
5804
+ }
5805
+
5806
+ function dailyHexagram(year, month, day) {
5807
+ const names = Object.keys(HEX64);
5808
+ if (!names.length) return null;
5809
+ const seed = (((year * 10007) ^ (month * 911) ^ (day * 97)) >>> 0) % names.length;
5810
+ const name = names[seed];
5811
+ return { name, ...HEX64[name] };
5812
+ }
5813
+
5814
+ const termName = (t) => (t && (t.name || TERM_NAMES[t.index])) || '';
5815
+
5816
+ function buildAlmanac(input) {
5817
+ assertCivil(input, '黄历');
5818
+ const { year, month, day } = input;
5819
+ if (day > daysInMonth(year, month)) {
5820
+ throw new Error(`非法日期:${year} 年 ${month} 月没有 ${day} 日`);
5821
+ }
5822
+ const jdNoon = partsToJd({ year, month, day, hour: 12, minute: 0 });
5823
+ const lunar = solarToLunar(year, month, day);
5824
+ const jdn = toJDN(year, month, day);
5825
+
5826
+ const lichunJd = termInstantJd(year, 2);
5827
+ const ganzhiYear = jdNoon < lichunJd ? year - 1 : year;
5828
+ const yearIdx = (((ganzhiYear - 4) % 60) + 60) % 60;
5829
+ const yearGz = ganzhiName(yearIdx);
5830
+ const springYear = lunar.lunarYear;
5831
+ const springGz = ganzhiName((((springYear - 4) % 60) + 60) % 60);
5832
+
5833
+ const jieTerms = termWindow(year, { jieOnly: true });
5834
+ let prevJie = null;
5835
+ let nextJie = null;
5836
+ for (const t of jieTerms) {
5837
+ if (t.jd <= jdNoon) prevJie = t; else { if (!nextJie) nextJie = t; }
5838
+ }
5839
+ const monthBranchIdx = ((2 + (prevJie.index - 2) / 2) % 12 + 12) % 12;
5840
+ const yinMonthStem = ((STEMS.indexOf(yearGz.stem) % 5) * 2 + 2) % 10;
5841
+ const monthStemIdx = (yinMonthStem + ((monthBranchIdx - 2 + 12) % 12)) % 10;
5842
+ const monthGz = ganzhiName(monthComposite(monthStemIdx, monthBranchIdx));
5843
+
5844
+ const dayGz = ganzhiName(dayGanzhiIndex(jdn));
5845
+ const dayBranch = dayGz.branch;
5846
+ const zhiXing = zhixingOf(dayBranch, BRANCHES[monthBranchIdx]);
5847
+ const chongIdx = (BRANCHES.indexOf(dayBranch) + 6) % 12;
5848
+ const chongShengXiao = SHENGXIAO[chongIdx];
5849
+
5850
+ const allTerms = termWindow(year);
5851
+ let prevTerm = null;
5852
+ let nextTerm = null;
5853
+ for (const t of allTerms) {
5854
+ if (t.jd <= jdNoon) prevTerm = t; else { if (!nextTerm) nextTerm = t; }
5855
+ }
5856
+ const fmtJdDate = (jd) => {
5857
+ const p = fromJDN(Math.floor(jd + 0.5));
5858
+ return `${p.year}-${String(p.month).padStart(2, '0')}-${String(p.day).padStart(2, '0')}`;
5859
+ };
5860
+
5861
+ const weekday = ['日', '一', '二', '三', '四', '五', '六'][(jdn + 1) % 7];
5862
+ const lunarText = `农历${springGz.name}年${lunarMonthText(lunar.month, lunar.isLeap)}${dayText(lunar.day)}`;
5863
+ const boundaryNote = yearGz.name !== springGz.name
5864
+ ? `年界:本条按立春界作${yearGz.name}年·属${SHENGXIAO[BRANCHES.indexOf(yearGz.branch)]}(命理排盘用);春节界为${springGz.name}年·属${SHENGXIAO[BRANCHES.indexOf(springGz.branch)]},两种都对,取用场合不同`
5865
+ : null;
5866
+
5867
+ return {
5868
+ system: 'almanac',
5869
+ meta: {
5870
+ date: `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`,
5871
+ weekday: `星期${weekday}`,
5872
+ ...(boundaryNote ? { boundaryNote } : {}),
5873
+ scope: '通行简表口径:宜忌只按建除十二神;逐日神煞/二十八宿/彭祖百忌/胎神等全量黄历数据未收录',
5874
+ },
5875
+ lunar: {
5876
+ year: springYear,
5877
+ month: lunar.month,
5878
+ day: lunar.day,
5879
+ isLeap: lunar.isLeap,
5880
+ text: lunarText,
5881
+ },
5882
+ ganzhi: {
5883
+ year: yearGz.name,
5884
+ month: monthGz.name,
5885
+ day: dayGz.name,
5886
+ shengXiao: SHENGXIAO[BRANCHES.indexOf(yearGz.branch)],
5887
+ springYear: springGz.name,
5888
+ springShengXiao: SHENGXIAO[BRANCHES.indexOf(springGz.branch)],
5889
+ },
5890
+ jieQi: {
5891
+ prev: prevTerm ? { name: termName(prevTerm), date: fmtJdDate(prevTerm.jd) } : null,
5892
+ next: nextTerm ? { name: termName(nextTerm), date: fmtJdDate(nextTerm.jd) } : null,
5893
+ },
5894
+ positions: { caiShen: CAI_SHEN[dayGz.stem] || null, xiShen: XI_SHEN[dayGz.stem] || null },
5895
+ zhiXing,
5896
+ yi: ZHIXING_YI[zhiXing].slice(),
5897
+ ji: ZHIXING_JI[zhiXing].slice(),
5898
+ chong: {
5899
+ shengXiao: chongShengXiao,
5900
+ text: `冲${chongShengXiao}`,
5901
+ sha: shaOf(dayBranch) ? `煞${shaOf(dayBranch)}` : null,
5902
+ },
5903
+ dailyHexagram: dailyHexagram(year, month, day),
5904
+ sources: [
5905
+ '农历与节气:本地天文历法(定朔 + 节气黄经)',
5906
+ '建除十二神与冲煞:传统通行口径',
5907
+ '宜忌:建除十二神通行简表(非逐日全量黄历)',
5908
+ '每日一卦:按公历日期确定性取卦(同日同卦)',
5909
+ ],
5910
+ disclaimer: '黄历为传统民俗口径的本地确定性计算;宜忌仅按建除简表,属娱乐参考,不构成择日结论。',
5911
+ };
5912
+ }
5913
+
5914
+ function monthComposite(stemIdx, branchIdx) {
5915
+ for (let i = 0; i < 60; i++) {
5916
+ if (i % 10 === stemIdx && i % 12 === branchIdx) return i;
5917
+ }
5918
+ return 0;
5919
+ }
5920
+
5921
+ return { ZHIXING, ZHIXING_YI, ZHIXING_JI, SHENGXIAO, hourToBranch, zhixingOf, lunarMonthText, dailyHexagram, buildAlmanac };
5922
+ })();
5923
+
5924
+ __mods.star = (function () {
5925
+ const { daysInMonth } = __mods.validate;
5926
+
5927
+ const SIGNS = [
5928
+ { name: '白羊座', element: '火象', planet: '火星', range: [3, 21, 4, 19], keywords: '热血、开拓、直来直往', compatible: '狮子、射手、天秤' },
5929
+ { name: '金牛座', element: '土象', planet: '金星', range: [4, 20, 5, 20], keywords: '沉稳、务实、惜物恋旧', compatible: '处女、摩羯、巨蟹' },
5930
+ { name: '双子座', element: '风象', planet: '水星', range: [5, 21, 6, 21], keywords: '灵动、好奇、能言善变', compatible: '天秤、水瓶、白羊' },
5931
+ { name: '巨蟹座', element: '水象', planet: '月亮', range: [6, 22, 7, 22], keywords: '顾家、念旧、外柔内韧', compatible: '天蝎、双鱼、金牛' },
5932
+ { name: '狮子座', element: '火象', planet: '太阳', range: [7, 23, 8, 22], keywords: '大方、自信、天生舞台感', compatible: '白羊、射手、天秤' },
5933
+ { name: '处女座', element: '土象', planet: '水星', range: [8, 23, 9, 22], keywords: '细致、挑剔、完美主义', compatible: '金牛、摩羯、巨蟹' },
5934
+ { name: '天秤座', element: '风象', planet: '金星', range: [9, 23, 10, 23], keywords: '优雅、权衡、社交高手', compatible: '双子、水瓶、狮子' },
5935
+ { name: '天蝎座', element: '水象', planet: '冥王星', range: [10, 24, 11, 22], keywords: '深挚、洞察、爱憎分明', compatible: '巨蟹、双鱼、处女' },
5936
+ { name: '射手座', element: '火象', planet: '木星', range: [11, 23, 12, 21], keywords: '乐观、自由、远方体质', compatible: '白羊、狮子、水瓶' },
5937
+ { name: '摩羯座', element: '土象', planet: '土星', range: [12, 22, 1, 19], keywords: '自律、坚忍、大器晚成', compatible: '金牛、处女、天蝎' },
5938
+ { name: '水瓶座', element: '风象', planet: '天王星', range: [1, 20, 2, 18], keywords: '独立、前卫、脑洞担当', compatible: '双子、天秤、射手' },
5939
+ { name: '双鱼座', element: '水象', planet: '海王星', range: [2, 19, 3, 20], keywords: '温柔、共情、浪漫滤镜', compatible: '巨蟹、天蝎、金牛' },
5940
+ ];
5941
+
5942
+ function signOfDate(month, day) {
5943
+ for (const s of SIGNS) {
5944
+ const [m1, d1, m2, d2] = s.range;
5945
+ if ((month === m1 && day >= d1) || (month === m2 && day <= d2)) return s;
5946
+ }
5947
+ return null;
5948
+ }
5949
+
5950
+ function daySeed(y, m, d, salt) {
5951
+ let x = ((y * 73856093) ^ (m * 19349663) ^ (d * 83492791) ^ Math.imul(salt, 2654435761)) >>> 0;
5952
+ x = Math.imul(x ^ (x >>> 13), 0x5bd1e995);
5953
+ return (x ^ (x >>> 15)) >>> 0;
5954
+ }
5955
+
5956
+ const COLORS = ['晨雾白', '海盐蓝', '苔原绿', '落日橘', '琥珀黄', '黛蓝', '藕荷粉', '石墨灰', '橄榄绿', '雾紫'];
5957
+ const TONE_TEXT = {
5958
+ 5: '势头极好,主动出击。',
5959
+ 4: '顺风缓行,乘势而为。',
5960
+ 3: '平顺持中,稳字当头。',
5961
+ 2: '略有阻滞,慢即是快。',
5962
+ 1: '宜静宜守,养精蓄锐。',
5963
+ };
5964
+
5965
+ function buildStar(input) {
5966
+ const { year, month, day } = input || {};
5967
+ if (!Number.isInteger(year) || !Number.isInteger(month) || !Number.isInteger(day)
5968
+ || year < 1900 || year > 2100 || month < 1 || month > 12 || day < 1 || day > daysInMonth(year, month)) {
5969
+ throw new Error('星座需要真实存在的公历日期(year 1900–2100、month、day)——请向用户询问');
5970
+ }
5971
+ const sign = signOfDate(month, day);
5972
+ if (!sign) throw new Error(`无法识别星座:${year}-${month}-${day}`);
5973
+ const dims = ['事业', '感情', '财运', '健康'].map((k, i) => {
5974
+ const stars = (daySeed(year, month, day, i + 1) % 5) + 1;
5975
+ return { name: k, stars, text: TONE_TEXT[stars] };
5976
+ });
5977
+ return {
5978
+ system: 'star',
5979
+ meta: { date: `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}` },
5980
+ sign: {
5981
+ name: sign.name,
5982
+ element: sign.element,
5983
+ planet: sign.planet,
5984
+ keywords: sign.keywords,
5985
+ compatible: sign.compatible,
5986
+ range: `${sign.range[0]}月${sign.range[1]}日–${sign.range[2]}月${sign.range[3]}日`,
5987
+ },
5988
+ daily: {
5989
+ tone: TONE_TEXT[(daySeed(year, month, day, 9) % 5) + 1],
5990
+ dims,
5991
+ luckyColor: COLORS[daySeed(year, month, day, 10) % COLORS.length],
5992
+ luckyNumber: (daySeed(year, month, day, 11) % 9) + 1,
5993
+ },
5994
+ sources: ['星座区间:太阳过宫通行日期', '当日基调:日期哈希确定性生成(同日同结果)'],
5995
+ disclaimer: '星座档案为通行天文口径;当日基调纯娱乐向,同日同结果,不构成任何现实预测。',
5996
+ };
5997
+ }
5998
+
5999
+ return { SIGNS, signOfDate, buildStar };
6000
+ })();
6001
+
6002
+ __mods.numerology = (function () {
6003
+ const { daysInMonth } = __mods.validate;
6004
+
6005
+ const NUMBER_MEANING = {
6006
+ 1: { title: '开创者', keywords: '独立、主见、起步快', tendency: '习惯自己拍板,适合从零开始的事。', lesson: '课题是学会合作与示弱,别把「我能行」当唯一答案。' },
6007
+ 2: { title: '协调者', keywords: '细腻、共情、善配合', tendency: '擅长读空气、做黏合剂,团队里离不开的角色。', lesson: '课题是别过度讨好、别把委屈攒成怨气。' },
6008
+ 3: { title: '表达者', keywords: '活跃、创意、感染力', tendency: '靠表达与点子吃饭,越有舞台越发光。', lesson: '课题是持续力与收尾,别让兴趣过载成半成品。' },
6009
+ 4: { title: '建构者', keywords: '务实、秩序、靠得住', tendency: '擅长把混乱理成流程,慢工出细活。', lesson: '课题是别过度求稳、别用规则捆住自己和别人。' },
6010
+ 5: { title: '探索者', keywords: '自由、变化、新鲜感', tendency: '适应力强,喜欢移动与尝鲜,厌烦一成不变。', lesson: '课题是承诺与专注,别把「换一个」当解药。' },
6011
+ 6: { title: '照顾者', keywords: '责任、顾家、审美', tendency: '天然把身边人放进自己的责任清单。', lesson: '课题是别替别人扛过头,爱不等于包办。' },
6012
+ 7: { title: '探究者', keywords: '内省、钻研、独处', tendency: '需要个人空间,喜欢把问题想透再动。', lesson: '课题是信任与敞开,别用距离保护自己。' },
6013
+ 8: { title: '经营者', keywords: '资源、格局、执行力', tendency: '对钱与权有直觉,擅长把资源盘活。', lesson: '课题是平衡控制欲,别用成果定义自我价值。' },
6014
+ 9: { title: '理想者', keywords: '情怀、包容、完成感', tendency: '看得远,愿意为更大的意义投入。', lesson: '课题是落地与界限,别在付出里丢了自己。' },
6015
+ 11: { title: '直觉者(主数)', keywords: '敏感、灵感、感召力', tendency: '直觉极准,容易成为别人的灯,但情绪消耗也大。', lesson: '课题是把天赋落地成具体作品,而不是只停在感受。' },
6016
+ 22: { title: '实践家(主数)', keywords: '愿景、系统、扛大梁', tendency: '能把大想法拆成可行工程,压力与能力同步放大。', lesson: '课题是别被宏大目标压住,先完成小闭环。' },
6017
+ 33: { title: '疗愈者(主数)', keywords: '大爱、教导、承担', tendency: '为他人承担是本能,适合教导与疗愈型角色。', lesson: '课题是先照顾好自己,别把牺牲当价值。' },
6018
+ };
6019
+
6020
+ function reduceNumber(n, keepMasters = true) {
6021
+ let v = Math.abs(Math.trunc(n));
6022
+ while (v > 9) {
6023
+ if (keepMasters && (v === 11 || v === 22 || v === 33)) return v;
6024
+ v = String(v).split('').reduce((s, c) => s + Number(c), 0);
6025
+ }
6026
+ return v;
6027
+ }
6028
+
6029
+ const digitSum = (n) => String(Math.abs(Math.trunc(n))).split('').reduce((s, c) => s + Number(c), 0);
6030
+
6031
+ function buildNumerology(input) {
6032
+ const { year, month, day } = input || {};
6033
+ if (!Number.isInteger(year) || !Number.isInteger(month) || !Number.isInteger(day)
6034
+ || year < 1900 || year > 2100 || month < 1 || month > 12 || day < 1 || day > daysInMonth(year, month)) {
6035
+ throw new Error('生命数字需要真实存在的公历出生日期(year 1900–2100、month、day)——请向用户询问,勿猜');
6036
+ }
6037
+ const lifePath = reduceNumber(digitSum(year) + digitSum(month) + digitSum(day));
6038
+ const birthday = reduceNumber(day);
6039
+ const attitude = reduceNumber(month + day);
6040
+ const parts = [
6041
+ { key: 'lifePath', name: '生命路径数', number: lifePath, meaning: NUMBER_MEANING[lifePath] },
6042
+ { key: 'birthday', name: '生日数', number: birthday, meaning: NUMBER_MEANING[birthday] },
6043
+ { key: 'attitude', name: '态度数', number: attitude, meaning: NUMBER_MEANING[attitude] },
6044
+ ];
6045
+ return {
6046
+ system: 'numerology',
6047
+ meta: { date: `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`, system: '西方数字学' },
6048
+ numbers: { lifePath, birthday, attitude },
6049
+ parts,
6050
+ primary: parts[0],
6051
+ sources: ['生命路径=出生年月日逐位求和归约;生日数=日归约;态度数=月+日归约(11/22/33 保留)'],
6052
+ disclaimer: '生命数字属西方数字学体系,与八字/紫微等东方术数口径不同、不可互相印证;仅供自我认识参考。',
6053
+ };
6054
+ }
6055
+
6056
+ return { NUMBER_MEANING, reduceNumber, buildNumerology };
6057
+ })();
6058
+
6059
+ __mods.chenggu = (function () {
6060
+ const { solarToLunar } = __mods.lunar;
6061
+ const { ganzhiName, toJDN, dayGanzhiIndex } = __mods.calendar;
6062
+ const { assertCivil } = __mods.validate;
6063
+ const { hourToBranch } = __mods.almanac;
6064
+
6065
+ const YEAR_WEIGHT = [
6066
+ 12, 9, 6, 7, 12, 5, 9, 8, 7, 8,
6067
+ 15, 9, 16, 8, 8, 19, 12, 6, 8, 7,
6068
+ 5, 15, 6, 16, 15, 7, 9, 12, 10, 7,
6069
+ 15, 6, 5, 14, 14, 9, 7, 7, 9, 12,
6070
+ 8, 7, 13, 5, 14, 5, 9, 17, 5, 7,
6071
+ 12, 8, 8, 6, 19, 6, 8, 16, 10, 7,
6072
+ ];
6073
+
6074
+ const MONTH_WEIGHT = [6, 7, 18, 9, 5, 16, 9, 15, 18, 8, 9, 5];
6075
+
6076
+ const DAY_WEIGHT = [
6077
+ 5, 10, 8, 15, 16, 15, 8, 16, 8, 16,
6078
+ 9, 17, 8, 17, 10, 8, 9, 18, 5, 15,
6079
+ 10, 9, 8, 9, 15, 18, 7, 8, 16, 6,
6080
+ ];
6081
+
6082
+ const HOUR_WEIGHT = { 子: 16, 丑: 6, 寅: 7, 卯: 10, 辰: 9, 巳: 16, 午: 10, 未: 8, 申: 8, 酉: 9, 戌: 6, 亥: 6 };
6083
+
6084
+ const LIANG = ['一', '二', '三', '四', '五', '六', '七'];
6085
+ const QIAN = ['一', '二', '三', '四', '五', '六', '七', '八', '九'];
6086
+ const MONTH_TEXT = ['正', '二', '三', '四', '五', '六', '七', '八', '九', '十', '冬', '腊'];
6087
+ const DAY_TENS = ['初', '十', '廿'];
6088
+ const DAY_UNITS = ['十', '一', '二', '三', '四', '五', '六', '七', '八', '九'];
6089
+
6090
+ function formatWeight(qian) {
6091
+ const liang = Math.floor(qian / 10);
6092
+ const rem = qian % 10;
6093
+ const liangPart = liang > 0 ? `${LIANG[liang - 1]}两` : '';
6094
+ const remPart = rem > 0 ? `${QIAN[rem - 1]}钱` : liang > 0 ? '整' : '零';
6095
+ return `${liangPart}${remPart}`;
6096
+ }
6097
+
6098
+ function lunarDayText(day) {
6099
+ if (day === 10) return '初十';
6100
+ if (day === 20) return '二十';
6101
+ if (day === 30) return '三十';
6102
+ const ten = Math.floor(day / 10);
6103
+ const unit = day % 10;
6104
+ return `${DAY_TENS[ten]}${DAY_UNITS[unit]}`;
6105
+ }
6106
+
6107
+ function lunarMonthText(month, isLeap = false) {
6108
+ return `${isLeap ? '闰' : ''}${MONTH_TEXT[month - 1] || month}月`;
6109
+ }
6110
+
6111
+ function bandVerdict(qian) {
6112
+ if (qian >= 55) return { band: '上上', text: '骨重沉实,根基深厚,一生多逢贵人,谋事易成;守正持重,福泽可久。' };
6113
+ if (qian >= 45) return { band: '上', text: '骨重充实,中年前后渐入佳境,事业家宅两旺;忌骄忌躁,善守即善成。' };
6114
+ if (qian >= 35) return { band: '中上', text: '骨重适中,先劳后逸,凭本事立身;中年转运之象,稳扎稳打自见分晓。' };
6115
+ if (qian >= 25) return { band: '中', text: '骨重平平,成败多靠自身经营;宜有一技傍身、广结善缘,勤则补拙。' };
6116
+ return { band: '中下', text: '骨重较轻,早年多磨,凡事宜稳不宜急;积跬步以至千里,晚景反可安宁。' };
6117
+ }
6118
+
6119
+ function buildChenggu(input) {
6120
+ assertCivil(input, '称骨');
6121
+ const { year, month, day, hour } = input;
6122
+ const minute = input.minute || 0;
6123
+ if (!Number.isInteger(hour) || hour < 0 || hour > 23) {
6124
+ throw new Error('称骨必须给时辰:hour 为 0–23 的整数(缺时辰四柱不齐,骨重无法求和)');
6125
+ }
6126
+ const lunar = solarToLunar(year, month, day);
6127
+ const lunarYear = lunar.lunarYear;
6128
+ const lunarMonth = lunar.month;
6129
+ const lunarDay = lunar.day;
6130
+ const yearGz = ganzhiName((((lunarYear - 4) % 60) + 60) % 60);
6131
+ const hourBranch = hourToBranch(hour, minute);
6132
+
6133
+ const wYear = YEAR_WEIGHT[((lunarYear - 4) % 60 + 60) % 60];
6134
+ const wMonth = MONTH_WEIGHT[lunarMonth - 1];
6135
+ const wDay = DAY_WEIGHT[lunarDay - 1];
6136
+ const wHour = HOUR_WEIGHT[hourBranch];
6137
+ if ([wYear, wMonth, wDay, wHour].some((x) => typeof x !== 'number')) {
6138
+ throw new Error(`称骨表缺项:年${wYear} 月${wMonth} 日${wDay} 时${wHour}`);
6139
+ }
6140
+ const total = wYear + wMonth + wDay + wHour;
6141
+ const verdict = bandVerdict(total);
6142
+ const jdn = toJDN(year, month, day);
6143
+
6144
+ return {
6145
+ system: 'chenggu',
6146
+ input: { solar: `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`, hour, minute },
6147
+ lunar: {
6148
+ text: `农历${yearGz.name}年${lunarMonthText(lunarMonth, lunar.isLeap)}${lunarDayText(lunarDay)} ${hourBranch}时`,
6149
+ year: lunarYear,
6150
+ month: lunarMonth,
6151
+ day: lunarDay,
6152
+ isLeap: lunar.isLeap,
6153
+ hourBranch,
6154
+ },
6155
+ ganzhi: { year: yearGz.name, day: ganzhiName(dayGanzhiIndex(jdn)).name },
6156
+ weights: {
6157
+ year: { qian: wYear, label: `${yearGz.name}年 ${formatWeight(wYear)}` },
6158
+ month: { qian: wMonth, label: `农历${lunarMonthText(lunarMonth, lunar.isLeap)} ${formatWeight(wMonth)}` },
6159
+ day: { qian: wDay, label: `农历${lunarDayText(lunarDay)} ${formatWeight(wDay)}` },
6160
+ hour: { qian: wHour, label: `${hourBranch}时 ${formatWeight(wHour)}` },
6161
+ },
6162
+ total: { qian: total, label: formatWeight(total) },
6163
+ verdict,
6164
+ sources: [
6165
+ '骨重表:通行本袁天罡称骨(六十甲子年 / 十二月 / 三十日 / 十二时辰)',
6166
+ '批语:白话自撰,非原版称骨歌诀原文,可自行对照',
6167
+ ],
6168
+ disclaimer: '骨重为通行表口径;批语为白话解读,属民俗娱乐参考,不构成命运断言。',
6169
+ };
6170
+ }
6171
+
6172
+ return { YEAR_WEIGHT, MONTH_WEIGHT, DAY_WEIGHT, HOUR_WEIGHT, formatWeight, lunarDayText, lunarMonthText, buildChenggu };
6173
+ })();
6174
+
6175
+ __mods.xiaoliuren = (function () {
6176
+ const { solarToLunar } = __mods.lunar;
6177
+ const { hourToBranch } = __mods.almanac;
6178
+ const { BRANCHES } = __mods.calendar;
6179
+
6180
+ const PALACES = {
6181
+ 大安: { luck: '吉', text: '事事昌顺,宜静守不宜妄动;所问之事根基稳,缓一缓更妥。' },
6182
+ 留连: { luck: '凶', text: '事多牵扯反复,暗中有耗;宜守不宜进,防拖延与口舌。' },
6183
+ 速喜: { luck: '吉', text: '喜事将至,行动宜快;所问之事三日内外见分晓。' },
6184
+ 赤口: { luck: '凶', text: '口舌是非、官讼之象;宜谨言慎行,凡事留凭证。' },
6185
+ 小吉: { luck: '吉', text: '小有助力,慢慢来能成;合作与人际有贵人。' },
6186
+ 空亡: { luck: '凶', text: '落空之象,谋事多不成;宜停一停,改期再议。' },
6187
+ };
6188
+
6189
+ const PALACE_ORDER = ['大安', '留连', '速喜', '赤口', '小吉', '空亡'];
6190
+
6191
+ const pick = (n) => PALACE_ORDER[(((n - 1) % 6) + 6) % 6];
6192
+
6193
+ function buildXiaoliuren(input) {
6194
+ const mode = input.mode === 'number' ? 'number' : 'time';
6195
+ let m;
6196
+ let d;
6197
+ let h;
6198
+ let originText;
6199
+ if (mode === 'time') {
6200
+ if (typeof input.hour !== 'number' || !Number.isInteger(input.hour) || input.hour < 0 || input.hour > 23) {
6201
+ throw new Error('时间起课必须同时给 date 与 hour(0–23):掐指按时辰推进,缺时辰不可代填');
6202
+ }
6203
+ const mt = /^(\d{4})-(\d{1,2})-(\d{1,2})$/.exec(String(input.date || '').trim());
6204
+ if (!mt) throw new Error('时间起课需要 date(公历 YYYY-MM-DD)——请向用户询问日期');
6205
+ const lunar = solarToLunar(Number(mt[1]), Number(mt[2]), Number(mt[3]));
6206
+ const branch = hourToBranch(input.hour);
6207
+ m = lunar.month;
6208
+ d = lunar.day;
6209
+ h = BRANCHES.indexOf(branch) + 1;
6210
+ originText = `农历${lunar.month}月${lunar.day}日 ${branch}时:月${m} → 日${d} → 时${h} 掐指落宫`;
6211
+ } else {
6212
+ const nums = [input.num1, input.num2, input.num3].map(Number);
6213
+ if (nums.some((x) => !Number.isInteger(x) || x < 1 || x > 99)) {
6214
+ throw new Error('报数起课需要三个 1–99 的正整数(随口报即可)——缺数请向用户索要,不要代填');
6215
+ }
6216
+ [m, d, h] = nums;
6217
+ originText = `报数 ${m}、${d}、${h}:依次按月/日/时位掐指落宫`;
6218
+ }
6219
+ const p1 = pick(m);
6220
+ const p2 = pick(PALACE_ORDER.indexOf(p1) + 1 + d - 1);
6221
+ const p3 = pick(PALACE_ORDER.indexOf(p2) + 1 + h - 1);
6222
+ const info = PALACES[p3];
6223
+ return {
6224
+ system: 'xiaoliuren',
6225
+ meta: { mode: mode === 'time' ? '时间起课' : '报数起课', question: input.question || null, originText },
6226
+ steps: [p1, p2, p3],
6227
+ result: { palace: p3, luck: info.luck, text: info.text },
6228
+ sources: ['掌诀口径:通行马前课(白话提炼,非原文照录)'],
6229
+ disclaimer: '小六壬为轻量快占,属民俗娱乐参考;重要事项请综合现实信息判断。',
6230
+ };
6231
+ }
6232
+
6233
+ return { PALACES, PALACE_ORDER, buildXiaoliuren };
6234
+ })();
6235
+
6236
+ __mods.zeri = (function () {
6237
+ const { fromJDN, toJDN } = __mods.calendar;
6238
+ const { buildAlmanac, SHENGXIAO } = __mods.almanac;
6239
+
6240
+ const TASKS = {
6241
+ 嫁娶: { yi: ['嫁娶', '结亲', '订盟', '纳采'], related: ['会亲友', '裁衣'], category: 'marriage' },
6242
+ 订婚: { yi: ['订盟', '纳采'], related: ['嫁娶', '会亲友'], category: 'marriage' },
6243
+ 开业: { yi: ['开市', '立券', '交易'], related: ['纳财', '挂匾'], category: 'business' },
6244
+ 入宅: { yi: ['入宅', '移徙'], related: ['安床', '修造', '扫舍'], category: 'move' },
6245
+ 搬家: { yi: ['移徙', '入宅'], related: ['安床', '修造'], category: 'move' },
6246
+ 动土: { yi: ['动土', '修造'], related: ['竖柱', '上梁'], category: 'construction' },
6247
+ 装修: { yi: ['修造', '修饰垣墙'], related: ['动土'], category: 'construction' },
6248
+ 安葬: { yi: ['安葬', '破土'], related: ['立碑', '埋穴'], category: 'burial' },
6249
+ 出行: { yi: ['出行'], related: ['上任', '赴任'], category: 'travel' },
6250
+ 祭祀: { yi: ['祭祀'], related: ['祈福', '求嗣'], category: 'worship' },
6251
+ 签约: { yi: ['立券', '交易'], related: ['纳财', '开市'], category: 'contract' },
6252
+ 求医: { yi: ['求医', '疗病'], related: ['针灸'], category: 'medical' },
6253
+ 纳财: { yi: ['纳财', '收账'], related: ['开市', '立券'], category: 'contract' },
6254
+ };
6255
+
6256
+ const ZHIXING_FAVOR = {
6257
+ marriage: ['满', '定', '成', '开'],
6258
+ business: ['开', '成', '满', '收'],
6259
+ move: ['定', '成', '开', '除'],
6260
+ construction: ['建', '除', '成', '开'],
6261
+ burial: ['除', '定', '闭', '破'],
6262
+ travel: ['建', '成', '开', '除'],
6263
+ worship: ['除', '满', '平', '定', '执', '成', '开'],
6264
+ contract: ['定', '成', '收', '开'],
6265
+ medical: ['除', '开'],
6266
+ };
6267
+
6268
+ const VERDICT = (score, ctx) => {
6269
+ if (ctx.chongSelf) {
6270
+ return { verdict: '回避', text: `当日冲${ctx.chongSelf}(当事人属相):传统择日径直排除,不进推荐与可用名单。`, excluded: true };
6271
+ }
6272
+ if (score >= 3) return { verdict: '推荐', text: '宜项与建除相配,此日对所问事项顺遂。', excluded: false };
6273
+ if (score >= 1) {
6274
+ return {
6275
+ verdict: '可用',
6276
+ text: ctx.jiHit.length ? `可考虑,但当日忌${ctx.jiHit.join('、')}:宜项加分、忌项减分,未抵消干净。` : '未见冲克,可用;若有更好的日子,优先取推荐日。',
6277
+ excluded: false,
6278
+ };
6279
+ }
6280
+ if (score >= 0) return { verdict: '平平', text: ctx.jiHit.length ? `当日忌${ctx.jiHit.join('、')},宜项不相干,不作首选。` : '宜忌不相干,无助力也无冲克。', excluded: false };
6281
+ return { verdict: '回避', text: ctx.jiHit.length ? `当日忌${ctx.jiHit.join('、')},对所问事项不宜。` : '减分项多于加分项,此日不宜。', excluded: false };
6282
+ };
6283
+
6284
+ function buildZeri(input) {
6285
+ const task = input.task;
6286
+ if (!TASKS[task]) throw new Error(`不支持的事项「${task}」。支持:${Object.keys(TASKS).join('、')}`);
6287
+ const m = /^(\d{4})-(\d{1,2})-(\d{1,2})$/.exec(String(input.startDate || '').trim());
6288
+ if (!m) throw new Error('择日需要 startDate(公历 YYYY-MM-DD)——请向用户询问起算日,不要代填');
6289
+ const start = { year: Number(m[1]), month: Number(m[2]), day: Number(m[3]) };
6290
+ const days = Math.max(1, Math.min(60, Number.isInteger(input.days) ? input.days : 30));
6291
+ const shengXiaoRaw = typeof input.shengXiao === 'string' ? input.shengXiao.trim() : '';
6292
+ const shengXiao = shengXiaoRaw || null;
6293
+ if (shengXiao && !SHENGXIAO.includes(shengXiao)) throw new Error(`shengXiao 须为生肖名:${SHENGXIAO.join('、')}`);
6294
+
6295
+ const spec = TASKS[task];
6296
+ const zhixingSet = ZHIXING_FAVOR[spec.category];
6297
+ const startJdn = toJDN(start.year, start.month, start.day);
6298
+ const list = [];
6299
+ for (let i = 0; i < days; i++) {
6300
+ const p = fromJDN(startJdn + i);
6301
+ const a = buildAlmanac(p);
6302
+ let score = 0;
6303
+ const reasons = [];
6304
+ const yiHit = spec.yi.filter((w) => a.yi.includes(w));
6305
+ if (yiHit.length) { score += 2; reasons.push(`宜${yiHit.join('、')}(+2)`); }
6306
+ const relHit = spec.related.filter((w) => a.yi.includes(w));
6307
+ if (relHit.length) { score += 1; reasons.push(`关联宜项${relHit.join('、')}(+1)`); }
6308
+ const jiHit = [...spec.yi, ...spec.related].filter((w) => a.ji.includes(w));
6309
+ if (jiHit.length) { score -= 3; reasons.push(`忌${jiHit.join('、')}(-3)`); }
6310
+ const chongSelf = shengXiao && a.chong.shengXiao === shengXiao ? shengXiao : null;
6311
+ if (chongSelf) { score -= 2; reasons.push(`冲${shengXiao}(当事人属相,径直排除)`); }
6312
+ if (zhixingSet.includes(a.zhiXing)) { score += 1; reasons.push(`建除${a.zhiXing}神利${task}(+1)`); }
6313
+ if (a.zhiXing === '破' && spec.category !== 'burial') { score -= 1; reasons.push('破日(-1)'); }
6314
+ if (a.zhiXing === '危' && spec.category !== 'travel' && spec.category !== 'worship') { score -= 1; reasons.push('危日(-1)'); }
6315
+
6316
+ const v = VERDICT(score, { chongSelf, jiHit });
6317
+ list.push({
6318
+ date: a.meta.date,
6319
+ weekday: a.meta.weekday,
6320
+ lunar: a.lunar.text,
6321
+ dayGanZhi: a.ganzhi.day,
6322
+ zhiXing: a.zhiXing,
6323
+ yi: a.yi,
6324
+ ji: a.ji,
6325
+ chong: a.chong.shengXiao,
6326
+ score,
6327
+ reasons,
6328
+ verdict: v.verdict,
6329
+ verdictText: v.text,
6330
+ ...(v.excluded ? { excluded: true } : {}),
6331
+ });
6332
+ }
6333
+
6334
+ const ranked = list.slice().sort((x, y) => y.score - x.score || (x.date < y.date ? -1 : 1));
6335
+ return {
6336
+ system: 'zeri',
6337
+ meta: {
6338
+ task,
6339
+ startDate: `${start.year}-${String(start.month).padStart(2, '0')}-${String(start.day).padStart(2, '0')}`,
6340
+ days,
6341
+ shengXiao,
6342
+ scope: shengXiao
6343
+ ? `宜忌按建除简表;已给当事人属相(${shengXiao}),凡当日冲该属相径直排除。评分仅用于排序引导。`
6344
+ : '宜忌按建除简表;未给当事人属相,未做冲属相排除。评分仅用于排序引导。',
6345
+ },
6346
+ best: ranked.filter((d) => d.verdict === '推荐' && !d.excluded).slice(0, 5).map((d) => d.date),
6347
+ usable: ranked.filter((d) => d.verdict === '可用' && !d.excluded).slice(0, 5).map((d) => d.date),
6348
+ avoid: list.filter((d) => d.verdict === '回避').map((d) => `${d.date}(${d.reasons.join(';') || '综合减分'})`),
6349
+ days: list,
6350
+ excludedCount: list.filter((d) => d.excluded).length,
6351
+ sources: ['宜忌:建除十二神通行简表', '建除适配与冲煞:传统通行口径', '排序:宜忌加减分 + 属相排除(确定性)'],
6352
+ disclaimer: '择日为传统黄历口径的本地计算,属民俗参考;评分只用于排序,不构成择日结论。',
6353
+ };
6354
+ }
6355
+
6356
+ return { TASKS, buildZeri };
6357
+ })();
6358
+
4864
6359
  __mods['interpret-plain'] = (function () {
4865
6360
 
4866
6361
  const s = (v, fallback = '') => (v === undefined || v === null || v === '' ? fallback : String(v));
@@ -6956,6 +8451,7 @@ function rectifyBirth(opts) {
6956
8451
  gender,
6957
8452
  lateZi: opts.lateZi !== false,
6958
8453
  trueSolar: opts.trueSolar !== false,
8454
+ dst: opts.dst !== false,
6959
8455
  address: typeof opts.address === 'string' ? opts.address : undefined,
6960
8456
  longitude: typeof opts.longitude === 'number' ? opts.longitude : undefined,
6961
8457
  geo: opts.geo && typeof opts.geo === 'object' ? opts.geo : undefined,
@@ -7024,6 +8520,15 @@ const { followup } = __mods.followup;
7024
8520
  const { crossCheck, crossText } = __mods.crosscheck;
7025
8521
  const { pastSignals, verifyFit } = __mods.verify;
7026
8522
  const { rectifyBirth } = __mods.rectify;
8523
+ const { buildAlmanac } = __mods.almanac;
8524
+ const { buildZeri } = __mods.zeri;
8525
+ const { buildChenggu } = __mods.chenggu;
8526
+ const { buildXiaoliuren } = __mods.xiaoliuren;
8527
+ const { buildStar } = __mods.star;
8528
+ const { buildNumerology } = __mods.numerology;
8529
+ const { buildTrend } = __mods.trend;
8530
+ const { buildFengshui } = __mods.fengshui;
8531
+ const { buildName } = __mods.name;
7027
8532
  const { fail } = __mods.fail;
7028
8533
  const { assertCivil } = __mods.validate;
7029
8534
 
@@ -7199,6 +8704,15 @@ function hepanText(r) {
7199
8704
  `对方 ${r.b.pillars.join(' ')} 日主 ${r.b.dayMaster} 日支 ${r.b.dayBranch} ${r.b.strength || ''}`,
7200
8705
  `真太阳时:你 ${solarLine(r.a)};对方 ${solarLine(r.b)}`,
7201
8706
  ...r.dimensions.map(d),
8707
+ ...(r.detail ? [
8708
+ `属相:${r.detail.yearRelation.label}——${r.detail.yearRelation.text}`,
8709
+ `日干:${r.detail.stems.text}`,
8710
+ ...(r.detail.crossBranches && r.detail.crossBranches.length
8711
+ ? [`跨盘地支:${r.detail.crossBranches.map((c) => `${c.name}(${c.a}${c.b})`).join('、')}`]
8712
+ : []),
8713
+ `年命纳音:${r.detail.nayin.a} / ${r.detail.nayin.b}(${r.detail.nayin.label})——${r.detail.nayin.text}`,
8714
+ `吉凶象:吉 ${r.detail.tally.good} / 凶 ${r.detail.tally.bad}(${r.detail.tally.lean};分档只数象,总分以上面三维度为准)`,
8715
+ ] : []),
7202
8716
  ...r.notes.map((n) => `※ ${n}`),
7203
8717
  ].join('\n');
7204
8718
  }
@@ -7292,6 +8806,7 @@ function compute(args = {}) {
7292
8806
  address: typeof args.address === 'string' ? args.address : undefined,
7293
8807
  geo: args.geo && typeof args.geo === 'object' ? args.geo : undefined,
7294
8808
  trueSolar: args.trueSolar !== false,
8809
+ dst: args.dst !== false,
7295
8810
  };
7296
8811
  const asOf = checkAsOf(args.asOf);
7297
8812
  const other = r.system === 'bazi'
@@ -7313,6 +8828,7 @@ function compute(args = {}) {
7313
8828
  longitude: typeof args.longitude === 'number' ? args.longitude : undefined,
7314
8829
  address: typeof args.address === 'string' ? args.address : undefined,
7315
8830
  geo: args.geo && typeof args.geo === 'object' ? args.geo : undefined,
8831
+ dst: args.dst !== false,
7316
8832
  nowYear: from,
7317
8833
  });
7318
8834
  r.past = {
@@ -7330,6 +8846,7 @@ function compute(args = {}) {
7330
8846
  hours: Array.isArray(args.rectifyHours) ? args.rectifyHours : undefined,
7331
8847
  lateZi: args.lateZi !== false,
7332
8848
  trueSolar: args.trueSolar !== false,
8849
+ dst: args.dst !== false,
7333
8850
  longitude: typeof args.longitude === 'number' ? args.longitude : undefined,
7334
8851
  address: typeof args.address === 'string' ? args.address : undefined,
7335
8852
  geo: args.geo && typeof args.geo === 'object' ? args.geo : undefined,
@@ -7401,6 +8918,7 @@ function computeRaw(args = {}) {
7401
8918
  geo: args.geo && typeof args.geo === 'object' ? args.geo : undefined,
7402
8919
  lateZi: args.lateZi !== false,
7403
8920
  trueSolar: args.trueSolar !== false,
8921
+ dst: args.dst !== false,
7404
8922
  nowYear: asOf ? asOf.year : undefined,
7405
8923
  });
7406
8924
  return { ...result, ...(asOf ? { asOf } : {}), precision: dt.precision, summary: baziText(result) };
@@ -7416,6 +8934,7 @@ function computeRaw(args = {}) {
7416
8934
  address: typeof args.address === 'string' ? args.address : undefined,
7417
8935
  geo: args.geo && typeof args.geo === 'object' ? args.geo : undefined,
7418
8936
  trueSolar: args.trueSolar,
8937
+ dst: args.dst !== false,
7419
8938
  asOf,
7420
8939
  });
7421
8940
  return { ...result, precision: dt.precision, summary: ziweiText(result) };
@@ -7470,6 +8989,7 @@ function computeRaw(args = {}) {
7470
8989
  gender: requireGender(args.gender, '你(主方)'),
7471
8990
  trueSolar: args.trueSolar,
7472
8991
  lateZi: args.lateZi,
8992
+ dst: args.dst !== false,
7473
8993
  address: typeof args.address === 'string' ? args.address : undefined,
7474
8994
  longitude: typeof args.longitude === 'number' ? args.longitude : undefined,
7475
8995
  geo: args.geo && typeof args.geo === 'object' ? args.geo : undefined,
@@ -7479,6 +8999,7 @@ function computeRaw(args = {}) {
7479
8999
  gender: requireGender(partner.gender, '对方'),
7480
9000
  trueSolar: partner.trueSolar,
7481
9001
  lateZi: partner.lateZi,
9002
+ dst: partner.dst !== false,
7482
9003
  address: typeof partner.address === 'string' ? partner.address : undefined,
7483
9004
  longitude: typeof partner.longitude === 'number' ? partner.longitude : undefined,
7484
9005
  geo: partner.geo && typeof partner.geo === 'object' ? partner.geo : undefined,
@@ -7532,10 +9053,248 @@ function computeRaw(args = {}) {
7532
9053
  if (args.seed !== undefined && args.seed !== null && args.seed !== '') result.seed = Number(args.seed);
7533
9054
  return { ...result, ...(asOf ? { asOf } : {}), summary: liuyaoText(result) };
7534
9055
  }
9056
+ if (system === 'almanac') {
9057
+ const dt = parseDateTime(args.datetime);
9058
+ const result = buildAlmanac({ year: dt.year, month: dt.month, day: dt.day });
9059
+ return { ...result, summary: almanacText(result) };
9060
+ }
9061
+ if (system === 'zeri') {
9062
+ if (typeof args.startDate !== 'string' || !args.startDate.trim()) {
9063
+ throw fail('E_INPUT', '择日需要 startDate(公历 YYYY-MM-DD,起算日);可另给 days(1–60,默认 30)与 shengXiao(当事人属相)');
9064
+ }
9065
+ const result = buildZeri({
9066
+ task: args.task,
9067
+ startDate: args.startDate,
9068
+ days: args.days,
9069
+ shengXiao: args.shengXiao,
9070
+ });
9071
+ return { ...result, summary: zeriText(result) };
9072
+ }
9073
+ if (system === 'chenggu') {
9074
+ const dt = parseDateTime(args.datetime);
9075
+ const hasTime = /[T\s]\d{1,2}:\d{1,2}/.test(String(args.datetime));
9076
+ const hour = Number.isInteger(args.hour) ? args.hour : hasTime ? dt.hour : null;
9077
+ if (hour === null || hour < 0 || hour > 23) {
9078
+ throw fail('E_INPUT', '称骨必须给时辰:datetime 带时分(如 1990-08-15T10:00),或额外给 hour(0–23)——缺时辰四柱不齐');
9079
+ }
9080
+ const result = buildChenggu({ year: dt.year, month: dt.month, day: dt.day, hour, minute: hasTime ? dt.minute : 0 });
9081
+ return { ...result, summary: chengguText(result) };
9082
+ }
9083
+ if (system === 'xiaoliuren') {
9084
+ const dt = args.datetime ? parseDateTime(args.datetime) : null;
9085
+ const result = buildXiaoliuren({
9086
+ mode: args.mode === 'number' ? 'number' : 'time',
9087
+ date: typeof args.date === 'string' && args.date.trim() ? args.date : dt ? `${dt.year}-${dt.month}-${dt.day}` : undefined,
9088
+ hour: Number.isInteger(args.hour) ? args.hour : dt && /[T\s]\d{1,2}:\d{1,2}/.test(String(args.datetime)) ? dt.hour : undefined,
9089
+ num1: args.num1,
9090
+ num2: args.num2,
9091
+ num3: args.num3,
9092
+ question: args.question,
9093
+ });
9094
+ return { ...result, summary: xiaoliurenText(result) };
9095
+ }
9096
+ if (system === 'star') {
9097
+ const dt = parseDateTime(args.datetime);
9098
+ const result = buildStar({ year: dt.year, month: dt.month, day: dt.day });
9099
+ return { ...result, summary: starText(result) };
9100
+ }
9101
+ if (system === 'numerology') {
9102
+ const dt = parseDateTime(args.datetime);
9103
+ const result = buildNumerology({ year: dt.year, month: dt.month, day: dt.day });
9104
+ return { ...result, summary: numerologyText(result) };
9105
+ }
9106
+ if (system === 'trend') {
9107
+ const dt = parseDateTime(args.datetime);
9108
+ const result = buildTrend({
9109
+ ...dt,
9110
+ gender: requireGender(args.gender),
9111
+ toAge: args.toAge,
9112
+ lateZi: args.lateZi !== false,
9113
+ trueSolar: args.trueSolar !== false,
9114
+ dst: args.dst !== false,
9115
+ longitude: typeof args.longitude === 'number' ? args.longitude : undefined,
9116
+ address: typeof args.address === 'string' ? args.address : undefined,
9117
+ geo: args.geo && typeof args.geo === 'object' ? args.geo : undefined,
9118
+ });
9119
+ return { ...result, precision: dt.precision, summary: trendText(result) };
9120
+ }
9121
+ if (system === 'fengshui') {
9122
+ const action = args.action === 'advise' ? 'advise' : 'natal';
9123
+ if (action === 'advise') {
9124
+ const result = buildFengshui({
9125
+ action,
9126
+ sitting: args.sitting,
9127
+ facing: args.facing,
9128
+ ...(args.datetime ? parseDateTime(args.datetime) : {}),
9129
+ ...(args.gender === 'male' || args.gender === 'female' ? { gender: args.gender } : {}),
9130
+ dst: args.dst !== false,
9131
+ });
9132
+ return { ...result, summary: fengshuiText(result) };
9133
+ }
9134
+ const dt = parseDateTime(args.datetime);
9135
+ const result = buildFengshui({
9136
+ action: 'natal',
9137
+ year: dt.year, month: dt.month, day: dt.day,
9138
+ gender: requireGender(args.gender),
9139
+ fortuneYear: Number.isInteger(args.fortuneYear) ? args.fortuneYear : undefined,
9140
+ dst: args.dst !== false,
9141
+ });
9142
+ return { ...result, summary: fengshuiText(result) };
9143
+ }
9144
+ if (system === 'name') {
9145
+ const action = args.action === 'generate' ? 'generate' : 'assess';
9146
+ const birth = args.datetime ? parseDateTime(args.datetime) : {};
9147
+ const result = buildName({
9148
+ action,
9149
+ surname: args.surname,
9150
+ givenName: args.givenName,
9151
+ limit: args.limit,
9152
+ ...birth,
9153
+ ...(args.gender === 'male' || args.gender === 'female' ? { gender: args.gender } : {}),
9154
+ ...(args.datetime ? {
9155
+ lateZi: args.lateZi !== false,
9156
+ trueSolar: args.trueSolar !== false,
9157
+ dst: args.dst !== false,
9158
+ longitude: typeof args.longitude === 'number' ? args.longitude : undefined,
9159
+ address: typeof args.address === 'string' ? args.address : undefined,
9160
+ geo: args.geo && typeof args.geo === 'object' ? args.geo : undefined,
9161
+ } : {}),
9162
+ });
9163
+ return { ...result, summary: nameText(result) };
9164
+ }
7535
9165
  if (!system) {
7536
- throw fail('E_INPUT', '缺少 system,可选:bazi(四柱八字)、ziwei(紫微斗数)、liuyao(六爻)、meihua(梅花易数)、daliuren(大六壬)、qimen(奇门遁甲)、hepan(合盘)、tarot(塔罗)。');
9166
+ throw fail('E_INPUT', '缺少 system,可选:bazi(四柱八字)、ziwei(紫微斗数)、liuyao(六爻)、meihua(梅花易数)、daliuren(大六壬)、qimen(奇门遁甲)、hepan(合盘)、tarot(塔罗)、almanac(黄历)、zeri(择日)、chenggu(称骨)、xiaoliuren(小六壬)、star(星座)、numerology(生命数字)、trend(人生 K 线)、fengshui(风水)、name(起名)。');
9167
+ }
9168
+ throw fail('E_SYSTEM', `未知体系「${system}」,可选:bazi(四柱八字)、ziwei(紫微斗数)、liuyao(六爻)、meihua(梅花易数)、daliuren(大六壬)、qimen(奇门遁甲)、hepan(合盘)、tarot(塔罗)、almanac(黄历)、zeri(择日)、chenggu(称骨)、xiaoliuren(小六壬)、star(星座)、numerology(生命数字)、trend(人生 K 线)、fengshui(风水)、name(起名)。`);
9169
+ }
9170
+
9171
+ function almanacText(r) {
9172
+ const g = r.ganzhi;
9173
+ const jq = r.jieQi && r.jieQi.next ? ` 下一节:${r.jieQi.next.name} ${r.jieQi.next.date}` : '';
9174
+ return [
9175
+ `【黄历】${r.meta.date} ${r.meta.weekday} ${r.lunar.text}`,
9176
+ `干支:${g.year}年 ${g.month}月 ${g.day}日 属${g.shengXiao}`,
9177
+ ...(r.meta.boundaryNote ? [` ${r.meta.boundaryNote}`] : []),
9178
+ `建除:${r.zhiXing}日 冲:${r.chong.text}${r.chong.sha ? `(${r.chong.sha})` : ''} 财神:${r.positions.caiShen || '—'} 喜神:${r.positions.xiShen || '—'}`,
9179
+ `宜:${r.yi.join('、')}`,
9180
+ `忌:${r.ji.join('、')}`,
9181
+ `节气:上一节 ${r.jieQi.prev ? `${r.jieQi.prev.name} ${r.jieQi.prev.date}` : '—'}${jq}`,
9182
+ r.dailyHexagram ? `每日一卦:${r.dailyHexagram.name} ${r.dailyHexagram.meaning || ''}` : '',
9183
+ `口径:${r.meta.scope}`,
9184
+ ].filter(Boolean).join('\n');
9185
+ }
9186
+
9187
+ function zeriText(r) {
9188
+ const head = `【择日·${r.meta.task}】${r.meta.startDate} 起 ${r.meta.days} 天${r.meta.shengXiao ? ` 当事人属${r.meta.shengXiao}` : '(未给属相,未做冲煞排除)'}`;
9189
+ const top = r.days.slice().sort((a, b) => b.score - a.score).slice(0, 5)
9190
+ .map((d) => `· ${d.date} ${d.weekday} ${d.verdict}(${d.score}分) 宜${d.yi.slice(0, 3).join('/')} ${d.reasons.join(';') || '无特别加减分'}`);
9191
+ return [
9192
+ head,
9193
+ `推荐日:${r.best.length ? r.best.join('、') : '本期无(都不够推荐分)'}`,
9194
+ `可用日:${r.usable.length ? r.usable.join('、') : '无'}`,
9195
+ `回避日:${r.avoid.length ? r.avoid.slice(0, 6).join('、') : '无'}`,
9196
+ '逐日排序:',
9197
+ ...top,
9198
+ `口径:${r.meta.scope}`,
9199
+ ].join('\n');
9200
+ }
9201
+
9202
+ function chengguText(r) {
9203
+ return [
9204
+ `【称骨】${r.input.solar} ${r.lunar.text}`,
9205
+ `四项:${r.weights.year.label} ${r.weights.month.label} ${r.weights.day.label} ${r.weights.hour.label}`,
9206
+ `合计:${r.total.label}(${r.verdict.band})`,
9207
+ `批语:${r.verdict.text}`,
9208
+ '口径:骨重为通行表口径,批语为白话自撰(非原版称骨歌诀原文)。',
9209
+ ].join('\n');
9210
+ }
9211
+
9212
+ function xiaoliurenText(r) {
9213
+ return [
9214
+ `【小六壬·${r.meta.mode}】${r.meta.question ? `所问:${r.meta.question}` : ''}`,
9215
+ r.meta.originText,
9216
+ `落宫:${r.steps.join(' → ')}`,
9217
+ `断语:${r.result.palace}(${r.result.luck}) ${r.result.text}`,
9218
+ ].join('\n');
9219
+ }
9220
+
9221
+ function starText(r) {
9222
+ return [
9223
+ `【星座】${r.meta.date} ${r.sign.name}(${r.sign.element}·守护星${r.sign.planet})`,
9224
+ `区间:${r.sign.range} 关键词:${r.sign.keywords} 契合:${r.sign.compatible}`,
9225
+ `当日基调:${r.daily.tone}`,
9226
+ r.daily.dims.map((d) => `· ${d.name}:${'★'.repeat(d.stars)}${'☆'.repeat(5 - d.stars)} ${d.text}`).join('\n'),
9227
+ `幸运色:${r.daily.luckyColor} 幸运数字:${r.daily.luckyNumber}`,
9228
+ '口径:当日基调由日期哈希确定性生成(同日同结果),纯娱乐向。',
9229
+ ].join('\n');
9230
+ }
9231
+
9232
+ function numerologyText(r) {
9233
+ return [
9234
+ `【生命数字】${r.meta.date}(西方数字学,与八字/紫微不可互证)`,
9235
+ ...r.parts.map((p) => `${p.name}:${p.number}(${p.meaning.title}) ${p.meaning.keywords}\n 倾向:${p.meaning.tendency}\n 课题:${p.meaning.lesson}`),
9236
+ ].join('\n');
9237
+ }
9238
+
9239
+ function trendText(r) {
9240
+ const s = r.meta.strength;
9241
+ const dm = r.meta.dayMaster;
9242
+ const dmText = dm && typeof dm === 'object' ? `${dm.stem}(${dm.element})` : String(dm || '');
9243
+ return [
9244
+ `【人生 K 线】${r.meta.birth} ${r.meta.gender} 日主 ${dmText} 旺衰 ${s.level}(${s.score})`,
9245
+ r.meta.boundaryNote ? `※ ${r.meta.boundaryNote}` : '',
9246
+ `【大运分段】${r.segments.map((g) => `${g.ganZhi} ${g.startAge}-${g.endAge}岁`).join(' ')}`,
9247
+ `【走势】${Array.isArray(r.sparkline) ? r.sparkline.join(' ') : r.sparkline}`,
9248
+ ...(r.highlights && r.highlights.best && r.highlights.best.length
9249
+ ? [`【关键年】最佳 ${r.highlights.best.join(';')}${r.highlights.worst && r.highlights.worst.length ? `\n最需留意 ${r.highlights.worst.join(';')}` : ''}`]
9250
+ : []),
9251
+ `※ ${r.disclaimer}`,
9252
+ ].filter(Boolean).join('\n');
9253
+ }
9254
+
9255
+ function fengshuiText(r) {
9256
+ if (r.meta.action === 'advise') {
9257
+ const match = r.mingMatch
9258
+ ? [`【命宅配】${r.mingMatch.text}`]
9259
+ : [`※ ${r.meta.birthNote || '未提供生辰,未做命宅配。'}`];
9260
+ return [
9261
+ `【宅卦】坐 ${r.meta.sitting} 向 ${r.meta.facing} ${r.meta.houseGua}宅(${r.meta.houseGroup})`,
9262
+ `【八星】${r.houseStars.map((x) => `${x.star}·${x.direction}${x.palace ? `(${x.palace})` : ''}`).join(' ')}`,
9263
+ `【门】${r.layout.door}`,
9264
+ `【灶】${r.layout.stove}`,
9265
+ `【床】${r.layout.bed}`,
9266
+ `【厕】${r.layout.toilet}`,
9267
+ ...match,
9268
+ `※ ${r.disclaimer}`,
9269
+ ].join('\n');
9270
+ }
9271
+ const g = r.mingGua;
9272
+ return [
9273
+ `【命卦】${g.num}${g.gua}(${g.group}) 方位 ${g.direction}`,
9274
+ `【四吉】${r.luckyDirections.map((x) => `${x.star}·${x.direction}${x.palace ? `(${x.palace})` : ''}`).join(' ')}`,
9275
+ `【四凶】${r.unluckyDirections.map((x) => `${x.star}·${x.direction}`).join(' ')}`,
9276
+ ...(r.yearlyFlying
9277
+ ? [`【${r.meta.fortuneYear} 年九宫飞星】中宫 ${r.yearlyFlying.centerName};${r.yearlyFlying.palaces.filter((p) => p.palace !== '中').map((p) => `${p.palace}${p.name}`).join(' ')}`]
9278
+ : [`※ ${r.meta.note || ''}`]),
9279
+ `※ ${r.disclaimer}`,
9280
+ ].filter(Boolean).join('\n');
9281
+ }
9282
+
9283
+ function nameText(r) {
9284
+ if (r.action === 'generate') {
9285
+ return [
9286
+ `【候选名】姓 ${r.surname}(${r.gender},喜用 ${r.bazi.favorable.join('、')})${r.bazi.basis}`,
9287
+ ...r.candidates.map((c) => `${c.rank}. ${c.name} ${c.chars.map((x) => `${x.char}(${x.element})${x.meaning}`).join(' ')}`),
9288
+ ...r.notes.map((n) => `※ ${n}`),
9289
+ ].join('\n');
7537
9290
  }
7538
- throw fail('E_SYSTEM', `未知体系「${system}」,可选:bazi(四柱八字)、ziwei(紫微斗数)、liuyao(六爻)、meihua(梅花易数)、daliuren(大六壬)、qimen(奇门遁甲)、hepan(合盘)、tarot(塔罗)。`);
9291
+ return [
9292
+ `【名字评估】${r.fullName}`,
9293
+ ...r.chars.map((c) => `· ${c.char}:五行 ${c.element}${c.element !== '未收录' ? `(喜用${c.favorable ? '中' : '外'})` : ''},${c.meaning}`),
9294
+ r.bazi ? `【八字】日主 ${r.bazi.dayMaster && r.bazi.dayMaster.stem ? `${r.bazi.dayMaster.stem}(${r.bazi.dayMaster.element})` : r.bazi.dayMaster},旺衰 ${r.bazi.strength.level}(${r.bazi.strength.score}),${r.bazi.basis}` : '',
9295
+ `【结论】${r.verdict}`,
9296
+ ...r.notes.map((n) => `※ ${n}`),
9297
+ ].filter(Boolean).join('\n');
7539
9298
  }
7540
9299
 
7541
9300
  function renderText(result) {
@@ -7564,7 +9323,7 @@ function renderText(result) {
7564
9323
  return parts.filter(Boolean).join('\n\n');
7565
9324
  }
7566
9325
 
7567
- return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAddress, resolveAddress, buildBazi, buildLiuyao, tossLines, followup, pastSignals, verifyFit, rectifyBirth, crossCheck, crossText, parseDateTime, compute, interpretText, renderText };
9326
+ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAddress, resolveAddress, buildBazi, buildLiuyao, tossLines, followup, pastSignals, verifyFit, rectifyBirth, crossCheck, crossText, buildAlmanac, buildZeri, buildChenggu, buildXiaoliuren, buildStar, buildNumerology, buildTrend, buildFengshui, buildName, parseDateTime, compute, interpretText, renderText };
7568
9327
  })();
7569
9328
 
7570
9329
  const Engine = __mods.index;
@@ -7600,9 +9359,9 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
7600
9359
  (function () {
7601
9360
  const CSS = `
7602
9361
  .pp,.mp-it{--ink:#1b1b1b;--ink2:#3f3a33;--muted:#6f6a60;--label:#8a8378;--faint:#a9a296;
7603
- --line:#c8c1b2;--dimline:#b9b2a4;--hair:#efe9dc;
7604
- --bg:#fffdf8;--card:#fffefb;--band:#f7f3ea;--hover:#f7f2e5;--hover2:#f2ece0;
7605
- --tri:#f9f3e6;--soul:#fdf3f1;--sel:#fff5f2;--link:#fbeee9;
9362
+ --line:#c8c1b2;--dimline:#b9b2a4;--hair:#e9e2d3;
9363
+ --bg:#f6f1e6;--card:#fffdf8;--band:#fbf7ee;--hover:#f3ede1;--hover2:#ece5d6;
9364
+ --tri:#f3e8cd;--soul:#fae4dc;--sel:#fceee7;--link:#f9ece0;
7606
9365
  --red:#b3261e;--red-rgb:179,38,30;--lu:#1f6f3f;--lu-rgb:31,111,63;
7607
9366
  --quan:#8a5a00;--quan-rgb:138,90,0;--ke:#1f4e79;--ke-rgb:31,78,121;--ji:#b3261e;
7608
9367
  --br-good:#4c7a5c;--br-bad:#9c5b56;--on-accent:#fff;--wx-metal:#8a6b1f;
@@ -7612,8 +9371,8 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
7612
9371
  @media screen and (prefers-color-scheme:dark){
7613
9372
  .pp,.mp-it{--ink:#eae5d9;--ink2:#d5cfc2;--muted:#aca598;--label:#a49c8e;--faint:#928b7e;
7614
9373
  --line:#514a3e;--dimline:#5f5849;--hair:#2f2b23;
7615
- --bg:#15140f;--card:#1e1c16;--band:#262218;--hover:#2a251b;--hover2:#312c21;
7616
- --tri:#231f16;--soul:#2e1e1a;--sel:#3a231d;--link:#2e211b;
9374
+ --bg:#12110c;--card:#211f18;--band:#282419;--hover:#2e2a1f;--hover2:#383322;
9375
+ --tri:#332913;--soul:#3a231c;--sel:#43271f;--link:#33241c;
7617
9376
  --red:#f08569;--red-rgb:240,133,105;--lu:#74c795;--lu-rgb:116,199,149;
7618
9377
  --quan:#e2a95c;--quan-rgb:226,169,92;--ke:#86bcea;--ke-rgb:134,188,234;--ji:#f08569;
7619
9378
  --br-good:#79c79a;--br-bad:#e09a8c;--on-accent:#14130f;--wx-metal:#dcb273;
@@ -7751,13 +9510,15 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
7751
9510
  .pp-ssi.neutral{color:var(--quan);border-color:rgba(var(--quan-rgb),.3)}
7752
9511
  .pp-ssi em{font-style:normal;font-size:12px;color:var(--muted);margin-left:2px}
7753
9512
 
7754
- .mp-hex{display:flex;gap:16%;height:11px;width:104px;flex:0 0 auto;color:var(--ink);align-items:stretch}
7755
- .mp-hex i{flex:1;background:currentColor;border-radius:1.5px}
7756
- .mp-hex.yin{gap:18%}
9513
+ .mp-hex{display:block;width:104px;height:12px;flex:0 0 auto;color:var(--ink);overflow:visible}
7757
9514
  .mp-hex.hex-on{color:var(--red)}
7758
9515
  .mp-hex.dim{color:var(--dimline)}
7759
9516
  .mp-hex.sm{width:74px;height:9px}
7760
9517
  .mp-hex.md{width:88px;height:10px}
9518
+ .mp-hy-ln{display:flex;align-items:center;gap:4px;min-width:0}
9519
+ .mp-hy-mv{flex:0 0 10px;font-style:normal;font-size:11px;line-height:1;color:var(--red);text-align:center}
9520
+ .mp-wxi{display:inline-block;width:12px;height:12px;vertical-align:-2px;margin-right:3px;flex:0 0 auto}
9521
+ .mp-trig{display:inline-block;width:14px;height:10px;vertical-align:-1px;margin-right:4px;color:currentColor}
7761
9522
 
7762
9523
  .mp-hy-wrap{display:flex;gap:22px;flex-wrap:wrap;align-items:flex-start}
7763
9524
  .mp-hy-box{border:1px solid var(--line);background:var(--card);padding:12px 16px 14px;min-width:250px}
@@ -7765,7 +9526,7 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
7765
9526
  .mp-hy-h b{color:var(--red);font-size:14px}
7766
9527
  .mp-hy-h span{color:var(--muted);font-size:12px}
7767
9528
  .mp-hy{display:flex;flex-direction:column;gap:6px}
7768
- .mp-hy-row{display:grid;grid-template-columns:18px 30px 112px 60px 54px 40px;gap:8px;align-items:center;font-size:13px;font-family:system-ui,sans-serif;padding:1px 0}
9529
+ .mp-hy-row{display:grid;grid-template-columns:18px 30px 128px 60px 54px 40px;gap:8px;align-items:center;font-size:13px;font-family:system-ui,sans-serif;padding:1px 0}
7769
9530
  .mp-hy-sy{color:var(--red);font-size:12px;font-weight:700}
7770
9531
  .mp-hy-nm{color:var(--muted);font-size:12px}
7771
9532
  .mp-hy-gz{letter-spacing:.04em}
@@ -7873,6 +9634,28 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
7873
9634
  .mp-dl-c .dl-t3{font-size:12.5px;letter-spacing:.2em;margin-top:3px}
7874
9635
  .mp-dl-legend{display:flex;gap:12px;justify-content:center;font-size:12px;color:var(--muted);font-family:system-ui,sans-serif;margin-top:6px}
7875
9636
 
9637
+ .mp-curve{margin-top:8px;border:1px solid var(--line);border-radius:8px;background:var(--card);padding:10px 12px 8px}
9638
+ .mp-curve-h{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap;font-size:12px;color:var(--label);font-family:system-ui,sans-serif;margin-bottom:4px}
9639
+ .mp-curve-h b{font-size:12.5px;color:var(--ink);font-weight:600}
9640
+ .mp-curve-h span{font-size:11.5px;color:var(--muted)}
9641
+ .mp-curve svg{display:block;width:100%;height:auto;overflow:visible}
9642
+ .mp-curve .cv-band{opacity:.09}
9643
+ .mp-curve .cv-band.b-good{fill:var(--lu)}
9644
+ .mp-curve .cv-band.b-bad{fill:var(--red)}
9645
+ .mp-curve .cv-band.b-flat{fill:var(--muted)}
9646
+ .mp-curve .cv-ax{stroke:var(--dimline);stroke-width:.6}
9647
+ .mp-curve .cv-ax.zero{stroke:var(--line);stroke-width:.9}
9648
+ .mp-curve .cv-lbl{fill:var(--muted);font-size:8px;font-family:system-ui,sans-serif}
9649
+ .mp-curve .cv-lbl.age{font-size:8.5px}
9650
+ .mp-curve .cv-dy{fill:none;stroke:var(--quan);stroke-width:1.6;stroke-linejoin:round}
9651
+ .mp-curve .cv-dyfill{fill:var(--quan);opacity:.10}
9652
+ .mp-curve .cv-ln{fill:none;stroke:var(--ke);stroke-width:1.4;stroke-linejoin:round;stroke-linecap:round}
9653
+ .mp-curve .cv-dot{r:2}
9654
+ .mp-curve .cv-dot.now{r:3.4;stroke:var(--card);stroke-width:1.2}
9655
+ .mp-curve .cv-legend{display:flex;gap:12px;flex-wrap:wrap;margin-top:5px;font-size:11px;color:var(--muted);font-family:system-ui,sans-serif}
9656
+ .mp-curve .cv-legend i{display:inline-block;width:14px;height:2px;vertical-align:3px;margin-right:4px}
9657
+ .mp-curve .cv-tip{font-size:11px;color:var(--muted);line-height:1.6;margin-top:4px}
9658
+ @media (max-width:520px){.mp-curve .cv-lbl.age{font-size:9px}}
7876
9659
  .mp-tt{padding:16px}
7877
9660
  .mp-tt-head{font-size:12.5px;color:var(--muted);margin-bottom:12px;letter-spacing:.04em}
7878
9661
  .mp-tt-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(172px,1fr));gap:10px}
@@ -7994,6 +9777,9 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
7994
9777
  .mp-fu-h{font-size:12px;font-weight:700;letter-spacing:.08em;color:var(--lu);margin-bottom:4px;font-family:system-ui,sans-serif}
7995
9778
  .mp-fu.miss .mp-fu-h{color:var(--red)}
7996
9779
  .mp-fu-ev{margin-top:5px;font-size:12px;color:var(--muted);font-family:system-ui,sans-serif}
9780
+ .mp-quick{margin:12px 16px;border:1px solid var(--line);border-radius:8px;background:var(--card);padding:12px 14px}
9781
+ .mp-quick-h{font-size:13px;color:var(--lu);margin-bottom:6px;font-family:system-ui,sans-serif}
9782
+ .mp-quick-pre{margin:0;white-space:pre-wrap;word-break:break-word;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:13px;line-height:1.9;color:var(--ink2)}
7997
9783
  .mp-it-dis{padding:11px 16px;border-top:1px solid var(--line);background:var(--band);font-size:12px;line-height:1.75;color:var(--muted);font-family:system-ui,sans-serif}
7998
9784
 
7999
9785
  @media (max-width:640px){
@@ -8040,9 +9826,9 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
8040
9826
  .mp-hy-box{min-width:0;width:100%}
8041
9827
  .mp-hy-row{grid-template-columns:14px 32px minmax(64px,1fr) 46px 42px 32px;gap:6px;font-size:12.5px}
8042
9828
  .mp-hy-row.hy-plain{grid-template-columns:32px minmax(72px,1fr)}
8043
- .mp-hex{width:auto;min-width:64px;height:12px}
8044
- .mp-hex.sm{min-width:56px;height:10px}
8045
- .mp-hex.md{min-width:60px;height:11px}
9829
+ .mp-hex{width:100%;max-width:104px;min-width:56px;height:12px;flex:1 1 auto}
9830
+ .mp-hex.sm{max-width:74px;min-width:52px;height:10px}
9831
+ .mp-hex.md{max-width:88px;min-width:56px;height:11px}
8046
9832
  .mp-mh{padding:10px}
8047
9833
  .mp-mh-main{gap:12px}
8048
9834
  .mp-hexbox.hb-mini{min-width:100px}
@@ -8105,9 +9891,9 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
8105
9891
  .mp-hy-box{min-width:0;width:100%}
8106
9892
  .mp-hy-row{grid-template-columns:14px 32px minmax(64px,1fr) 46px 42px 32px;gap:6px;font-size:12.5px}
8107
9893
  .mp-hy-row.hy-plain{grid-template-columns:32px minmax(72px,1fr)}
8108
- .mp-hex{width:auto;min-width:64px;height:12px}
8109
- .mp-hex.sm{min-width:56px;height:10px}
8110
- .mp-hex.md{min-width:60px;height:11px}
9894
+ .mp-hex{width:100%;max-width:104px;min-width:56px;height:12px;flex:1 1 auto}
9895
+ .mp-hex.sm{max-width:74px;min-width:52px;height:10px}
9896
+ .mp-hex.md{max-width:88px;min-width:56px;height:11px}
8111
9897
  .mp-mh{padding:10px}
8112
9898
  .mp-mh-main{gap:12px}
8113
9899
  .mp-hexbox.hb-mini{min-width:100px}
@@ -8171,7 +9957,44 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
8171
9957
 
8172
9958
  const esc = (s) => String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
8173
9959
 
8174
- const hexBar = (yang, moving, size) => `<span class="mp-hex${yang ? '' : ' yin'}${moving ? ' on' : ''}${size ? ' ' + size : ''}">${yang ? '<i></i>' : '<i></i><i></i>'}</span>`;
9960
+ const hexBar = (yang, moving, size) => {
9961
+ const W = 104, H = 12, bh = 7, y = (H - bh) / 2, r = 1.6;
9962
+ const gap = 18, seg = Math.round((W - gap) / 2);
9963
+ const bars = yang
9964
+ ? `<rect x="0" y="${y}" width="${W}" height="${bh}" rx="${r}" fill="currentColor"/>`
9965
+ : `<rect x="0" y="${y}" width="${seg}" height="${bh}" rx="${r}" fill="currentColor"/>`
9966
+ + `<rect x="${seg + gap}" y="${y}" width="${W - seg - gap}" height="${bh}" rx="${r}" fill="currentColor"/>`;
9967
+ return `<svg class="mp-hex${moving ? ' hex-on' : ''}${size ? ' ' + size : ''}" viewBox="0 0 ${W} ${H}" preserveAspectRatio="none" aria-hidden="true" focusable="false">${bars}</svg>`;
9968
+ };
9969
+
9970
+ const WX_ICON = {
9971
+ 木: 'M6 1.6V10.8M6 5.2 2.6 2.2M6 7.4 9.4 4.4',
9972
+ 火: 'M6 1.4C7.6 3.6 9.2 5.4 9.2 7.6A3.2 3.2 0 0 1 2.8 7.6C2.8 5.4 4.4 3.6 6 1.4Z',
9973
+ 土: 'M1.6 9.6h8.8M6 2.4v7.2M3.4 5.6h5.2',
9974
+ 金: 'M6 1.2 10.6 6 6 10.8 1.4 6Z',
9975
+ 水: 'M1.4 7.2C2.9 4.2 4.5 10.2 6 7.2 7.5 4.2 9.1 10.2 10.6 7.2',
9976
+ };
9977
+ const wxIcon = (el) => {
9978
+ const d = WX_ICON[el];
9979
+ if (!d) return '';
9980
+ return `<svg class="mp-wxi" viewBox="0 0 12 12" aria-hidden="true" focusable="false"><path d="${d}" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
9981
+ };
9982
+
9983
+ const triGlyph = (name) => {
9984
+ const bits = (TRI_BITS[name] || [0, 0, 0]).slice().reverse();
9985
+ const rows = bits.map((b, i) => {
9986
+ const y = i * 4;
9987
+ return b
9988
+ ? `<rect x="0" y="${y}" width="14" height="2" rx="0.6" fill="currentColor"/>`
9989
+ : `<rect x="0" y="${y}" width="5.6" height="2" rx="0.6" fill="currentColor"/>`
9990
+ + `<rect x="8.4" y="${y}" width="5.6" height="2" rx="0.6" fill="currentColor"/>`;
9991
+ }).join('');
9992
+ return `<svg class="mp-trig" viewBox="0 0 14 10" aria-hidden="true" focusable="false">${rows}</svg>`;
9993
+ };
9994
+
9995
+ pp._hexBar = hexBar;
9996
+ pp._wxIcon = wxIcon;
9997
+ pp._triGlyph = triGlyph;
8175
9998
 
8176
9999
  function starsHTML(stars, viewHua) {
8177
10000
  if (!stars || !stars.length) return '';
@@ -8472,10 +10295,13 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
8472
10295
  const sh = o.shiYing
8473
10296
  ? (l.position === o.shi ? '<span class="mp-hy-sy">世</span>' : l.position === o.ying ? '<span class="mp-hy-sy">应</span>' : '<span></span>')
8474
10297
  : '<span></span>';
10298
+ const mv = l.moving
10299
+ ? `<i class="mp-hy-mv" title="动爻">${yang ? '○' : '×'}</i>`
10300
+ : '<i class="mp-hy-mv"></i>';
8475
10301
  return `<div class="mp-hy-row">
8476
10302
  ${sh}
8477
10303
  <span class="mp-hy-nm">${l.name || POS_NAME[l.position - 1]}</span>
8478
- ${hexBar(yang, l.moving)}
10304
+ <span class="mp-hy-ln">${hexBar(yang, l.moving)}${mv}</span>
8479
10305
  <span class="mp-hy-gz">${esc(l.ganzhi || '')}</span>
8480
10306
  <span class="mp-hy-lq">${esc(l.liuqin || '')}</span>
8481
10307
  <span class="mp-hy-ls">${esc(l.liushen || '')}</span>
@@ -8515,7 +10341,7 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
8515
10341
  const lowerLines = benBits.slice(3);
8516
10342
  const movingInUpper = r.moving >= 4;
8517
10343
  const trib = (label, tri, lines, isBody) => `<div class="mp-trib">
8518
- <div class="mp-trib-h">${label} ${tri}${TRI_EL(tri)}<span class="mp-tritag ${isBody ? 'tritag-ti' : 'tritag-yong'}">${isBody ? '体' : '用'}</span></div>
10344
+ <div class="mp-trib-h">${label} ${triGlyph(tri)}${tri}${TRI_EL(tri)}<span class="mp-tritag ${isBody ? 'tritag-ti' : 'tritag-yong'}">${isBody ? '体' : '用'}</span></div>
8519
10345
  <div class="mp-hy" style="gap:5px">${hexRows(lines, { plain: true })}</div>
8520
10346
  </div>`;
8521
10347
  const mainBox = `<div class="mp-hexbox">
@@ -8739,6 +10565,106 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
8739
10565
  </div>`;
8740
10566
  };
8741
10567
 
10568
+ pp.curveHTML = function (r) {
10569
+ const dyList = (r && r.dayun && Array.isArray(r.dayun.list)) ? r.dayun.list : [];
10570
+ const lnList = (r && Array.isArray(r.liunian)) ? r.liunian : [];
10571
+ if (dyList.length < 2) return '';
10572
+
10573
+ const num = (v) => (typeof v === 'number' && isFinite(v) ? v : 0);
10574
+ const dyScore = (d) => (d.luck ? num(d.luck.score) : 0);
10575
+
10576
+ const VMAX = 3.2;
10577
+ const VMIN = -2.2;
10578
+ const startAge = num(r.dayun.startAge && r.dayun.startAge.years);
10579
+ const dyEnd = dyList.length
10580
+ ? dyList[dyList.length - 1].startAge + 10
10581
+ : startAge + 80;
10582
+ const lnAges = lnList.map((l) => num(l.age)).filter((a) => a > 0);
10583
+ const a0 = Math.min(startAge, ...(lnAges.length ? lnAges : [startAge]));
10584
+ const a1 = Math.max(dyEnd, ...(lnAges.length ? lnAges : [dyEnd]));
10585
+ const span = Math.max(1, a1 - a0);
10586
+
10587
+ const W = 640, H = 190;
10588
+ const M = { t: 14, r: 16, b: 26, l: 30 };
10589
+ const pw = W - M.l - M.r;
10590
+ const ph = H - M.t - M.b;
10591
+ const X = (age) => M.l + ((age - a0) / span) * pw;
10592
+ const Y = (v) => M.t + ph - ((v - VMIN) / (VMAX - VMIN)) * ph;
10593
+
10594
+ const BANDS = [
10595
+ { from: 1.2, to: VMAX, cls: 'b-good' },
10596
+ { from: 0.55, to: 1.2, cls: 'b-good' },
10597
+ { from: -0.8, to: 0.55, cls: 'b-flat' },
10598
+ { from: -1.45, to: -0.8, cls: 'b-bad' },
10599
+ { from: VMIN, to: -1.45, cls: 'b-bad' },
10600
+ ];
10601
+ const bands = BANDS.map((b) =>
10602
+ `<rect class="cv-band ${b.cls}" x="${M.l}" y="${Y(b.to).toFixed(1)}" width="${pw}" height="${(Y(b.from) - Y(b.to)).toFixed(1)}"/>`).join('');
10603
+
10604
+ const stepW = (10 / span) * pw;
10605
+ const dyPts = [];
10606
+ dyList.forEach((d, i) => {
10607
+ const a = num(d.startAge);
10608
+ const v = dyScore(d);
10609
+ const xL = Math.max(M.l, X(a));
10610
+ const xR = Math.min(M.l + pw, X(a + 10));
10611
+ dyPts.push(`${xL.toFixed(1)},${Y(v).toFixed(1)}`);
10612
+ dyPts.push(`${xR.toFixed(1)},${Y(v).toFixed(1)}`);
10613
+ });
10614
+ const dyLine = dyPts.length
10615
+ ? `<polyline class="cv-dy" points="${dyPts.join(' ')}"/>` : '';
10616
+ const dyFill = dyPts.length
10617
+ ? `<polygon class="cv-dyfill" points="${M.l},${(M.t + ph).toFixed(1)} ${dyPts.join(' ')} ${(M.l + pw).toFixed(1)},${(M.t + ph).toFixed(1)}"/>` : '';
10618
+
10619
+ const lnPts = lnList
10620
+ .filter((l) => l.luck && typeof l.luck.score === 'number')
10621
+ .map((l) => ({ x: X(num(l.age)), y: Y(num(l.luck.score)), y0: num(l.year), gz: l.ganzhi, tier: l.luck.tier, v: num(l.luck.score), age: num(l.age) }));
10622
+ const lnLine = lnPts.length > 1
10623
+ ? `<polyline class="cv-ln" points="${lnPts.map((p) => `${p.x.toFixed(1)},${p.y.toFixed(1)}`).join(' ')}"/>` : '';
10624
+
10625
+ const TIER_FILL = { 大吉: 'var(--lu)', 吉: 'var(--lu)', 平: 'var(--muted)', 忌: 'var(--red)', 大忌: 'var(--red)' };
10626
+ const curYear = lnPts.length ? Math.min(...lnPts.map((p) => p.y0)) : null;
10627
+ const dots = lnPts.map((p) => {
10628
+ const isNow = p.y0 === curYear;
10629
+ return `<circle class="cv-dot${isNow ? ' now' : ''}" cx="${p.x.toFixed(1)}" cy="${p.y.toFixed(1)}" fill="${TIER_FILL[p.tier] || 'var(--muted)'}"><title>${esc(`${p.y0}年(${p.age}岁)${p.gz} ${p.tier} ${p.v}`)}</title></circle>`;
10630
+ }).join('');
10631
+
10632
+ const axes = `<line class="cv-ax zero" x1="${M.l}" y1="${Y(0).toFixed(1)}" x2="${M.l + pw}" y2="${Y(0).toFixed(1)}"/>`
10633
+ + `<line class="cv-ax" x1="${M.l}" y1="${M.t}" x2="${M.l}" y2="${M.t + ph}"/>`;
10634
+ const tickStep = span > 60 ? 10 : 5;
10635
+ const ticks = [];
10636
+ for (let a = Math.ceil(a0 / tickStep) * tickStep; a <= a1; a += tickStep) {
10637
+ ticks.push(`<text class="cv-lbl age" x="${X(a).toFixed(1)}" y="${(M.t + ph + 13).toFixed(1)}" text-anchor="middle">${a}岁</text>`);
10638
+ }
10639
+ const yLabels = `<text class="cv-lbl" x="${(M.l - 5).toFixed(1)}" y="${(Y(2.4) + 3).toFixed(1)}" text-anchor="end">吉</text>`
10640
+ + `<text class="cv-lbl" x="${(M.l - 5).toFixed(1)}" y="${(Y(0) + 3).toFixed(1)}" text-anchor="end">平</text>`
10641
+ + `<text class="cv-lbl" x="${(M.l - 5).toFixed(1)}" y="${(Y(-1.8) + 3).toFixed(1)}" text-anchor="end">忌</text>`;
10642
+
10643
+ const sorted = dyList.slice().sort((a, b) => dyScore(b) - dyScore(a));
10644
+ const best = sorted[0];
10645
+ const worst = sorted[sorted.length - 1];
10646
+ const mark = best && dyScore(best) > 0.55
10647
+ ? `<text class="cv-lbl" x="${X(num(best.startAge) + 5).toFixed(1)}" y="${(Y(dyScore(best)) - 5).toFixed(1)}" text-anchor="middle">▲${best.startAge}岁 ${best.luck.tier}</text>` : '';
10648
+ const markLo = worst && dyScore(worst) < -0.8
10649
+ ? `<text class="cv-lbl" x="${X(num(worst.startAge) + 5).toFixed(1)}" y="${(Y(dyScore(worst)) + 11).toFixed(1)}" text-anchor="middle">▼${worst.startAge}岁 ${worst.luck.tier}</text>` : '';
10650
+
10651
+ const legend = `<div class="cv-legend">
10652
+ <span><i style="background:var(--quan)"></i>大运(阶梯,一步十年)</span>
10653
+ <span><i style="background:var(--ke)"></i>流年(逐年折线)</span>
10654
+ <span>绿带=吉 灰带=平 红带=忌</span>
10655
+ </div>`;
10656
+
10657
+ const tip = `<div class="cv-tip">曲线只呈现引擎已给出的行运评分(${VMIN} ~ ${VMAX}),不预测具体事件;档位与依据见下方「大运 / 流年」各节。悬停看逐年详情。</div>`;
10658
+
10659
+ return `<div class="mp-curve">
10660
+ <div class="mp-curve-h"><b>人生运势曲线</b><span>${a0}–${a1} 岁 ${r.dayun.rule},${startAge} 岁起运</span></div>
10661
+ <svg viewBox="0 0 ${W} ${H}" role="img" aria-label="人生运势曲线:${a0} 到 ${a1} 岁的大运与流年评分走势">
10662
+ ${bands}${axes}${dyFill}${dyLine}${lnLine}${dots}${ticks.join('')}${yLabels}${mark}${markLo}
10663
+ </svg>
10664
+ ${legend}${tip}
10665
+ </div>`;
10666
+ };
10667
+
8742
10668
  pp.baziHTML = function (r) {
8743
10669
  const cols = [['year', '年柱'], ['month', '月柱'], ['day', '日柱'], ['hour', '时柱']];
8744
10670
  const SHORT = { year: '年', month: '月', day: '日', hour: '时' };
@@ -8751,7 +10677,7 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
8751
10677
  偏印: 'tg-yin', 正印: 'tg-yin',
8752
10678
  };
8753
10679
  const tg = (name) => `<span class="tg ${TG[name] || ''}">${name}</span>`;
8754
- const el = (e) => `<span class="wx-${ELC[e] || 'earth'}">${e}</span>`;
10680
+ const el = (e) => `<span class="wx-${ELC[e] || 'earth'}">${wxIcon(e)}${e}</span>`;
8755
10681
  const ssTag = (x) => `<span class="ss-s ${x.luck || 'neutral'}" title="${esc(`${x.cat} · ${x.via}查${x.target} | ${x.rule}`)}">${x.name}</span>`;
8756
10682
  const line = (label, fn) => `<tr><td class="bz-lbl">${label}</td>${cols.map(([k]) => `<td data-c="${SHORT[k]}">${fn(r.pillars[k], k)}</td>`).join('')}</tr>`;
8757
10683
  const head = `<thead><tr><th></th>${cols.map(([k, n]) => `<th${k === 'day' ? ' class="bz-hdr-day"' : ''}>${n}</th>`).join('')}</tr></thead>`;
@@ -8786,7 +10712,7 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
8786
10712
  : '<span class="pp-dim">—</span>')),
8787
10713
  wxRow,
8788
10714
  ].join('');
8789
- const wx = Object.entries(r.wuxing).map(([k, v]) => `<span class="pp-chip wx-${ELC[k]}">${k} ${v}</span>`).join('');
10715
+ const wx = Object.entries(r.wuxing).map(([k, v]) => `<span class="pp-chip wx-${ELC[k]}">${wxIcon(k)}${k} ${v}</span>`).join('');
8790
10716
 
8791
10717
  const fr = r.frame || {};
8792
10718
  const tierCls = (t) => (t === '大吉' || t === '吉' ? 'ok' : t === '大忌' || t === '忌' ? 'warn' : '');
@@ -8824,7 +10750,7 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
8824
10750
  ${sect('格局', gj)}
8825
10751
  ${sect('用神', ys)}
8826
10752
  ${sect('五行与十神', `<div class="pp-chips">${wx}</div>${gods}`)}
8827
- ${sect('大运', `<div class="pp-chips">${dy}</div>`)}
10753
+ ${sect('大运', `${pp.curveHTML(r)}<div class="pp-chips">${dy}</div>`)}
8828
10754
  ${ln ? sect('流年', ln) : ''}
8829
10755
  ${r.palaces ? sect('胎元与命身宫', `<div class="pp-chips">
8830
10756
  <span class="pp-chip" title="${r.palaces.taiyuan.note}">胎元 ${r.palaces.taiyuan.ganzhi}</span>
@@ -8925,6 +10851,21 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
8925
10851
  return (html || '') + pp.interpretHTML(it, opt);
8926
10852
  };
8927
10853
 
10854
+ pp.quickHTML = function (r) {
10855
+ if (!r) return '';
10856
+ const LABEL = {
10857
+ almanac: '黄历', zeri: '择日', chenggu: '称骨', xiaoliuren: '小六壬',
10858
+ star: '星座', numerology: '生命数字', trend: '人生 K 线', fengshui: '风水', name: '起名',
10859
+ };
10860
+ const title = LABEL[r.system] || '速查';
10861
+ const text = r.summary || '';
10862
+ const body = text ? `<pre class="mp-quick-pre">${esc(text)}</pre>` : '';
10863
+ return `<section class="mp-quick" aria-label="${esc(title)}结果">
10864
+ <div class="mp-quick-h"><b>${esc(title)}</b></div>
10865
+ ${body}
10866
+ </section>`;
10867
+ };
10868
+
8928
10869
  pp.summaryCardPNG = function (r, it) {
8929
10870
  try {
8930
10871
  if (typeof document === 'undefined' || !document.createElement) return '';
@@ -9184,6 +11125,15 @@ const TABS = [
9184
11125
  { k: 'qimen', label: '奇门', timeLabel: '起局时刻', hint: '奇门时家转盘:用起局时刻定节气、三元与局数;换局规则决定符头与节气如何对齐。' },
9185
11126
  { k: 'hepan', label: '合盘', timeLabel: '你的出生时刻', hint: '合盘按「日主 + 夫妻宫 + 五行互补」看两个人的相处与互补,需要双方出生信息。' },
9186
11127
  { k: 'tarot', label: '塔罗', timeLabel: '', hint: '塔罗不看时间,只记录抽牌时刻与你的提问。' },
11128
+ { k: 'almanac', label: '黄历', timeLabel: '', hint: '黄历按公历日期查农历、干支、建除、宜忌与冲煞;只做本地计算。' },
11129
+ { k: 'zeri', label: '择日', timeLabel: '', hint: '择日把区间内每天的建除宜忌与事项适配打分排序,并按属相排除冲煞日。' },
11130
+ { k: 'chenggu', label: '称骨', timeLabel: '出生时刻', hint: '称骨按农历年、月、日、时的骨重求和;时辰必填。' },
11131
+ { k: 'xiaoliuren', label: '小六壬', timeLabel: '起课时刻', hint: '小六壬用农历月/日/时掐指,也可用三个随机数起课。' },
11132
+ { k: 'star', label: '星座', timeLabel: '', hint: '星座按公历日期给出太阳星座与当日基调;同日同星座结果固定。' },
11133
+ { k: 'numerology', label: '生命数字', timeLabel: '', hint: '生命数字按公历生日归约出生命路径数、生日数与态度数。' },
11134
+ { k: 'trend', label: '人生K线', timeLabel: '出生时刻', hint: '人生 K 线按大运与流年十神给逐年运势分,画成走势图。' },
11135
+ { k: 'fengshui', label: '风水', timeLabel: '', hint: '风水看八宅命卦、大游年四吉四凶与流年九宫飞星;也可按坐向看门主灶倾向。' },
11136
+ { k: 'name', label: '起名', timeLabel: '', hint: '起名按八字喜用匹配字库五行,可评估现有名字或生成候选名。' },
9187
11137
  ];
9188
11138
 
9189
11139
  function pad(n) { return String(n).padStart(2, '0'); }
@@ -9197,6 +11147,9 @@ const TIME_KIND = {
9197
11147
  ziwei: 'birth', bazi: 'birth', hepan: 'birth',
9198
11148
  liuyao: 'cast', meihua: 'cast', daliuren: 'cast', qimen: 'cast',
9199
11149
  tarot: 'none',
11150
+ almanac: 'date', zeri: 'date', star: 'date', numerology: 'date',
11151
+ chenggu: 'birth', trend: 'birth', fengshui: 'birth', name: 'birth',
11152
+ xiaoliuren: 'cast',
9200
11153
  };
9201
11154
  const SAMPLE = { date: '1990-05-12', time: '14:30' };
9202
11155
  const kindOf = (t) => TIME_KIND[t] || 'none';
@@ -9226,6 +11179,19 @@ function App() {
9226
11179
  const [fit, setFit] = useState(null);
9227
11180
  const [rectEvents, setRectEvents] = useState([{ year: '2019', kind: 'career' }, { year: '2022', kind: 'love' }]);
9228
11181
  const [rectRes, setRectRes] = useState(null);
11182
+ const [task, setTask] = useState('开业');
11183
+ const [days, setDays] = useState('30');
11184
+ const [shengXiao, setShengXiao] = useState('');
11185
+ const [xlrMode, setXlrMode] = useState('time');
11186
+ const [num1, setNum1] = useState(''); const [num2, setNum2] = useState(''); const [num3, setNum3] = useState('');
11187
+ const [toAge, setToAge] = useState('60');
11188
+ const [fsMode, setFsMode] = useState('natal');
11189
+ const [sitting, setSitting] = useState('子');
11190
+ const [facing, setFacing] = useState('午');
11191
+ const [nnMode, setNnMode] = useState('assess');
11192
+ const [surname, setSurname] = useState('李');
11193
+ const [givenName, setGivenName] = useState('');
11194
+ const [limitN, setLimitN] = useState('6');
9229
11195
  const [onlineGeo, setOnlineGeo] = useState(false);
9230
11196
  const [lines, setLines] = useState('');
9231
11197
  const [result, setResult] = useState(null);
@@ -9246,10 +11212,13 @@ function App() {
9246
11212
  const [manual, setManual] = useState({ birth: null, cast: null });
9247
11213
 
9248
11214
  const meta = TABS.find((t) => t.k === tab) || TABS[0];
9249
- const isBirth = tab === 'bazi' || tab === 'ziwei' || tab === 'hepan';
11215
+ const isBirth = tab === 'bazi' || tab === 'ziwei' || tab === 'hepan'
11216
+ || tab === 'chenggu' || tab === 'trend' || tab === 'fengshui' || tab === 'name';
9250
11217
  const timeKind = kindOf(tab);
9251
11218
  const usesTimeInput = !(tab === 'meihua' && mhMode === 'number');
9252
- const showDT = tab !== 'tarot' && usesTimeInput;
11219
+ const showDate = tab !== 'tarot' && usesTimeInput;
11220
+ const showTime = showDate && timeKind !== 'date';
11221
+ const showDT = showTime;
9253
11222
 
9254
11223
  const run = async (kind, override, modeOverride, over) => {
9255
11224
  const k = kind || tab;
@@ -9282,6 +11251,42 @@ function App() {
9282
11251
  else if (parsed.warning) setNotice(parsed.warning);
9283
11252
  }
9284
11253
  if (k === 'qimen') { args.juMethod = qimenRule; args.lateZiHourMode = qLateZi; }
11254
+ if (k === 'bazi') args.verifyPast = 6;
11255
+ if (k === 'zeri') {
11256
+ args.startDate = dt.date;
11257
+ args.task = task;
11258
+ args.days = Number(days) || 30;
11259
+ if (shengXiao) args.shengXiao = shengXiao;
11260
+ }
11261
+ if (k === 'xiaoliuren') {
11262
+ args.mode = xlrMode;
11263
+ if (xlrMode === 'number') {
11264
+ args.num1 = Number(num1) || undefined;
11265
+ args.num2 = Number(num2) || undefined;
11266
+ args.num3 = Number(num3) || undefined;
11267
+ } else {
11268
+ args.date = dt.date;
11269
+ const hh = Number(String(dt.time).split(':')[0]);
11270
+ args.hour = Number.isFinite(hh) ? hh : 12;
11271
+ }
11272
+ }
11273
+ if (k === 'trend') { args.gender = genderV; args.toAge = Number(toAge) || 60; args.trueSolar = trueSolar; args.lateZi = lateZi; }
11274
+ if (k === 'fengshui') {
11275
+ args.action = fsMode;
11276
+ if (fsMode === 'advise') { args.sitting = sitting; args.facing = facing; delete args.datetime; }
11277
+ else { args.gender = genderV; }
11278
+ }
11279
+ if (k === 'name') {
11280
+ args.action = nnMode;
11281
+ args.surname = surname.trim();
11282
+ if (nnMode === 'generate') {
11283
+ args.gender = genderV; args.limit = Number(limitN) || 6;
11284
+ args.trueSolar = trueSolar; args.lateZi = lateZi;
11285
+ } else {
11286
+ args.givenName = givenName.trim();
11287
+ delete args.datetime;
11288
+ }
11289
+ }
9285
11290
  if (k === 'tarot') args.count = Number(count);
9286
11291
  if (k === 'hepan') {
9287
11292
  args.chart = hpChart;
@@ -9558,9 +11563,9 @@ function App() {
9558
11563
 
9559
11564
  h('div', { key: 'head', className: 'mp-panel' },
9560
11565
  h('div', { className: 'mp-row' },
9561
- showDT ? field(isBirth ? '出生日期(公历)' : '起卦日期(公历)',
11566
+ showDate ? field(isBirth ? '出生日期(公历)' : timeKind === 'date' ? '日期(公历)' : '起卦日期(公历)',
9562
11567
  h('input', { className: 'mp-input', type: 'date', value: date, onChange: (e) => editDate(e.target.value) })) : null,
9563
- showDT ? field(isBirth ? '出生时刻' : '起卦时刻',
11568
+ showTime ? field(isBirth ? '出生时刻' : '起卦时刻',
9564
11569
  h('input', { className: 'mp-input', type: 'time', value: time, onChange: (e) => editTime(e.target.value) })) : null,
9565
11570
  isBirth ? field('性别', h('select', {
9566
11571
  className: 'mp-input', value: gender, onChange: (e) => setGender(e.target.value),
@@ -9635,6 +11640,63 @@ function App() {
9635
11640
  className: 'mp-input', value: changing, placeholder: '1-6',
9636
11641
  onChange: (e) => setChanging(e.target.value),
9637
11642
  })) : null,
11643
+ tab === 'zeri' ? field('所办事项', h('select', {
11644
+ className: 'mp-input', value: task, onChange: (e) => setTask(e.target.value),
11645
+ }, ['嫁娶', '订婚', '开业', '入宅', '搬家', '动土', '装修', '安葬', '出行', '祭祀', '签约', '求医', '纳财']
11646
+ .map((t) => h('option', { key: t, value: t }, t)))) : null,
11647
+ tab === 'zeri' ? field('看多少天', h('input', {
11648
+ className: 'mp-input', type: 'number', min: 1, max: 60, value: days,
11649
+ onChange: (e) => setDays(e.target.value),
11650
+ })) : null,
11651
+ tab === 'zeri' ? field('当事人属相(可空)', h('select', {
11652
+ className: 'mp-input', value: shengXiao, onChange: (e) => setShengXiao(e.target.value),
11653
+ }, h('option', { value: '' }, '不排除'), ['鼠', '牛', '虎', '兔', '龙', '蛇', '马', '羊', '猴', '鸡', '狗', '猪']
11654
+ .map((s) => h('option', { key: s, value: s }, s)))) : null,
11655
+ tab === 'xiaoliuren' ? field('起课方式', h('div', { className: 'mp-seg' },
11656
+ h('button', { className: xlrMode === 'time' ? 'on' : '', onClick: () => setXlrMode('time') }, '时间'),
11657
+ h('button', { className: xlrMode === 'number' ? 'on' : '', onClick: () => setXlrMode('number') }, '报数'))) : null,
11658
+ (tab === 'xiaoliuren' && xlrMode === 'number') ? field('第一个数', h('input', {
11659
+ className: 'mp-input', type: 'number', min: 1, max: 99, value: num1,
11660
+ onChange: (e) => setNum1(e.target.value),
11661
+ })) : null,
11662
+ (tab === 'xiaoliuren' && xlrMode === 'number') ? field('第二个数', h('input', {
11663
+ className: 'mp-input', type: 'number', min: 1, max: 99, value: num2,
11664
+ onChange: (e) => setNum2(e.target.value),
11665
+ })) : null,
11666
+ (tab === 'xiaoliuren' && xlrMode === 'number') ? field('第三个数', h('input', {
11667
+ className: 'mp-input', type: 'number', min: 1, max: 99, value: num3,
11668
+ onChange: (e) => setNum3(e.target.value),
11669
+ })) : null,
11670
+ tab === 'trend' ? field('算到几岁', h('input', {
11671
+ className: 'mp-input', type: 'number', min: 10, max: 100, value: toAge,
11672
+ onChange: (e) => setToAge(e.target.value),
11673
+ })) : null,
11674
+ tab === 'fengshui' ? field('看什么', h('div', { className: 'mp-seg' },
11675
+ h('button', { className: fsMode === 'natal' ? 'on' : '', onClick: () => setFsMode('natal') }, '命卦'),
11676
+ h('button', { className: fsMode === 'advise' ? 'on' : '', onClick: () => setFsMode('advise') }, '门主灶'))) : null,
11677
+ (tab === 'fengshui' && fsMode === 'advise') ? field('坐山(24 山)', h('input', {
11678
+ className: 'mp-input', value: sitting, placeholder: '如 子 / 乾',
11679
+ onChange: (e) => setSitting(e.target.value),
11680
+ })) : null,
11681
+ (tab === 'fengshui' && fsMode === 'advise') ? field('朝向(24 山)', h('input', {
11682
+ className: 'mp-input', value: facing, placeholder: '如 午 / 巽',
11683
+ onChange: (e) => setFacing(e.target.value),
11684
+ })) : null,
11685
+ tab === 'name' ? field('看什么', h('div', { className: 'mp-seg' },
11686
+ h('button', { className: nnMode === 'assess' ? 'on' : '', onClick: () => setNnMode('assess') }, '评名字'),
11687
+ h('button', { className: nnMode === 'generate' ? 'on' : '', onClick: () => setNnMode('generate') }, '生成候选'))) : null,
11688
+ tab === 'name' ? field('姓', h('input', {
11689
+ className: 'mp-input', value: surname, placeholder: '如 李',
11690
+ onChange: (e) => setSurname(e.target.value),
11691
+ })) : null,
11692
+ (tab === 'name' && nnMode === 'assess') ? field('名字', h('input', {
11693
+ className: 'mp-input', value: givenName, placeholder: '如 沐宸',
11694
+ onChange: (e) => setGivenName(e.target.value),
11695
+ })) : null,
11696
+ (tab === 'name' && nnMode === 'generate') ? field('候选个数', h('input', {
11697
+ className: 'mp-input', type: 'number', min: 1, max: 20, value: limitN,
11698
+ onChange: (e) => setLimitN(e.target.value),
11699
+ })) : null,
9638
11700
  ),
9639
11701
  isBirth ? h('div', { className: 'mp-checks' },
9640
11702
  h('label', { title: '按出生地经度把钟表时间换算成真太阳时' },
@@ -9705,7 +11767,8 @@ function App() {
9705
11767
  : result.kind === 'qimen' ? (PP.qimenHTML(result.r) + PP.qimenFactsHTML(result.r))
9706
11768
  : result.kind === 'hepan' ? (result.r.chart === 'ziwei' ? PP.ziweiHepanHTML(result.r) : PP.hepanHTML(result.r))
9707
11769
  : result.kind === 'tarot' ? PP.tarotHTML(result.r)
9708
- : PP.liuyaoHTML(result.r),
11770
+ : result.kind === 'liuyao' ? PP.liuyaoHTML(result.r)
11771
+ : PP.quickHTML(result.r),
9709
11772
  result.it,
9710
11773
  { folded: defaultFolded() },
9711
11774
  ),