guanwei 1.1.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.
Files changed (157) hide show
  1. package/LICENSE +21 -0
  2. package/README.en.md +202 -0
  3. package/README.md +247 -0
  4. package/dist/assets/index-CsOD5rSo.js +102 -0
  5. package/dist/assets/index-CsOD5rSo.js.map +1 -0
  6. package/dist/assets/index-DQx62TUr.css +1 -0
  7. package/dist/favicon.svg +12 -0
  8. package/dist/index.html +15 -0
  9. package/eslint.config.js +28 -0
  10. package/index.html +25 -0
  11. package/package.json +96 -0
  12. package/postcss.config.js +10 -0
  13. package/public/favicon.svg +12 -0
  14. package/scripts/guanwei +245 -0
  15. package/scripts/install.sh +79 -0
  16. package/scripts/release.sh +42 -0
  17. package/scripts/setup.bat +36 -0
  18. package/scripts/setup.sh +123 -0
  19. package/server/src/data/db.json +12059 -0
  20. package/server/src/data/guanwei.db +0 -0
  21. package/server/src/data/lifeContext.ts +423 -0
  22. package/server/src/data/spreads.ts +255 -0
  23. package/server/src/data/tarotCards.ts +2353 -0
  24. package/server/src/index.ts +71 -0
  25. package/server/src/routes/ai.ts +376 -0
  26. package/server/src/routes/divine.ts +140 -0
  27. package/server/src/routes/hour.ts +40 -0
  28. package/server/src/routes/tarot.ts +129 -0
  29. package/server/src/routes/users.ts +140 -0
  30. package/server/src/services/chartBrief.ts +131 -0
  31. package/server/src/services/divineStore.ts +188 -0
  32. package/server/src/services/hourInference.ts +150 -0
  33. package/server/src/services/llmProvider.ts +181 -0
  34. package/server/src/services/promptBuilder.ts +327 -0
  35. package/server/src/services/relativesCheck.ts +139 -0
  36. package/server/src/services/sixRelatives.ts +70 -0
  37. package/server/src/services/skills.ts +116 -0
  38. package/server/src/types/index.ts +104 -0
  39. package/server/src/utils/semanticAnalyzer.ts +555 -0
  40. package/server/src/utils/tarotEngine.ts +849 -0
  41. package/server/tsconfig.json +18 -0
  42. package/shared/core/data/classics.ts +276 -0
  43. package/shared/core/data/courses.ts +211 -0
  44. package/shared/core/data/duanyu.ts +148 -0
  45. package/shared/core/data/ganzhi.ts +53 -0
  46. package/shared/core/data/gua64.ts +93 -0
  47. package/shared/core/data/liuren.ts +18 -0
  48. package/shared/core/data/liuyao.ts +73 -0
  49. package/shared/core/data/qimen.ts +27 -0
  50. package/shared/core/data/region-full.ts +3 -0
  51. package/shared/core/data/region.ts +310 -0
  52. package/shared/core/data/tarotSpreads.ts +78 -0
  53. package/shared/core/data/xiaoliuren.ts +18 -0
  54. package/shared/core/data/ziwei.ts +97 -0
  55. package/shared/core/data/zodiac.ts +20 -0
  56. package/shared/core/engine/astrology.ts +169 -0
  57. package/shared/core/engine/bazi.ts +306 -0
  58. package/shared/core/engine/calendar.ts +127 -0
  59. package/shared/core/engine/daily.ts +133 -0
  60. package/shared/core/engine/liuren.ts +86 -0
  61. package/shared/core/engine/liuyao.ts +93 -0
  62. package/shared/core/engine/meihua.ts +87 -0
  63. package/shared/core/engine/qimen.ts +78 -0
  64. package/shared/core/engine/questionFit.ts +94 -0
  65. package/shared/core/engine/tarot.ts +69 -0
  66. package/shared/core/engine/trueSolarTime.ts +86 -0
  67. package/shared/core/engine/xiaoliuren.ts +19 -0
  68. package/shared/core/engine/ziwei.ts +217 -0
  69. package/shared/core/engine/ziweiInterpret.ts +161 -0
  70. package/shared/core/types.ts +206 -0
  71. package/src/App.tsx +69 -0
  72. package/src/arts/registry.ts +31 -0
  73. package/src/assets/react.svg +1 -0
  74. package/src/components/Backdrop.tsx +27 -0
  75. package/src/components/BambooArt.tsx +41 -0
  76. package/src/components/CalendarInput.tsx +131 -0
  77. package/src/components/DateInput.tsx +103 -0
  78. package/src/components/Disclaimer.tsx +12 -0
  79. package/src/components/Empty.tsx +8 -0
  80. package/src/components/ErrorBoundary.tsx +38 -0
  81. package/src/components/HomeSideAnchor.tsx +62 -0
  82. package/src/components/InkVein.tsx +10 -0
  83. package/src/components/LifeEventsInput.tsx +47 -0
  84. package/src/components/LocationPicker.tsx +117 -0
  85. package/src/components/Motes.tsx +65 -0
  86. package/src/components/Navigation.tsx +80 -0
  87. package/src/components/ProfileForm.tsx +40 -0
  88. package/src/components/ProfilePicker.tsx +40 -0
  89. package/src/components/QuestionFields.tsx +47 -0
  90. package/src/components/ReportView.tsx +211 -0
  91. package/src/components/ResultCard.tsx +40 -0
  92. package/src/components/SealButton.tsx +18 -0
  93. package/src/components/SiteFooter.tsx +17 -0
  94. package/src/components/SiteNav.tsx +103 -0
  95. package/src/components/SongDialog.tsx +64 -0
  96. package/src/components/SongSearchSelect.tsx +81 -0
  97. package/src/components/SongSelect.tsx +61 -0
  98. package/src/components/StarField.tsx +119 -0
  99. package/src/components/TarotCard.tsx +129 -0
  100. package/src/components/TimeShichenInput.tsx +29 -0
  101. package/src/components/arts/AstrologyArt.tsx +141 -0
  102. package/src/components/arts/BaziArt.tsx +269 -0
  103. package/src/components/arts/LiurenArt.tsx +87 -0
  104. package/src/components/arts/LiuyaoArt.tsx +111 -0
  105. package/src/components/arts/MeihuaArt.tsx +105 -0
  106. package/src/components/arts/QimenArt.tsx +86 -0
  107. package/src/components/arts/TarotArt.tsx +110 -0
  108. package/src/components/arts/XiaoliurenArt.tsx +93 -0
  109. package/src/components/arts/ZiweiArt.tsx +296 -0
  110. package/src/data/arts.ts +84 -0
  111. package/src/data/lifeContext.ts +423 -0
  112. package/src/data/sample-report.json +146 -0
  113. package/src/data/shichen.ts +21 -0
  114. package/src/data/spreads.ts +255 -0
  115. package/src/data/tarotCards.ts +2353 -0
  116. package/src/hooks/useAIInterpret.ts +76 -0
  117. package/src/hooks/useDivine.ts +39 -0
  118. package/src/hooks/useTheme.ts +29 -0
  119. package/src/index.css +190 -0
  120. package/src/lib/utils.ts +6 -0
  121. package/src/main.tsx +12 -0
  122. package/src/pages/AboutPage.tsx +25 -0
  123. package/src/pages/AcademyPage.tsx +128 -0
  124. package/src/pages/AstrologyPage.tsx +33 -0
  125. package/src/pages/AuthPage.tsx +263 -0
  126. package/src/pages/BaZiPage.tsx +33 -0
  127. package/src/pages/ClassicsPage.tsx +120 -0
  128. package/src/pages/DemoPage.tsx +161 -0
  129. package/src/pages/HistoryPage.tsx +133 -0
  130. package/src/pages/Home.tsx +179 -0
  131. package/src/pages/HomePage.tsx +174 -0
  132. package/src/pages/ModulePage.tsx +320 -0
  133. package/src/pages/SpreadEditor.tsx +230 -0
  134. package/src/pages/SpreadEditorPage.tsx +105 -0
  135. package/src/pages/SpreadLibrary.tsx +199 -0
  136. package/src/pages/SpreadLibraryPage.tsx +17 -0
  137. package/src/pages/TarotPage.tsx +738 -0
  138. package/src/pages/ZiWeiPage.tsx +33 -0
  139. package/src/services/api.ts +342 -0
  140. package/src/stores/useDivinationStore.ts +22 -0
  141. package/src/styles/song.css +1066 -0
  142. package/src/styles/tokens.css +60 -0
  143. package/src/types/index.ts +104 -0
  144. package/src/utils/astrologyCalc.ts +61 -0
  145. package/src/utils/comboEngine.ts +134 -0
  146. package/src/utils/dst.ts +24 -0
  147. package/src/utils/panTone.ts +206 -0
  148. package/src/utils/recordStore.ts +60 -0
  149. package/src/utils/semanticAnalyzer.ts +555 -0
  150. package/src/utils/spreadStorage.ts +32 -0
  151. package/src/utils/storage.ts +27 -0
  152. package/src/utils/tarotEngine.ts +849 -0
  153. package/src/utils/userStore.ts +234 -0
  154. package/src/vite-env.d.ts +1 -0
  155. package/tailwind.config.js +52 -0
  156. package/tsconfig.json +40 -0
  157. package/vite.config.ts +34 -0
@@ -0,0 +1,206 @@
1
+ // 观微 · shared/core 通用类型(前后端共享,物理单一副本)
2
+
3
+ export type ArtId =
4
+ | 'bazi' | 'ziwei' | 'qimen' | 'meihua' | 'liuyao'
5
+ | 'liuren' | 'xiaoliuren' | 'astrology' | 'tarot';
6
+
7
+ // 出生地点(省市县区 → 经纬度)
8
+ export interface GeoLocation {
9
+ province: string;
10
+ city: string;
11
+ district: string;
12
+ lng: number; // 东经正数
13
+ lat: number; // 北纬正数
14
+ }
15
+
16
+ // 真太阳时结果
17
+ export interface TrueSolarTime {
18
+ beijingHours: number; // 北京时间(小时,含分钟小数)
19
+ localMeanHours: number; // 本地平太阳时
20
+ trueSolarHours: number; // 真太阳时
21
+ eotMinutes: number; // 均时差(分钟)
22
+ hourIndex: number; // 校正后时辰序 0=子
23
+ dateOffset: number; // 相对北京时间的日期偏移(0 或 ±1)
24
+ }
25
+
26
+ // 八字输入输出
27
+ export interface BaziInput {
28
+ y: number; m: number; d: number;
29
+ hourIndex: number; // 0-11
30
+ time?: string; // 精确出生时刻 HH:MM(有 location 时以此做真太阳时校正)
31
+ gender: '男' | '女';
32
+ location?: GeoLocation; // 提供时做真太阳时校正
33
+ }
34
+ export interface BaziResult {
35
+ yearGZ: string; monthGZ: string; dayGZ: string; hourGZ: string;
36
+ dayGan: string; dayGanWx: string;
37
+ wxCount: Record<string, number>;
38
+ shishen: { gan: string; name: string }[];
39
+ strength: '身强' | '身弱' | '中和';
40
+ support: number; drain: number;
41
+ nayin: string;
42
+ correctedHourIndex: number;
43
+ trueSolar?: TrueSolarTime;
44
+ // ─── 补齐层(2026-08-20 排盘要点调研)───
45
+ canggan: { zhi: string; gans: { gan: string; wx: string; shishen: string; qi: '本气' | '中气' | '余气' }[] }[]; // 四支藏干 + 十神
46
+ wxWeighted: Record<string, number>; // 含藏干权重的五行分
47
+ strengthDetail: { score: number; ling: number; gen: number; shi: number; reasons: string[] }; // 旺衰拆解
48
+ yongshen: { wx: string; shishen: string; xi: string[]; ji: string[]; tiaohou: string; reason: string }; // 用神/喜忌
49
+ dayun: { gz: string; ganShishen: string; zhiShishen: string; startAge: number; startYear: number; endYear: number; forward: boolean }[]; // 大运
50
+ qiYun: { startAge: number; startMonth: number; detail: string }; // 起运
51
+ liunian: { year: number; gz: string; ganShishen: string; zhiShishen: string }; // 流年(当前年)
52
+ shensha: { name: string; zhi: string; type: '吉' | '平' | '凶' }[]; // 神煞
53
+ taiyuan: string; // 胎元
54
+ minggong: string; // 命宫
55
+ shengong: string; // 身宫
56
+ geju: { name: string; gan: string; shishen: string; basis: string }; // 月令取格
57
+ }
58
+
59
+ // 紫微输入输出
60
+ export interface ZiweiInput {
61
+ ganzhi: string; // 如 癸酉
62
+ month: number; day: number; hour: number;
63
+ time?: string; // 精确出生时刻 HH:MM(有 location 时以此校正时辰)
64
+ location?: GeoLocation;
65
+ gender?: '男' | '女';
66
+ birthYear?: number; // 公历出生年(算虚岁/流年用)
67
+ solarDate?: [number, number, number]; // 公历出生日期(完整真太阳时含均时差用)
68
+ }
69
+ export interface ZiweiResult {
70
+ ming: number; // 命宫地支位 0=寅
71
+ zwPos: number; // 紫微宫位
72
+ zwStars: Record<string, number>;
73
+ palaces: Record<number, number>;
74
+ juName: string; nayin: string;
75
+ correctedHour: number;
76
+ dayun?: { palaceIdx: number; start: number; end: number }[];
77
+ curDayunIdx?: number;
78
+ nominalAge?: number;
79
+ liunianIdx?: number;
80
+ liunianPalaceName?: string;
81
+ liunianStars?: string[];
82
+ startAge?: number;
83
+ forward?: boolean;
84
+ // ─── 补齐层(2026-08-20 排盘要点调研)───
85
+ shen: number; // 身宫地支位 0=寅
86
+ mingGZ: string; // 命宫干支(五行局之源)
87
+ fuStars: Record<string, number>; // 辅星位置(0=寅起)
88
+ sihua: { lu: string; quan: string; ke: string; ji: string }; // 生年四化星名
89
+ sihuaPos: { lu?: number; quan?: number; ke?: number; ji?: number }; // 四化落宫位
90
+ brightness: Record<string, string>; // 十四主星亮度('庙'|'旺'|'得'|'利'|'平'|'陷')
91
+ geju: { key: string; name: string; ji: '吉' | '凶' | '平'; desc: string; why: string }[]; // 格局
92
+ }
93
+
94
+ // 奇门输入输出
95
+ export interface QimenInput { datetime: string | Date }
96
+ export interface QimenResult {
97
+ yin: boolean; ju: number; jqName: string;
98
+ dayGZ: string; hourGZ: string;
99
+ xunShou: string; xunshouName: string;
100
+ zfStar: string; zsMen: string; zfPalace: number;
101
+ pan: Record<number, { yi: string; men: string; star: string }>;
102
+ // ─── 补齐层(2026-08-20)───
103
+ zsPalace: number; // 值使门落宫(时干加临)
104
+ tianYi: Record<number, string>; // 天盘奇仪(暗干)
105
+ shen: Record<number, string>; // 八神布宫
106
+ }
107
+
108
+ // 梅花输入输出
109
+ export interface MeihuaInput {
110
+ mode: 'time' | 'num';
111
+ n1?: number; n2?: number; n3?: number;
112
+ now?: Date;
113
+ }
114
+ export interface MeihuaResult {
115
+ upper: number; lower: number; move: number;
116
+ benGua: GuaEntry; bianGua: GuaEntry; huGua: GuaEntry | null;
117
+ tiGua: number; yongGua: number; tiWx: string; yongWx: string;
118
+ shengke: string; benYao: number[];
119
+ // ─── 补齐层(2026-08-20)───
120
+ monthWx?: string; // 起卦月支五行
121
+ tiWangShuai?: '旺' | '相' | '休' | '囚' | '死'; // 体卦旺衰(月令)
122
+ yongWangShuai?: '旺' | '相' | '休' | '囚' | '死'; // 用卦旺衰
123
+ wangShuaiNote?: string; // 旺衰吉凶加成说明
124
+ }
125
+
126
+ // 六十四卦条目
127
+ export interface GuaEntry {
128
+ up: number; down: number;
129
+ name: string; ci: string; xiang: string;
130
+ }
131
+
132
+ // 六爻输出
133
+ export interface LiuyaoResult {
134
+ yao: number[];
135
+ names: { v: number; nm: string; backs: number }[];
136
+ benGua: GuaEntry; bianGua: GuaEntry; dongYao: number[];
137
+ // ─── 补齐层(2026-08-20 纳甲筮法)───
138
+ najia?: {
139
+ gong: string; // 卦宫
140
+ lines: { gz: string; ganWx: string; zhiWx: string; liuqin: string; shen: string; isShi: boolean; isYing: boolean; kong: boolean }[]; // 初→上
141
+ shiPos: number; yingPos: number;
142
+ dayGZ: string; monthZhi: string; // 起卦日干支/月支
143
+ yuePo: string[]; // 月破地支
144
+ xunKong: string[]; // 旬空地支
145
+ shiLiQin: string; // 世爻六亲(用神参考)
146
+ };
147
+ }
148
+
149
+ // 大六壬输出
150
+ export interface LiurenResult {
151
+ dayGZ: string; hourGZ: string;
152
+ jiang: string; jqName: string;
153
+ tianpan: Record<number, string>;
154
+ ganJi: string; ke1: string; ke2: string; ke3: string; ke4: string;
155
+ chuan1: string; chuan2: string; chuan3: string;
156
+ // ─── 补齐层(2026-08-20)───
157
+ guiRen: string; // 贵人(昼夜贵人地支)
158
+ isDay: boolean; // 昼占/夜占
159
+ tianJiang: Record<number, string>; // 十二天将布宫(地盘支 → 天将)
160
+ chuanJiang: { chuan: string; jiang: string }[]; // 三传所乘天将
161
+ keti: string; // 课体(伏吟/反吟/常课)
162
+ ketiNote: string; // 课体断语
163
+ }
164
+
165
+ // 小六壬输出
166
+ export interface XiaoliurenResult {
167
+ a: number; b: number; c: number;
168
+ idx: number; name: string;
169
+ detail: { ji: string; wx: string; num: string; dir: string; poem: string; text: string };
170
+ }
171
+
172
+ // 星盘输出
173
+ export interface AstrologyResult {
174
+ planets: [string, string, number, string][];
175
+ asc: number; sun: number; moon: number;
176
+ lstHours: number;
177
+ aspects: [string, string, string, string][];
178
+ mc?: number; // 中天黄经
179
+ epsilon?: number; // 黄赤交角(度)
180
+ // ─── 补齐层(2026-08-20 星盘细化)───
181
+ houseSystem: 'whole-sign'; // 当前宫位制:整宫制(以上升点为 1 宫头,每宫 30°)
182
+ houses: { num: number; cusp: number; sign: string; ruler: string; rulerLng: number }[]; // 十二宫
183
+ planetDetails: {
184
+ cn: string; sym: string; color: string;
185
+ lng: number; sign: string; degree: number; // 黄经/星座/宫内经度
186
+ house: number; // 落宫 1-12
187
+ retrograde: boolean; // 逆行
188
+ dignity: { status: '庙' | '旺' | '陷' | '弱' | ''; note: string }; // 古典庙旺
189
+ }[];
190
+ ascSign: string; sunSign: string; moonSign: string; // 上升/太阳/月亮星座
191
+ }
192
+
193
+ // 塔罗输出
194
+ export interface TarotCardData {
195
+ name: string; en: string; num?: string;
196
+ up: string; rev: string;
197
+ major: boolean; suit?: string;
198
+ reversed: boolean;
199
+ }
200
+
201
+ // 通用引擎契约
202
+ export interface ArtEngine<TInput, TResult> {
203
+ id: ArtId;
204
+ calc(input: TInput, rng?: () => number): TResult;
205
+ validate?(input: TInput): string[];
206
+ }
package/src/App.tsx ADDED
@@ -0,0 +1,69 @@
1
+ // 观微 · 应用路由(HashRouter,映射参考项目 hash 语义)
2
+ // 路由表:
3
+ // / 首页(含 #home-arts / #home-fa 锚点)
4
+ // /about 缘起
5
+ // /classics 古籍(列表 / 详情 :id)
6
+ // /academy 学馆(列表 / 课程 :id)
7
+ // /history 历史(列表 / 详情 :id)
8
+ // /art/:artId 九术模块(进入滚动置顶;切换保留状态)
9
+ import { HashRouter, Routes, Route, useLocation } from 'react-router-dom';
10
+ import { useEffect } from 'react';
11
+ import Backdrop from '@/components/Backdrop';
12
+ import InkVein from '@/components/InkVein';
13
+ import { SongDialogProvider } from '@/components/SongDialog';
14
+ import ErrorBoundary from '@/components/ErrorBoundary';
15
+ import SiteNav from '@/components/SiteNav';
16
+ import SiteFooter from '@/components/SiteFooter';
17
+ import HomePage from '@/pages/HomePage';
18
+ import AboutPage from '@/pages/AboutPage';
19
+ import ModulePage from '@/pages/ModulePage';
20
+ import DemoPage from '@/pages/DemoPage';
21
+ import ClassicsPage from '@/pages/ClassicsPage';
22
+ import AcademyPage from '@/pages/AcademyPage';
23
+ import HistoryPage from '@/pages/HistoryPage';
24
+ import AuthPage from '@/pages/AuthPage';
25
+ import SpreadEditorPage from '@/pages/SpreadEditorPage';
26
+
27
+ // 进入九术模块视图时滚动置顶(instant,无动画)
28
+ function ScrollTopOnArt() {
29
+ const location = useLocation();
30
+ useEffect(() => {
31
+ if (location.pathname.startsWith('/art/')) {
32
+ window.scrollTo({ top: 0, behavior: 'instant' as ScrollBehavior });
33
+ }
34
+ }, [location.pathname]);
35
+ return null;
36
+ }
37
+
38
+ export default function App() {
39
+ return (
40
+ <HashRouter>
41
+ <SongDialogProvider>
42
+ <ScrollTopOnArt />
43
+ <Backdrop />
44
+ <InkVein />
45
+ <SiteNav />
46
+ <main>
47
+ <ErrorBoundary>
48
+ <Routes>
49
+ <Route path="/" element={<HomePage />} />
50
+ <Route path="/about" element={<AboutPage />} />
51
+ <Route path="/classics" element={<ClassicsPage />} />
52
+ <Route path="/classics/:id" element={<ClassicsPage />} />
53
+ <Route path="/academy" element={<AcademyPage />} />
54
+ <Route path="/academy/:id" element={<AcademyPage />} />
55
+ <Route path="/history" element={<HistoryPage />} />
56
+ <Route path="/history/:id" element={<HistoryPage />} />
57
+ <Route path="/auth" element={<AuthPage />} />
58
+ <Route path="/spread-editor" element={<SpreadEditorPage />} />
59
+ <Route path="/demo" element={<DemoPage />} />
60
+ <Route path="/art/:artId" element={<ModulePage />} />
61
+ <Route path="*" element={<HomePage />} />
62
+ </Routes>
63
+ </ErrorBoundary>
64
+ </main>
65
+ <SiteFooter />
66
+ </SongDialogProvider>
67
+ </HashRouter>
68
+ );
69
+ }
@@ -0,0 +1,31 @@
1
+ // 九术组件注册表:面板与结果渲染(ModulePage 与 HistoryPage 共用)
2
+ import type { ComponentType } from 'react';
3
+ import { BaziPanel, BaziResult } from '@/components/arts/BaziArt';
4
+ import { ZiweiPanel, ZiweiResult } from '@/components/arts/ZiweiArt';
5
+ import { QimenPanel, QimenResultView } from '@/components/arts/QimenArt';
6
+ import { MeihuaPanel, MeihuaResult } from '@/components/arts/MeihuaArt';
7
+ import { LiuyaoPanel, LiuyaoResult } from '@/components/arts/LiuyaoArt';
8
+ import { LiurenPanel, LiurenResult } from '@/components/arts/LiurenArt';
9
+ import { XiaoliurenPanel, XiaoliurenResult } from '@/components/arts/XiaoliurenArt';
10
+ import { AstrologyPanel, AstrologyResult } from '@/components/arts/AstrologyArt';
11
+ import { TarotPanel, TarotResult } from '@/components/arts/TarotArt';
12
+
13
+ export interface ArtPair {
14
+ Panel: ComponentType<{ onDivine: (fn: () => unknown) => void }>;
15
+ Result: ComponentType<{ data: any }>;
16
+ placeholder: string;
17
+ }
18
+
19
+ export const ART_PAIRS: Record<string, ArtPair> = {
20
+ bazi: { Panel: BaziPanel, Result: BaziResult, placeholder: '静候四柱 · 观五行流转' },
21
+ ziwei: { Panel: ZiweiPanel, Result: ZiweiResult, placeholder: '静候星布 · 观十四主星' },
22
+ qimen: { Panel: QimenPanel, Result: QimenResultView, placeholder: '静候局成 · 观九宫遁甲' },
23
+ meihua: { Panel: MeihuaPanel, Result: MeihuaResult, placeholder: '静候心动 · 观梅花开落' },
24
+ liuyao: { Panel: LiuyaoPanel, Result: LiuyaoResult, placeholder: '静候钱落 · 观六爻成象' },
25
+ liuren: { Panel: LiurenPanel, Result: LiurenResult, placeholder: '静候课起 · 观天地人盘' },
26
+ xiaoliuren: { Panel: XiaoliurenPanel, Result: XiaoliurenResult, placeholder: '静候指落 · 观掌诀玄机' },
27
+ astrology: { Panel: AstrologyPanel, Result: AstrologyResult, placeholder: '静候星布 · 观天穹为书' },
28
+ tarot: { Panel: TarotPanel, Result: TarotResult, placeholder: '静候牌启 · 观镜照本心' },
29
+ };
30
+
31
+ export const artPairOf = (artId: string): ArtPair | undefined => ART_PAIRS[artId];
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
@@ -0,0 +1,27 @@
1
+ // 全局底景:宣纸纹理 + 水墨远山(浮动)+ 墨尘粒子 + 光晕
2
+ import Motes from './Motes';
3
+
4
+ export default function Backdrop() {
5
+ return (
6
+ <>
7
+ <div id="paper-grain" aria-hidden="true" />
8
+ <Motes />
9
+ <span className="hero-glow hero-glow--left" aria-hidden="true" />
10
+ <span className="hero-glow hero-glow--right" aria-hidden="true" />
11
+ <svg id="mountains" viewBox="0 0 1440 320" preserveAspectRatio="xMidYMax slice" aria-hidden="true">
12
+ <defs>
13
+ <linearGradient id="mg1" x1="0" y1="0" x2="0" y2="1">
14
+ <stop offset="0" stopColor="#96A88F" stopOpacity="0" />
15
+ <stop offset="1" stopColor="#96A88F" stopOpacity=".30" />
16
+ </linearGradient>
17
+ <linearGradient id="mg2" x1="0" y1="0" x2="0" y2="1">
18
+ <stop offset="0" stopColor="#7C9792" stopOpacity="0" />
19
+ <stop offset="1" stopColor="#7C9792" stopOpacity=".22" />
20
+ </linearGradient>
21
+ </defs>
22
+ <path d="M0,220 C160,140 300,120 420,168 C540,216 640,140 760,120 C880,100 960,150 1080,170 C1200,190 1320,150 1440,190 L1440,320 L0,320 Z" fill="url(#mg1)" />
23
+ <path d="M0,260 C140,200 260,210 400,236 C560,264 680,210 820,200 C980,190 1100,240 1240,228 C1320,222 1380,240 1440,250 L1440,320 L0,320 Z" fill="url(#mg2)" />
24
+ </svg>
25
+ </>
26
+ );
27
+ }
@@ -0,0 +1,41 @@
1
+ // 水墨竹影装饰(缘起等页面右侧):手绘风 SVG 竹 + 石 + 山
2
+ export default function BambooArt({ className = '' }: { className?: string }) {
3
+ return (
4
+ <div className={'bamboo-art ' + className} aria-hidden="true">
5
+ <svg viewBox="0 0 320 520" fill="none" xmlns="http://www.w3.org/2000/svg">
6
+ {/* 远山淡影 */}
7
+ <path d="M0,470 C60,430 120,445 180,425 C240,405 285,420 320,400 L320,520 L0,520 Z" fill="#96A88F" opacity=".12" />
8
+ <path d="M0,495 C80,465 160,480 240,462 C280,453 300,462 320,455 L320,520 L0,520 Z" fill="#7C9792" opacity=".10" />
9
+ {/* 竹节 */}
10
+ {[0, 1, 2, 3, 4].map(i => {
11
+ const x = 88 + i * 34;
12
+ const top = 40 + i * 14;
13
+ const bottom = 500 - i * 30;
14
+ return (
15
+ <g key={i} stroke="#5F7660" strokeWidth="3.5" strokeLinecap="round" fill="none" opacity=".75">
16
+ <path d={"M" + x + "," + top + " C" + (x - 6) + "," + ((top + bottom) / 2) + " " + (x + 8) + "," + ((top + bottom) / 2 + 20) + " " + x + "," + bottom} />
17
+ {/* 节环 */}
18
+ <path d={"M" + (x - 5) + ",150 C" + x + ",146 " + x + ",146 " + (x + 5) + ",150"} strokeWidth="2" opacity=".5" />
19
+ <path d={"M" + (x - 4) + ",268 C" + x + ",265 " + x + ",265 " + (x + 4) + ",268"} strokeWidth="2" opacity=".5" />
20
+ <path d={"M" + (x - 3) + ",386 C" + x + ",383 " + x + ",383 " + (x + 3) + ",386"} strokeWidth="2" opacity=".5" />
21
+ </g>
22
+ );
23
+ })}
24
+ {/* 竹叶 */}
25
+ {[
26
+ [60, 190, 40, 210, 55, 240], [120, 140, 95, 150, 108, 175], [180, 230, 160, 240, 172, 262],
27
+ [240, 120, 218, 130, 230, 152], [70, 320, 52, 330, 66, 350], [150, 300, 128, 308, 140, 328],
28
+ [230, 260, 210, 270, 222, 290], [100, 80, 80, 92, 94, 112], [200, 170, 178, 180, 190, 200],
29
+ [250, 340, 232, 348, 244, 366], [60, 420, 44, 430, 58, 448], [170, 400, 152, 410, 164, 428],
30
+ ].map((pts, i) => (
31
+ <path key={i} d={"M" + pts[0] + "," + pts[1] + " Q" + pts[2] + "," + pts[3] + " " + pts[4] + "," + pts[5] + " Q" + pts[2] + "," + pts[3] + " " + pts[0] + "," + pts[1]} fill="#5F7660" opacity=".6" />
32
+ ))}
33
+ {/* 石 */}
34
+ <path d="M40,505 C60,470 95,465 115,490 C100,505 70,510 40,505 Z" fill="#8C9A8A" opacity=".18" />
35
+ {/* 小印 */}
36
+ <rect x="248" y="470" width="34" height="34" rx="2" stroke="#A5402D" strokeWidth="1.5" opacity=".55" />
37
+ <text x="265" y="492" textAnchor="middle" fontSize="16" fill="#A5402D" opacity=".55" style={{ fontFamily: 'Kaiti SC, KaiTi, serif' }}>观微</text>
38
+ </svg>
39
+ </div>
40
+ );
41
+ }
@@ -0,0 +1,131 @@
1
+ // CalendarInput:公历日期输入(可手输 YYYY-MM-DD,也可点开自绘月历选择)
2
+ import { useEffect, useRef, useState } from 'react';
3
+
4
+ const WEEK_HEAD = ['日', '一', '二', '三', '四', '五', '六'];
5
+ const MONTHS = ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'];
6
+
7
+ interface Props {
8
+ value: string;
9
+ onChange: (iso: string) => void;
10
+ id?: string;
11
+ }
12
+
13
+ function parseISO(v: string): [number, number, number] {
14
+ const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(v);
15
+ return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : [1990, 1, 1];
16
+ }
17
+
18
+ function daysInMonth(y: number, m: number) {
19
+ return new Date(y, m, 0).getDate();
20
+ }
21
+
22
+ export default function CalendarInput({ value, onChange, id }: Props) {
23
+ const [open, setOpen] = useState(false);
24
+ const [text, setText] = useState(value);
25
+ const [error, setError] = useState(false);
26
+ const ref = useRef<HTMLDivElement>(null);
27
+ const [y, m, d] = parseISO(value);
28
+ const [viewY, setViewY] = useState(y);
29
+ const [viewM, setViewM] = useState(m);
30
+
31
+ useEffect(() => {
32
+ setText(value);
33
+ const [vy, vm] = parseISO(value);
34
+ setViewY(vy); setViewM(vm);
35
+ }, [value]);
36
+
37
+ useEffect(() => {
38
+ if (!open) return;
39
+ const close = (e: MouseEvent) => {
40
+ if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false);
41
+ };
42
+ document.addEventListener('mousedown', close);
43
+ return () => document.removeEventListener('mousedown', close);
44
+ }, [open]);
45
+
46
+ const commit = (iso: string) => {
47
+ onChange(iso);
48
+ setText(iso);
49
+ setError(false);
50
+ };
51
+
52
+ const handleText = (v: string) => {
53
+ setText(v);
54
+ const m = /^(\d{4})-(\d{1,2})-(\d{1,2})$/.exec(v);
55
+ if (m) {
56
+ const yy = Number(m[1]), mm = Number(m[2]), dd = Number(m[3]);
57
+ if (mm >= 1 && mm <= 12 && dd >= 1 && dd <= daysInMonth(yy, mm)) {
58
+ commit(yy + '-' + String(mm).padStart(2, '0') + '-' + String(dd).padStart(2, '0'));
59
+ return;
60
+ }
61
+ }
62
+ setError(true);
63
+ };
64
+
65
+ // 月历网格:从周日开始
66
+ const firstDay = new Date(viewY, viewM - 1, 1).getDay();
67
+ const total = daysInMonth(viewY, viewM);
68
+ const cells: (number | null)[] = [
69
+ ...Array.from({ length: firstDay }, () => null),
70
+ ...Array.from({ length: total }, (_, i) => i + 1),
71
+ ];
72
+ while (cells.length % 7 !== 0) cells.push(null);
73
+
74
+ const nav = (delta: number) => {
75
+ let ny = viewY, nm = viewM + delta;
76
+ if (nm < 1) { nm = 12; ny--; }
77
+ if (nm > 12) { nm = 1; ny++; }
78
+ setViewY(ny); setViewM(nm);
79
+ };
80
+
81
+ const isSelected = (dd: number) => viewY === y && viewM === m && dd === d;
82
+
83
+ return (
84
+ <div className="cal-input" ref={ref}>
85
+ <div className="cal-field">
86
+ <input
87
+ className={"input-line" + (error ? ' cal-error' : '')}
88
+ id={id}
89
+ placeholder="1990-06-15"
90
+ value={text}
91
+ onChange={e => handleText(e.target.value)}
92
+ />
93
+ <button type="button" className="cal-toggle" onClick={() => setOpen(v => !v)} aria-label="开月历">
94
+ <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
95
+ <rect width="18" height="18" x="3" y="4" rx="2" />
96
+ <path d="M16 2v4M8 2v4M3 10h18" />
97
+ </svg>
98
+ </button>
99
+ </div>
100
+ {error && <p className="hint" style={{ color: 'var(--cinnabar)' }}>历日未合 · 请书如 1990-06-15</p>}
101
+ {open && (
102
+ <div className="cal-panel">
103
+ <div className="cal-head">
104
+ <button type="button" className="cal-nav" onClick={() => nav(-1)} aria-label="上月">
105
+ <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="m15 18-6-6 6-6" /></svg>
106
+ </button>
107
+ <span className="cal-title">{viewY}年 {MONTHS[viewM - 1]}</span>
108
+ <button type="button" className="cal-nav" onClick={() => nav(1)} aria-label="下月">
109
+ <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="m9 18 6-6-6-6" /></svg>
110
+ </button>
111
+ </div>
112
+ <div className="cal-week">
113
+ {WEEK_HEAD.map(w => <span key={w}>{w}</span>)}
114
+ </div>
115
+ <div className="cal-grid">
116
+ {cells.map((c, i) => c === null ? <span key={i} /> : (
117
+ <button
118
+ key={i}
119
+ type="button"
120
+ className={isSelected(c) ? 'sel' : ''}
121
+ onClick={() => { commit(viewY + '-' + String(viewM).padStart(2, '0') + '-' + String(c).padStart(2, '0')); setOpen(false); }}
122
+ >
123
+ {c}
124
+ </button>
125
+ ))}
126
+ </div>
127
+ </div>
128
+ )}
129
+ </div>
130
+ );
131
+ }
@@ -0,0 +1,103 @@
1
+ // DateInput:出生日期输入(公历 / 农历双历法,lunar-typescript 互转,实时联动)
2
+ import { useEffect, useState } from 'react';
3
+ import { Solar, Lunar } from 'lunar-typescript';
4
+ import SongSelect from '@/components/SongSelect';
5
+ import CalendarInput from '@/components/CalendarInput';
6
+
7
+ interface Props {
8
+ value: string; // 公历 YYYY-MM-DD(对外统一输出公历)
9
+ onChange: (solarISO: string) => void;
10
+ id?: string;
11
+ }
12
+
13
+ const LUNAR_YEARS = Array.from({ length: 201 }, (_, i) => 1900 + i);
14
+ const pad2 = (n: number) => String(n).padStart(2, '0');
15
+
16
+ export default function DateInput({ value, onChange, id }: Props) {
17
+ const [mode, setMode] = useState<'solar' | 'lunar'>('solar');
18
+ const [y, m, d] = value ? value.split('-').map(Number) : [1990, 1, 1];
19
+
20
+ // 农历状态(由当前公历派生)
21
+ const [ly, setLy] = useState(1990);
22
+ const [lm, setLm] = useState(1);
23
+ const [ld, setLd] = useState(1);
24
+ const [leap, setLeap] = useState(false);
25
+
26
+ // 公历变化 → 同步农历
27
+ useEffect(() => {
28
+ try {
29
+ const l = Solar.fromYmd(y, m, d).getLunar();
30
+ const rawMonth = l.getMonth();
31
+ setLy(l.getYear());
32
+ setLm(Math.abs(rawMonth));
33
+ setLd(l.getDay());
34
+ setLeap(rawMonth < 0);
35
+ } catch { /* ignore */ }
36
+ }, [y, m, d]);
37
+
38
+ const lunarToSolar = (yy: number, mm: number, dd: number, isLeap: boolean): string => {
39
+ try {
40
+ // 闰月以负数月表示
41
+ const s = Lunar.fromYmd(yy, isLeap ? -mm : mm, dd).getSolar();
42
+ return s.getYear() + '-' + pad2(s.getMonth()) + '-' + pad2(s.getDay());
43
+ } catch {
44
+ return value;
45
+ }
46
+ };
47
+
48
+ // 农历选择变化 → 实时转公历输出(父组件 value 变化会再同步农历,闭环稳定)
49
+ useEffect(() => {
50
+ if (mode === 'lunar') {
51
+ const iso = lunarToSolar(ly, lm, ld, leap);
52
+ if (iso !== value) onChange(iso);
53
+ }
54
+ // eslint-disable-next-line react-hooks/exhaustive-deps
55
+ }, [mode, ly, lm, ld, leap]);
56
+
57
+ // 当年闰月检测(探测负数月)
58
+ const leapMonth = (() => {
59
+ for (let m = 1; m <= 12; m++) {
60
+ try { Lunar.fromYmd(ly, -m, 1); return m; } catch { /* 非闰月 */ }
61
+ }
62
+ return 0;
63
+ })();
64
+ const isLeapNow = leapMonth > 0 && lm === leapMonth;
65
+
66
+ const daysInMonth = (() => {
67
+ for (let dd = 30; dd >= 29; dd--) {
68
+ try { Lunar.fromYmd(ly, leap ? -lm : lm, dd); return dd; } catch { /* 继续探测 */ }
69
+ }
70
+ return 30;
71
+ })();
72
+
73
+ return (
74
+ <div>
75
+ <div style={{ display: 'flex', gap: '.5rem', marginBottom: '.5rem', maxWidth: 130 }}>
76
+ <SongSelect
77
+ value={mode}
78
+ options={[{ value: 'solar', label: '公历' }, { value: 'lunar', label: '农历' }]}
79
+ onChange={v => setMode(v as 'solar' | 'lunar')}
80
+ />
81
+ </div>
82
+ {mode === 'solar' ? (
83
+ <CalendarInput id={id} value={value} onChange={onChange} />
84
+ ) : (
85
+ <div className="field-row" style={{ gridTemplateColumns: '1fr 1fr 1fr' }}>
86
+ <div className="field"><SongSelect value={String(ly)} options={LUNAR_YEARS.map(v => ({ value: String(v), label: String(v) + '年' }))} onChange={v => setLy(Number(v))} /></div>
87
+ <div className="field"><SongSelect value={String(lm)} options={Array.from({ length: 12 }, (_, i) => ({ value: String(i + 1), label: (i + 1) + '月' }))} onChange={v => { setLm(Number(v)); setLeap(false); }} /></div>
88
+ <div className="field"><SongSelect value={String(ld)} options={Array.from({ length: Math.max(daysInMonth, ld) }, (_, i) => ({ value: String(i + 1), label: (i + 1) + '日' }))} onChange={v => setLd(Number(v))} /></div>
89
+ </div>
90
+ )}
91
+ {mode === 'lunar' && (
92
+ <p className="hint">
93
+ {ly}年{leap ? '闰' : ''}{lm}月{ld}日 即公历 <strong>{lunarToSolar(ly, lm, ld, leap)}</strong>
94
+ {isLeapNow && (
95
+ <button type="button" style={{ marginLeft: '.6rem', color: 'var(--cinnabar)', textDecoration: 'underline' }} onClick={() => setLeap(v => !v)}>
96
+ {leap ? '取平月' : '取闰月'}
97
+ </button>
98
+ )}
99
+ </p>
100
+ )}
101
+ </div>
102
+ );
103
+ }
@@ -0,0 +1,12 @@
1
+ // 免责声明(观微三戒)
2
+ export default function Disclaimer({ text }: { text?: string }) {
3
+ return (
4
+ <div className="disclaimer" role="note">
5
+ <span className="seal-mini">慎</span>
6
+ <div>
7
+ <strong>观微三戒:</strong>
8
+ {text ?? '凡占问所得,皆为先贤智慧之投影,仅供修身养性、怡情遣兴之用,不构成任何决策依据。心诚则灵,勿迷勿执;大事不决,请询人事、法律或专业人士。'}
9
+ </div>
10
+ </div>
11
+ );
12
+ }
@@ -0,0 +1,8 @@
1
+ import { cn } from '@/lib/utils'
2
+
3
+ // Empty component
4
+ export default function Empty() {
5
+ return (
6
+ <div className={cn('flex h-full items-center justify-center')}>Empty</div>
7
+ )
8
+ }