commit a3e6aa9c01d711cc21982acd372b6ab61160e36e Author: gouki Date: Thu Aug 6 08:47:09 2026 +0000 feat: 祈福小助手万年历小程序第一期 diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..82c328b Binary files /dev/null and b/.DS_Store differ diff --git a/mini/app.js b/mini/app.js new file mode 100644 index 0000000..171f957 --- /dev/null +++ b/mini/app.js @@ -0,0 +1,26 @@ +// app.js +App({ + onLaunch() { + // 初始化本地存储 + const settings = wx.getStorageSync('lunar-settings'); + if (!settings) { + wx.setStorageSync('lunar-settings', { + theme: 'light', + weekStartDay: 0, + ziHourSect: 'lateZiNextDay', + solarTime: true, + }); + } + const profiles = wx.getStorageSync('lunar-user-profiles'); + if (!profiles) { + wx.setStorageSync('lunar-user-profiles', { profiles: [], activeIndex: 0 }); + } + const bookmarks = wx.getStorageSync('lunar-bookmarks'); + if (!bookmarks) { + wx.setStorageSync('lunar-bookmarks', []); + } + }, + globalData: { + // 全局共享数据 + }, +}); diff --git a/mini/app.json b/mini/app.json new file mode 100644 index 0000000..7972d50 --- /dev/null +++ b/mini/app.json @@ -0,0 +1,60 @@ +{ + "pages": [ + "pages/home/home", + "pages/calendar/calendar", + "pages/day-detail/day-detail", + "pages/bazi/bazi", + "pages/fortune/fortune", + "pages/divination/divination", + "pages/solar-terms/solar-terms", + "pages/settings/settings" + ], + "window": { + "backgroundTextStyle": "light", + "navigationBarBackgroundColor": "#C41E3A", + "navigationBarTitleText": "万年历", + "navigationBarTextStyle": "white", + "backgroundColor": "#FFFBF5" + }, + "tabBar": { + "color": "#9E8E7E", + "selectedColor": "#C41E3A", + "backgroundColor": "#FFFFFF", + "borderStyle": "black", + "list": [ + { + "pagePath": "pages/home/home", + "text": "首页", + "iconPath": "images/home.png", + "selectedIconPath": "images/home-active.png" + }, + { + "pagePath": "pages/calendar/calendar", + "text": "日历", + "iconPath": "images/calendar.png", + "selectedIconPath": "images/calendar-active.png" + }, + { + "pagePath": "pages/bazi/bazi", + "text": "八字", + "iconPath": "images/bazi.png", + "selectedIconPath": "images/bazi-active.png" + }, + { + "pagePath": "pages/fortune/fortune", + "text": "运势", + "iconPath": "images/fortune.png", + "selectedIconPath": "images/fortune-active.png" + }, + { + "pagePath": "pages/settings/settings", + "text": "设置", + "iconPath": "images/settings.png", + "selectedIconPath": "images/settings-active.png" + } + ] + }, + "style": "v2", + "sitemapLocation": "sitemap.json", + "lazyCodeLoading": "requiredComponents" +} diff --git a/mini/app.wxss b/mini/app.wxss new file mode 100644 index 0000000..dfafedc --- /dev/null +++ b/mini/app.wxss @@ -0,0 +1,153 @@ +/* app.wxss — 全局样式 */ +page { + background-color: #FFFBF5; + color: #1A1A1A; + font-family: 'PingFang SC', 'Noto Sans SC', 'Hiragino Sans GB', system-ui, -apple-system, sans-serif; + font-size: 28rpx; + line-height: 1.5; +} + +/* 中文衬线字体 */ +.font-chinese { + font-family: 'Noto Serif SC', 'STSong', 'SimSun', 'Songti SC', serif; +} + +/* 卡片基础样式 */ +.card { + background-color: #FFFFFF; + border-radius: 24rpx; + border: 1rpx solid #E8D5C4; + padding: 24rpx; + margin-bottom: 16rpx; +} + +/* 徽章 */ +.badge { + display: inline-flex; + align-items: center; + padding: 4rpx 16rpx; + border-radius: 999rpx; + font-size: 20rpx; + font-weight: 500; +} +.badge-lucky { + background-color: #FEF2F2; + color: #C41E3A; +} +.badge-unlucky { + background-color: #F1F5F9; + color: #546E7A; +} +.badge-outline { + border: 1rpx solid #E8D5C4; + color: #6B5E53; + background: transparent; +} +.badge-festival { + background-color: #C41E3A; + color: #FFFFFF; +} + +/* 按钮 */ +.btn-primary { + background-color: #C41E3A; + color: #FFFFFF; + border-radius: 16rpx; + padding: 16rpx 32rpx; + font-size: 28rpx; + font-weight: 500; + border: none; +} +.btn-primary:active { + background-color: #8B1A2B; +} +.btn-outline { + background-color: transparent; + border: 2rpx solid #E8D5C4; + color: #1A1A1A; + border-radius: 16rpx; + padding: 16rpx 32rpx; + font-size: 28rpx; +} +.btn-ghost { + background-color: transparent; + color: #1A1A1A; + border-radius: 16rpx; + padding: 16rpx 32rpx; + font-size: 28rpx; +} + +/* 文本颜色 */ +.text-primary { color: #C41E3A; } +.text-secondary { color: #6B5E53; } +.text-muted { color: #9E8E7E; } +.text-lucky { color: #C41E3A; } +.text-unlucky { color: #546E7A; } + +/* 背景色 */ +.bg-card { background-color: #FFFFFF; } +.bg-page { background-color: #FFFBF5; } + +/* 边框 */ +.border { border: 1rpx solid #E8D5C4; } +.border-b { border-bottom: 1rpx solid #E8D5C4; } + +/* 布局 */ +.flex { display: flex; } +.flex-col { flex-direction: column; } +.items-center { align-items: center; } +.justify-center { justify-content: center; } +.justify-between { justify-content: space-between; } +.flex-1 { flex: 1; } +.text-center { text-align: center; } + +/* 间距 */ +.mt-1 { margin-top: 8rpx; } +.mt-2 { margin-top: 16rpx; } +.mb-1 { margin-bottom: 8rpx; } +.mb-2 { margin-bottom: 16rpx; } +.p-2 { padding: 16rpx; } +.p-3 { padding: 24rpx; } + +/* 圆角 */ +.rounded { border-radius: 16rpx; } +.rounded-lg { border-radius: 24rpx; } +.rounded-full { border-radius: 999rpx; } + +/* 字号 */ +.text-xs { font-size: 20rpx; } +.text-sm { font-size: 24rpx; } +.text-base { font-size: 28rpx; } +.text-lg { font-size: 32rpx; } +.text-xl { font-size: 36rpx; } +.text-2xl { font-size: 48rpx; } +.text-3xl { font-size: 60rpx; } + +/* 字重 */ +.font-medium { font-weight: 500; } +.font-bold { font-weight: 700; } + +/* 网格 */ +.grid { display: grid; } +.grid-cols-2 { grid-template-columns: repeat(2, 1fr); } +.grid-cols-3 { grid-template-columns: repeat(3, 1fr); } +.grid-cols-4 { grid-template-columns: repeat(4, 1fr); } +.grid-cols-7 { grid-template-columns: repeat(7, 1fr); } +.gap-1 { gap: 8rpx; } +.gap-2 { gap: 16rpx; } +.gap-3 { gap: 24rpx; } + +/* 隐藏 */ +.hidden { display: none; } + +/* 滚动 */ +.scroll-x { + overflow-x: auto; + white-space: nowrap; + -webkit-overflow-scrolling: touch; +} + +/* 安全区域 */ +.safe-area-bottom { + padding-bottom: env(safe-area-inset-bottom); +} diff --git a/mini/images/bazi-active.png b/mini/images/bazi-active.png new file mode 100644 index 0000000..91a99b9 Binary files /dev/null and b/mini/images/bazi-active.png differ diff --git a/mini/images/bazi.png b/mini/images/bazi.png new file mode 100644 index 0000000..91a99b9 Binary files /dev/null and b/mini/images/bazi.png differ diff --git a/mini/images/calendar-active.png b/mini/images/calendar-active.png new file mode 100644 index 0000000..91a99b9 Binary files /dev/null and b/mini/images/calendar-active.png differ diff --git a/mini/images/calendar.png b/mini/images/calendar.png new file mode 100644 index 0000000..91a99b9 Binary files /dev/null and b/mini/images/calendar.png differ diff --git a/mini/images/divination.png b/mini/images/divination.png new file mode 100644 index 0000000..91a99b9 Binary files /dev/null and b/mini/images/divination.png differ diff --git a/mini/images/fortune-active.png b/mini/images/fortune-active.png new file mode 100644 index 0000000..91a99b9 Binary files /dev/null and b/mini/images/fortune-active.png differ diff --git a/mini/images/fortune.png b/mini/images/fortune.png new file mode 100644 index 0000000..91a99b9 Binary files /dev/null and b/mini/images/fortune.png differ diff --git a/mini/images/home-active.png b/mini/images/home-active.png new file mode 100644 index 0000000..91a99b9 Binary files /dev/null and b/mini/images/home-active.png differ diff --git a/mini/images/home.png b/mini/images/home.png new file mode 100644 index 0000000..91a99b9 Binary files /dev/null and b/mini/images/home.png differ diff --git a/mini/images/settings-active.png b/mini/images/settings-active.png new file mode 100644 index 0000000..91a99b9 Binary files /dev/null and b/mini/images/settings-active.png differ diff --git a/mini/images/settings.png b/mini/images/settings.png new file mode 100644 index 0000000..91a99b9 Binary files /dev/null and b/mini/images/settings.png differ diff --git a/mini/pages/bazi/bazi.js b/mini/pages/bazi/bazi.js new file mode 100644 index 0000000..2ad1093 --- /dev/null +++ b/mini/pages/bazi/bazi.js @@ -0,0 +1,78 @@ +const { birthInfoToBazi, analyzeElementBalance } = require('../../utils/core/index.js'); + +Page({ + data: { + gender: 'male', + birthDate: '1990-01-01', + birthTime: '12:00', + ziSect: 'lateZiNextDay', + bazi: null, + pillars: [], + elements: [], + elementAnalysis: {} + }, + + onLoad() { + // 尝试从本地存储恢复 + const saved = wx.getStorageSync('lunar-bazi-input'); + if (saved) { + this.setData(saved); + } + }, + + setGender(e) { + this.setData({ gender: e.currentTarget.dataset.g }); + }, + + setZiSect(e) { + this.setData({ ziSect: e.currentTarget.dataset.s }); + }, + + onDateChange(e) { + this.setData({ birthDate: e.detail.value }); + }, + + onTimeChange(e) { + this.setData({ birthTime: e.detail.value }); + }, + + calculate() { + const { gender, birthDate, birthTime, ziSect } = this.data; + const [year, month, day] = birthDate.split('-').map(Number); + const [hour, minute] = birthTime.split(':').map(Number); + + const bazi = birthInfoToBazi({ year, month, day, hour, minute, gender, ziSect }); + const elementAnalysis = analyzeElementBalance(bazi.eightChar); + + const pillars = [ + { label: '年柱', ...bazi.eightChar.yearPillar }, + { label: '月柱', ...bazi.eightChar.monthPillar }, + { label: '日柱', ...bazi.eightChar.dayPillar }, + { label: '时柱', ...bazi.eightChar.hourPillar } + ]; + + const total = elementAnalysis.total || 1; + const elements = [ + { name: '木', count: elementAnalysis.wood, percent: Math.round(elementAnalysis.wood / total * 100) }, + { name: '火', count: elementAnalysis.fire, percent: Math.round(elementAnalysis.fire / total * 100) }, + { name: '土', count: elementAnalysis.earth, percent: Math.round(elementAnalysis.earth / total * 100) }, + { name: '金', count: elementAnalysis.metal, percent: Math.round(elementAnalysis.metal / total * 100) }, + { name: '水', count: elementAnalysis.water, percent: Math.round(elementAnalysis.water / total * 100) } + ]; + + this.setData({ bazi, pillars, elements, elementAnalysis }); + + // 保存输入 + wx.setStorageSync('lunar-bazi-input', { gender, birthDate, birthTime, ziSect }); + // 保存八字供运势页使用 + wx.setStorageSync('lunar-user-bazi', bazi); + }, + + reset() { + this.setData({ bazi: null }); + }, + + goFortune() { + wx.switchTab({ url: '/pages/fortune/fortune' }); + } +}); diff --git a/mini/pages/bazi/bazi.json b/mini/pages/bazi/bazi.json new file mode 100644 index 0000000..b0a08e9 --- /dev/null +++ b/mini/pages/bazi/bazi.json @@ -0,0 +1,4 @@ +{ + "navigationBarTitleText": "八字排盘", + "enablePullDownRefresh": false +} diff --git a/mini/pages/bazi/bazi.wxml b/mini/pages/bazi/bazi.wxml new file mode 100644 index 0000000..a827639 --- /dev/null +++ b/mini/pages/bazi/bazi.wxml @@ -0,0 +1,104 @@ + + + + 输入出生信息 + + + 性别 + + + + + + + + 出生日期 + + {{birthDate}} + + + + + 出生时间 + + {{birthTime}} + + + + + 子时流派 + + 晚子时日柱算次日 + 晚子时日柱算当日 + + + + 排盘 + + + + + + + 四柱八字 + + + {{item.label}} + {{item.ganzhi}} + {{item.tenStar || '日主'}} + + + + + + + 五行分析 + + + {{item.name}} + + + + {{item.count}} + + + + 日主:{{bazi.eightChar.dayMaster}} | dominant: {{elementAnalysis.dominant}} | weakest: {{elementAnalysis.weakest}} + + + + + + 大运 + + + + {{item.startAge}}-{{item.endAge}}岁 + {{item.ganzhi}} + {{item.startYear}}-{{item.endYear}} + + + + + + + + 流年 + + + + {{item.age}}岁 + {{item.ganzhi}} + {{item.year}} + + + + + + + + 重新排盘 + 看运势 + + + diff --git a/mini/pages/bazi/bazi.wxss b/mini/pages/bazi/bazi.wxss new file mode 100644 index 0000000..4a3e466 --- /dev/null +++ b/mini/pages/bazi/bazi.wxss @@ -0,0 +1,107 @@ +.container { + padding: 20rpx; +} + +.section-title { + font-size: 28rpx; + font-weight: 600; + margin-bottom: 16rpx; + color: #C41E3A; +} + +.form-item { + margin-bottom: 24rpx; +} + +.form-label { + font-size: 24rpx; + color: #6B5E53; + margin-bottom: 8rpx; + display: block; +} + +.picker-input { + padding: 20rpx; + background: #FFFBF5; + border-radius: 12rpx; + border: 1rpx solid #E8D5C4; +} + +.btn-outline.active { + background: #C41E3A; + color: #FFFFFF; + border-color: #C41E3A; +} + +.pillar { + display: flex; + flex-direction: column; + align-items: center; + padding: 20rpx; + background: #FFFBF5; + border-radius: 12rpx; +} + +.pillar-label { + font-size: 20rpx; + color: #9E8E7E; +} + +.pillar-ganzhi { + font-size: 36rpx; + font-weight: 700; + color: #C41E3A; + margin: 8rpx 0; +} + +.pillar-ten { + font-size: 20rpx; + color: #6B5E53; +} + +.element-bar { + display: flex; + flex-direction: column; + gap: 12rpx; +} + +.element-item { + display: flex; + align-items: center; + gap: 12rpx; +} + +.element-name { + width: 60rpx; + font-size: 24rpx; +} + +.element-track { + flex: 1; + height: 16rpx; + background: #E8D5C4; + border-radius: 8rpx; + overflow: hidden; +} + +.element-fill { + height: 100%; + background: #C41E3A; + border-radius: 8rpx; +} + +.element-count { + width: 60rpx; + text-align: right; + font-size: 24rpx; +} + +.fortune-item { + display: flex; + flex-direction: column; + align-items: center; + padding: 16rpx 24rpx; + background: #FFFBF5; + border-radius: 12rpx; + min-width: 160rpx; +} diff --git a/mini/pages/calendar/calendar.js b/mini/pages/calendar/calendar.js new file mode 100644 index 0000000..bd37927 --- /dev/null +++ b/mini/pages/calendar/calendar.js @@ -0,0 +1,86 @@ +const { getMonthCalendar } = require('../../utils/core/index.js'); + +Page({ + data: { + year: new Date().getFullYear(), + month: new Date().getMonth() + 1, + weekHeaders: ['日', '一', '二', '三', '四', '五', '六'], + calendarDays: [], + selectedDay: null, + showMonthPicker: false, + years: [], + months: [1,2,3,4,5,6,7,8,9,10,11,12], + pickerValue: [0, 0] + }, + + onLoad() { + const years = []; + for (let y = 1900; y <= 2100; y++) years.push(y); + this.setData({ years }); + this.loadCalendar(); + }, + + loadCalendar() { + const { year, month } = this.data; + const weeks = getMonthCalendar(year, month); + const calendarDays = []; + weeks.forEach(week => { + week.forEach(day => calendarDays.push(day)); + }); + this.setData({ calendarDays }); + }, + + prevMonth() { + let { year, month } = this.data; + month--; + if (month < 1) { month = 12; year--; } + this.setData({ year, month }, () => this.loadCalendar()); + }, + + nextMonth() { + let { year, month } = this.data; + month++; + if (month > 12) { month = 1; year++; } + this.setData({ year, month }, () => this.loadCalendar()); + }, + + showPicker() { + const { year, month } = this.data; + this.setData({ + showMonthPicker: true, + pickerValue: [year - 1900, month - 1] + }); + }, + + hidePicker() { + this.setData({ showMonthPicker: false }); + }, + + pickerChange(e) { + this.setData({ pickerValue: e.detail.value }); + }, + + confirmPicker() { + const { pickerValue, years, months } = this.data; + this.setData({ + year: years[pickerValue[0]], + month: months[pickerValue[1]], + showMonthPicker: false + }, () => this.loadCalendar()); + }, + + selectDay(e) { + const date = e.currentTarget.dataset.date; + const day = this.data.calendarDays.find(d => d.solarDate === date); + this.setData({ selectedDay: day }); + }, + + goDetail() { + const { selectedDay } = this.data; + if (selectedDay) { + wx.navigateTo({ url: `/pages/day-detail/day-detail?date=${selectedDay.solarDate}` }); + } + }, + + noop() {} +}); diff --git a/mini/pages/calendar/calendar.json b/mini/pages/calendar/calendar.json new file mode 100644 index 0000000..3d11152 --- /dev/null +++ b/mini/pages/calendar/calendar.json @@ -0,0 +1,4 @@ +{ + "navigationBarTitleText": "日历", + "enablePullDownRefresh": false +} diff --git a/mini/pages/calendar/calendar.wxml b/mini/pages/calendar/calendar.wxml new file mode 100644 index 0000000..352f33d --- /dev/null +++ b/mini/pages/calendar/calendar.wxml @@ -0,0 +1,60 @@ + + + + + {{year}}年{{month}}月 + + + + + + {{item}} + + + + + + {{item.solarDay}} + + {{item.lunarFestival || item.solarFestival || item.lunarDayName}} + + + + + + + + + + {{selectedDay.solarDate}} + {{selectedDay.lunarMonthName}}{{selectedDay.lunarDayName}} + + 详情 + + + + + + + + + 取消 + 选择年月 + 确定 + + + + {{item}}年 + + + {{item}}月 + + + + diff --git a/mini/pages/calendar/calendar.wxss b/mini/pages/calendar/calendar.wxss new file mode 100644 index 0000000..3375a62 --- /dev/null +++ b/mini/pages/calendar/calendar.wxss @@ -0,0 +1,92 @@ +.container { + padding: 20rpx; +} + +.day-cell { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 12rpx 4rpx; + background: #FFFFFF; + border-radius: 12rpx; + min-height: 100rpx; +} + +.day-cell.today { + background: #C41E3A; + color: #FFFFFF; +} + +.day-cell.today .day-lunar { + color: rgba(255,255,255,0.8); +} + +.day-cell.other-month { + opacity: 0.4; +} + +.day-number { + font-size: 28rpx; + font-weight: 500; +} + +.day-lunar { + font-size: 20rpx; + color: #9E8E7E; + margin-top: 4rpx; +} + +.day-lunar.festival { + color: #C41E3A; + font-weight: 500; +} + +.day-dot { + width: 8rpx; + height: 8rpx; + border-radius: 50%; + background: #C41E3A; + margin-top: 4rpx; +} + +.picker-mask { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0,0,0,0.5); + z-index: 100; + display: none; +} + +.picker-mask.show { + display: block; +} + +.picker-panel { + position: absolute; + bottom: 0; + left: 0; + right: 0; + background: #FFFFFF; + border-radius: 24rpx 24rpx 0 0; +} + +.picker-header { + display: flex; + justify-content: space-between; + padding: 24rpx; + border-bottom: 1rpx solid #E8D5C4; +} + +.picker-view { + height: 400rpx; +} + +.picker-item { + text-align: center; + line-height: 68rpx; + font-size: 32rpx; +} diff --git a/mini/pages/day-detail/day-detail.js b/mini/pages/day-detail/day-detail.js new file mode 100644 index 0000000..2707c45 --- /dev/null +++ b/mini/pages/day-detail/day-detail.js @@ -0,0 +1,43 @@ +const { getDayInfo, getAlmanacInfo } = require('../../utils/core/index.js'); + +Page({ + data: { + date: '', + dayInfo: {}, + almanac: {} + }, + + onLoad(options) { + const date = options.date || new Date().toISOString().split('T')[0]; + this.setData({ date }); + this.loadData(date); + }, + + loadData(dateStr) { + const [year, month, day] = dateStr.split('-').map(Number); + const dayInfo = getDayInfo(year, month, day); + const almanac = getAlmanacInfo(year, month, day); + this.setData({ dayInfo, almanac }); + }, + + addBookmark() { + const bookmarks = wx.getStorageSync('lunar-bookmarks') || []; + const { dayInfo } = this.data; + const exists = bookmarks.some(b => b.date === dayInfo.solarDate); + if (!exists) { + bookmarks.push({ + date: dayInfo.solarDate, + lunarDate: `${dayInfo.lunarMonthName}${dayInfo.lunarDayName}`, + note: '' + }); + wx.setStorageSync('lunar-bookmarks', bookmarks); + wx.showToast({ title: '已收藏', icon: 'success' }); + } else { + wx.showToast({ title: '已收藏过', icon: 'none' }); + } + }, + + goBazi() { + wx.navigateTo({ url: '/pages/bazi/bazi' }); + } +}); diff --git a/mini/pages/day-detail/day-detail.json b/mini/pages/day-detail/day-detail.json new file mode 100644 index 0000000..b95b1fc --- /dev/null +++ b/mini/pages/day-detail/day-detail.json @@ -0,0 +1,4 @@ +{ + "navigationBarTitleText": "日详情", + "enablePullDownRefresh": false +} diff --git a/mini/pages/day-detail/day-detail.wxml b/mini/pages/day-detail/day-detail.wxml new file mode 100644 index 0000000..5e8963f --- /dev/null +++ b/mini/pages/day-detail/day-detail.wxml @@ -0,0 +1,147 @@ + + + + + {{dayInfo.solarDay}} + {{dayInfo.solarYear}}年{{dayInfo.solarMonth}}月 {{dayInfo.weekDay}} + + + {{dayInfo.lunarFestival}} + {{dayInfo.solarFestival}} + {{dayInfo.solarTerm}} + + + + + + 农历 + + + 农历日期 + {{dayInfo.lunarYear}}年{{dayInfo.lunarMonthName}}{{dayInfo.lunarDayName}} + + + 生肖 + {{dayInfo.zodiac}} + + + 干支 + {{dayInfo.lunarYearGanzhi}}年 {{dayInfo.lunarMonthGanzhi}}月 {{dayInfo.lunarDayGanzhi}}日 + + + 月相 + {{dayInfo.moonPhase}} + + + + + + + 宜忌 + + + + + {{item}} + + + + + + {{item}} + + + + + + + + 黄历 + + + 值神 + {{almanac.duty}}({{almanac.dutyLuck === 'good' ? '吉' : almanac.dutyLuck === 'bad' ? '凶' : '平'}}) + + + 黄道黑道 + {{almanac.twelveStar.name}}({{almanac.twelveStar.ecliptic}}) + + + 二十八宿 + {{almanac.twentyEightStar.name}} + + + 九星 + {{almanac.nineStar.name}} + + + 六曜 + {{almanac.sixStar}} + + + 小六壬 + {{almanac.minorRen.name}} + + + 纳音 + {{almanac.nayin}} + + + 胎神 + {{almanac.fetus.position}} + + + + + + + 冲煞 + + + + {{almanac.clash}} + + + + {{almanac.harm}} + + + + {{almanac.combine}} + + + 煞方 + {{almanac.evilDirection}} + + + + + + + 彭祖百忌 + {{almanac.pengZu}} + + + + + 时辰黄历 + + + + {{item.name}}({{item.range}}) + {{item.ganzhi}} + + + 值神:{{item.twelveStar}} + 九星:{{item.nineStar}} + + + + + + + + 收藏 + 排八字 + + diff --git a/mini/pages/day-detail/day-detail.wxss b/mini/pages/day-detail/day-detail.wxss new file mode 100644 index 0000000..01f1957 --- /dev/null +++ b/mini/pages/day-detail/day-detail.wxss @@ -0,0 +1,41 @@ +.container { + padding: 20rpx; +} + +.header-card { + background: linear-gradient(135deg, #C41E3A 0%, #8B1A2B 100%); + color: #FFFFFF; + border: none; + padding: 40rpx 24rpx; +} + +.header-card .text-muted { + color: rgba(255,255,255,0.7); +} + +.section-title { + font-size: 28rpx; + font-weight: 600; + margin-bottom: 16rpx; + color: #C41E3A; +} + +.info-item { + padding: 16rpx; + background: #FFFBF5; + border-radius: 12rpx; +} + +.hour-list { + max-height: 600rpx; + overflow-y: auto; +} + +.hour-item { + padding: 16rpx; + border-bottom: 1rpx solid #E8D5C4; +} + +.hour-item:last-child { + border-bottom: none; +} diff --git a/mini/pages/divination/divination.js b/mini/pages/divination/divination.js new file mode 100644 index 0000000..717d6bd --- /dev/null +++ b/mini/pages/divination/divination.js @@ -0,0 +1,64 @@ +const { calculatePlumBlossom, calculateBoneWeight, HEAVEN_STEMS, EARTH_BRANCHES, LUNAR_MONTH_NAMES, LUNAR_DAY_NAMES, HOUR_NAMES } = require('../../utils/core/index.js'); + +Page({ + data: { + plumDate: new Date().toISOString().split('T')[0], + plumTime: '12:00', + plumResult: null, + yearGanzhiList: [], + monthList: LUNAR_MONTH_NAMES, + dayList: LUNAR_DAY_NAMES, + hourList: HOUR_NAMES, + boneYearIndex: 0, + boneMonthIndex: 0, + boneDayIndex: 0, + boneHourIndex: 0, + boneResult: null + }, + + onLoad() { + const yearGanzhiList = []; + for (let i = 0; i < 60; i++) { + yearGanzhiList.push(HEAVEN_STEMS[i % 10] + EARTH_BRANCHES[i % 12]); + } + this.setData({ yearGanzhiList }); + }, + + onPlumDateChange(e) { + this.setData({ plumDate: e.detail.value }); + }, + + onPlumTimeChange(e) { + this.setData({ plumTime: e.detail.value }); + }, + + calcPlum() { + const { plumDate, plumTime } = this.data; + const [year, month, day] = plumDate.split('-').map(Number); + const [hour] = plumTime.split(':').map(Number); + const result = calculatePlumBlossom(year, month, day, hour); + this.setData({ plumResult: result }); + }, + + onBoneYearChange(e) { + this.setData({ boneYearIndex: e.detail.value }); + }, + + onBoneMonthChange(e) { + this.setData({ boneMonthIndex: e.detail.value }); + }, + + onBoneDayChange(e) { + this.setData({ boneDayIndex: e.detail.value }); + }, + + onBoneHourChange(e) { + this.setData({ boneHourIndex: e.detail.value }); + }, + + calcBone() { + const { boneYearIndex, boneMonthIndex, boneDayIndex, boneHourIndex } = this.data; + const result = calculateBoneWeight(boneYearIndex, boneMonthIndex + 1, boneDayIndex + 1, boneHourIndex); + this.setData({ boneResult: result }); + } +}); diff --git a/mini/pages/divination/divination.json b/mini/pages/divination/divination.json new file mode 100644 index 0000000..ac33cae --- /dev/null +++ b/mini/pages/divination/divination.json @@ -0,0 +1,4 @@ +{ + "navigationBarTitleText": "占卜", + "enablePullDownRefresh": false +} diff --git a/mini/pages/divination/divination.wxml b/mini/pages/divination/divination.wxml new file mode 100644 index 0000000..c6a255f --- /dev/null +++ b/mini/pages/divination/divination.wxml @@ -0,0 +1,110 @@ + + + + 梅花易数 + + 起卦时间 + + {{plumDate}} + + + + 时辰 + + {{plumTime}} + + + 起卦 + + + + + 卦象 + + + {{plumResult.upperTrigram.symbol}} + {{plumResult.upperTrigram.name}}({{plumResult.upperTrigram.element}}) + + + {{plumResult.lowerTrigram.symbol}} + {{plumResult.lowerTrigram.name}}({{plumResult.lowerTrigram.element}}) + + + + {{plumResult.originalHexagram.name}} + 第{{plumResult.changingLine}}爻动 + + + {{plumResult.originalHexagram.judgment}} + + + {{plumResult.originalHexagram.image}} + + + 变卦:{{plumResult.transformedHexagram.name}} + + + 体用关系:{{plumResult.relationship}} + + + + + + 称骨算命 + + 出生年份(干支) + + {{yearGanzhiList[boneYearIndex]}} + + + + 农历月份 + + {{monthList[boneMonthIndex]}} + + + + 农历日期 + + {{dayList[boneDayIndex]}} + + + + 时辰 + + {{hourList[boneHourIndex]}} + + + 算命 + + + + + 称骨结果 + + {{boneResult.totalLiang}}两{{boneResult.totalQian}}钱 + {{boneResult.fortune}} + + + {{boneResult.interpretation}} + + + + + {{boneResult.yearWeight}}钱 + + + + {{boneResult.monthWeight}}钱 + + + + {{boneResult.dayWeight}}钱 + + + + {{boneResult.hourWeight}}钱 + + + + diff --git a/mini/pages/divination/divination.wxss b/mini/pages/divination/divination.wxss new file mode 100644 index 0000000..479a5b5 --- /dev/null +++ b/mini/pages/divination/divination.wxss @@ -0,0 +1,52 @@ +.container { + padding: 20rpx; +} + +.section-title { + font-size: 28rpx; + font-weight: 600; + margin-bottom: 16rpx; + color: #C41E3A; +} + +.form-item { + margin-bottom: 24rpx; +} + +.form-label { + font-size: 24rpx; + color: #6B5E53; + margin-bottom: 8rpx; + display: block; +} + +.picker-input { + padding: 20rpx; + background: #FFFBF5; + border-radius: 12rpx; + border: 1rpx solid #E8D5C4; +} + +.hexagram-display { + display: flex; + flex-direction: column; + align-items: center; + padding: 24rpx; + background: #FFFBF5; + border-radius: 16rpx; +} + +.trigram-row { + display: flex; + align-items: center; + gap: 16rpx; + margin: 8rpx 0; +} + +.trigram-symbol { + font-size: 64rpx; +} + +.trigram-name { + font-size: 28rpx; +} diff --git a/mini/pages/fortune/fortune.js b/mini/pages/fortune/fortune.js new file mode 100644 index 0000000..fcd45bf --- /dev/null +++ b/mini/pages/fortune/fortune.js @@ -0,0 +1,47 @@ +const { calculateDailyFortune } = require('../../utils/core/index.js'); + +Page({ + data: { + hasBazi: false, + fortune: null, + scoreLevelText: '' + }, + + onShow() { + this.loadFortune(); + }, + + onPullDownRefresh() { + this.loadFortune(); + wx.stopPullDownRefresh(); + }, + + loadFortune() { + const bazi = wx.getStorageSync('lunar-user-bazi'); + if (!bazi) { + this.setData({ hasBazi: false, fortune: null }); + return; + } + + const today = new Date(); + const fortune = calculateDailyFortune(bazi.eightChar, today); + + const levelMap = { + great: '大吉', + good: '吉', + fair: '平', + poor: '凶', + bad: '大凶' + }; + + this.setData({ + hasBazi: true, + fortune, + scoreLevelText: levelMap[fortune.scoreLevel] || '平' + }); + }, + + goBazi() { + wx.switchTab({ url: '/pages/bazi/bazi' }); + } +}); diff --git a/mini/pages/fortune/fortune.json b/mini/pages/fortune/fortune.json new file mode 100644 index 0000000..007652b --- /dev/null +++ b/mini/pages/fortune/fortune.json @@ -0,0 +1,4 @@ +{ + "navigationBarTitleText": "每日运势", + "enablePullDownRefresh": true +} diff --git a/mini/pages/fortune/fortune.wxml b/mini/pages/fortune/fortune.wxml new file mode 100644 index 0000000..1ed08f6 --- /dev/null +++ b/mini/pages/fortune/fortune.wxml @@ -0,0 +1,98 @@ + + + + 请先排八字以查看每日运势 + 去排八字 + + + + + + + + {{fortune.overallScore}} + {{scoreLevelText}} + + {{fortune.date}} {{fortune.lunarDate}} + + + + + 幸运元素 + + + 幸运色 + {{fortune.luckyMeta.colors.join('、')}} + + + 幸运数字 + {{fortune.luckyMeta.numbers.join('、')}} + + + 幸运方位 + {{fortune.luckyMeta.direction}} + + + 五行 + {{fortune.luckyMeta.element}} + + + + + + + 四柱与今日关系 + + + + {{item.pillarLabel}} + {{item.score > 0 ? '+' : ''}}{{item.score}} + + + {{item.userGanzhi}} vs {{item.dayGanzhi}} + + + {{item.stemTenStar}} + 天干合 + 天干冲 + 地支合 + 地支冲 + + + + + + + + 今日建议 + + + {{item}} + + + + + + + 分项运势 + + + 爱情 + {{fortune.categoryScores.love}} + + + 事业 + {{fortune.categoryScores.career}} + + + 财运 + {{fortune.categoryScores.wealth}} + + + 健康 + {{fortune.categoryScores.health}} + + + + + diff --git a/mini/pages/fortune/fortune.wxss b/mini/pages/fortune/fortune.wxss new file mode 100644 index 0000000..afefe71 --- /dev/null +++ b/mini/pages/fortune/fortune.wxss @@ -0,0 +1,47 @@ +.container { + padding: 20rpx; +} + +.score-card { + background: linear-gradient(135deg, #C41E3A 0%, #8B1A2B 100%); + color: #FFFFFF; + border: none; + padding: 40rpx 24rpx; +} + +.score-value { + font-size: 80rpx; + font-weight: 700; +} + +.score-level { + font-size: 32rpx; + margin-left: 16rpx; + opacity: 0.9; +} + +.section-title { + font-size: 28rpx; + font-weight: 600; + margin-bottom: 16rpx; + color: #C41E3A; +} + +.info-item { + padding: 16rpx; + background: #FFFBF5; + border-radius: 12rpx; +} + +.relation-item { + padding: 16rpx; + border-bottom: 1rpx solid #E8D5C4; +} + +.relation-item:last-child { + border-bottom: none; +} + +.suggestion-item { + padding: 12rpx 0; +} diff --git a/mini/pages/home/home.js b/mini/pages/home/home.js new file mode 100644 index 0000000..95531e5 --- /dev/null +++ b/mini/pages/home/home.js @@ -0,0 +1,28 @@ +const { getTodayInfo, getAlmanacInfo } = require('../../utils/core/index.js'); + +Page({ + data: { + dayInfo: {}, + almanac: {} + }, + + onLoad() { + this.loadData(); + }, + + onPullDownRefresh() { + this.loadData(); + wx.stopPullDownRefresh(); + }, + + loadData() { + const dayInfo = getTodayInfo(); + const almanac = getAlmanacInfo(dayInfo.solarYear, dayInfo.solarMonth, dayInfo.solarDay); + this.setData({ dayInfo, almanac }); + }, + + navTo(e) { + const url = e.currentTarget.dataset.url; + wx.navigateTo({ url }); + } +}); diff --git a/mini/pages/home/home.json b/mini/pages/home/home.json new file mode 100644 index 0000000..d343716 --- /dev/null +++ b/mini/pages/home/home.json @@ -0,0 +1,5 @@ +{ + "navigationBarTitleText": "万年历", + "enablePullDownRefresh": true, + "backgroundTextStyle": "dark" +} diff --git a/mini/pages/home/home.wxml b/mini/pages/home/home.wxml new file mode 100644 index 0000000..8f91db7 --- /dev/null +++ b/mini/pages/home/home.wxml @@ -0,0 +1,94 @@ + + + + + + {{dayInfo.solarDay}} + {{dayInfo.solarMonth}}月{{dayInfo.solarYear}}年 + + + {{dayInfo.weekDay}} + {{dayInfo.constellation}} + + + + {{dayInfo.lunarFestival}} + {{dayInfo.solarFestival}} + {{dayInfo.solarTerm}} + + + + + + + + {{dayInfo.lunarMonthName}}{{dayInfo.lunarDayName}} + {{dayInfo.lunarYearGanzhi}}年 {{dayInfo.lunarMonthGanzhi}}月 {{dayInfo.lunarDayGanzhi}}日 + + + {{dayInfo.zodiac}}年 + {{dayInfo.moonPhase}} + + + + + + + + + + + {{item}} + + + + + + {{item}} + + + + + + + + + + 值神 + {{almanac.duty}} + + + 黄道 + {{almanac.twelveStar.name}}({{almanac.twelveStar.ecliptic}}) + + + 二十八宿 + {{almanac.twentyEightStar.name}} + + + 六曜 + {{almanac.sixStar}} + + + + + + + + + 日历 + + + + 八字 + + + + 运势 + + + + 占卜 + + + diff --git a/mini/pages/home/home.wxss b/mini/pages/home/home.wxss new file mode 100644 index 0000000..bd4cc92 --- /dev/null +++ b/mini/pages/home/home.wxss @@ -0,0 +1,31 @@ +.container { + padding: 20rpx; + padding-bottom: 40rpx; +} + +.date-card { + background: linear-gradient(135deg, #C41E3A 0%, #8B1A2B 100%); + color: #FFFFFF; + border: none; +} + +.date-card .text-muted { + color: rgba(255,255,255,0.7); +} + +.quick-entry { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 24rpx; + background: #FFFFFF; + border-radius: 16rpx; + border: 1rpx solid #E8D5C4; +} + +.quick-icon { + width: 64rpx; + height: 64rpx; + margin-bottom: 8rpx; +} diff --git a/mini/pages/settings/settings.js b/mini/pages/settings/settings.js new file mode 100644 index 0000000..8c2faf0 --- /dev/null +++ b/mini/pages/settings/settings.js @@ -0,0 +1,74 @@ +Page({ + data: { + userInfo: null, + weekStartDay: 0, + ziHourSect: 'lateZiNextDay', + solarTime: true, + bookmarks: [] + }, + + onLoad() { + this.loadSettings(); + this.loadBookmarks(); + }, + + onShow() { + this.loadBookmarks(); + }, + + loadSettings() { + const settings = wx.getStorageSync('lunar-settings') || {}; + this.setData({ + weekStartDay: settings.weekStartDay || 0, + ziHourSect: settings.ziHourSect || 'lateZiNextDay', + solarTime: settings.solarTime !== false + }); + }, + + saveSettings() { + const { weekStartDay, ziHourSect, solarTime } = this.data; + wx.setStorageSync('lunar-settings', { weekStartDay, ziHourSect, solarTime }); + }, + + loadBookmarks() { + const bookmarks = wx.getStorageSync('lunar-bookmarks') || []; + this.setData({ bookmarks }); + }, + + setWeekStart(e) { + this.setData({ weekStartDay: Number(e.currentTarget.dataset.v) }); + this.saveSettings(); + }, + + setZiSect(e) { + this.setData({ ziHourSect: e.currentTarget.dataset.v }); + this.saveSettings(); + }, + + onSolarTimeChange(e) { + this.setData({ solarTime: e.detail.value }); + this.saveSettings(); + }, + + removeBookmark(e) { + const date = e.currentTarget.dataset.date; + let bookmarks = this.data.bookmarks.filter(b => b.date !== date); + wx.setStorageSync('lunar-bookmarks', bookmarks); + this.setData({ bookmarks }); + wx.showToast({ title: '已删除', icon: 'success' }); + }, + + login() { + wx.getUserProfile({ + desc: '用于完善用户资料', + success: (res) => { + this.setData({ userInfo: res.userInfo }); + wx.setStorageSync('lunar-user-info', res.userInfo); + wx.showToast({ title: '登录成功', icon: 'success' }); + }, + fail: () => { + wx.showToast({ title: '登录失败', icon: 'none' }); + } + }); + } +}); diff --git a/mini/pages/settings/settings.json b/mini/pages/settings/settings.json new file mode 100644 index 0000000..e8d793c --- /dev/null +++ b/mini/pages/settings/settings.json @@ -0,0 +1,4 @@ +{ + "navigationBarTitleText": "设置", + "enablePullDownRefresh": false +} diff --git a/mini/pages/settings/settings.wxml b/mini/pages/settings/settings.wxml new file mode 100644 index 0000000..1068513 --- /dev/null +++ b/mini/pages/settings/settings.wxml @@ -0,0 +1,68 @@ + + + + 用户信息 + + + + {{userInfo.nickName}} + 已登录 + + + 微信登录 + + + + + 偏好设置 + + + 周起始日 + + 周日 + 周一 + + + + + 子时流派 + + 晚子时算次日 + 晚子时算当日 + + + + + 真太阳时校正 + + + + + + + 我的收藏 + + + + + {{item.date}} + {{item.lunarDate}} + + 删除 + + + + + 暂无收藏 + + + + + + 关于 + + 万年历小程序 v1.0.0 + 提供农历、黄历、八字、运势、占卜等功能 + + + diff --git a/mini/pages/settings/settings.wxss b/mini/pages/settings/settings.wxss new file mode 100644 index 0000000..6b7a84c --- /dev/null +++ b/mini/pages/settings/settings.wxss @@ -0,0 +1,47 @@ +.container { + padding: 20rpx; +} + +.section-title { + font-size: 28rpx; + font-weight: 600; + margin-bottom: 16rpx; + color: #C41E3A; +} + +.avatar { + width: 80rpx; + height: 80rpx; + border-radius: 50%; +} + +.setting-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 20rpx 0; + border-bottom: 1rpx solid #E8D5C4; +} + +.setting-item:last-child { + border-bottom: none; +} + +.setting-label { + font-size: 28rpx; +} + +.btn-outline.active { + background: #C41E3A; + color: #FFFFFF; + border-color: #C41E3A; +} + +.bookmark-item { + padding: 16rpx 0; + border-bottom: 1rpx solid #E8D5C4; +} + +.bookmark-item:last-child { + border-bottom: none; +} diff --git a/mini/pages/solar-terms/solar-terms.js b/mini/pages/solar-terms/solar-terms.js new file mode 100644 index 0000000..462b419 --- /dev/null +++ b/mini/pages/solar-terms/solar-terms.js @@ -0,0 +1,36 @@ +const { SOLAR_TERMS, SOLAR_TERM_DATES } = require('../../utils/core/index.js'); + +Page({ + data: { + year: new Date().getFullYear(), + terms: [] + }, + + onLoad() { + this.loadTerms(); + }, + + loadTerms() { + const { year } = this.data; + const today = new Date(); + const terms = []; + + for (let m = 0; m < 12; m++) { + for (let t = 0; t < 2; t++) { + const termIndex = m * 2 + t; + const day = SOLAR_TERM_DATES[m][t]; + const dateStr = `${year}-${String(m + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`; + const termDate = new Date(year, m, day); + terms.push({ + name: SOLAR_TERMS[termIndex], + date: dateStr, + isPast: termDate < today + }); + } + } + + // 按日期排序 + terms.sort((a, b) => new Date(a.date) - new Date(b.date)); + this.setData({ terms }); + } +}); diff --git a/mini/pages/solar-terms/solar-terms.json b/mini/pages/solar-terms/solar-terms.json new file mode 100644 index 0000000..c223a2b --- /dev/null +++ b/mini/pages/solar-terms/solar-terms.json @@ -0,0 +1,4 @@ +{ + "navigationBarTitleText": "二十四节气", + "enablePullDownRefresh": false +} diff --git a/mini/pages/solar-terms/solar-terms.wxml b/mini/pages/solar-terms/solar-terms.wxml new file mode 100644 index 0000000..f4238f9 --- /dev/null +++ b/mini/pages/solar-terms/solar-terms.wxml @@ -0,0 +1,16 @@ + + + {{year}}年二十四节气 + + + + + {{item.name}} + {{item.date}} + + {{item.isPast ? '已过' : '未来'}} + + + + + diff --git a/mini/pages/solar-terms/solar-terms.wxss b/mini/pages/solar-terms/solar-terms.wxss new file mode 100644 index 0000000..a52fa81 --- /dev/null +++ b/mini/pages/solar-terms/solar-terms.wxss @@ -0,0 +1,24 @@ +.container { + padding: 20rpx; +} + +.section-title { + font-size: 28rpx; + font-weight: 600; + margin-bottom: 16rpx; + color: #C41E3A; +} + +.term-list { + display: flex; + flex-direction: column; +} + +.term-item { + padding: 20rpx 0; + border-bottom: 1rpx solid #E8D5C4; +} + +.term-item:last-child { + border-bottom: none; +} diff --git a/mini/project.config.json b/mini/project.config.json new file mode 100644 index 0000000..b06f6e6 --- /dev/null +++ b/mini/project.config.json @@ -0,0 +1,51 @@ +{ + "description": "万年历小程序", + "packOptions": { + "ignore": [], + "include": [] + }, + "setting": { + "bundle": false, + "userConfirmedBundleSwitch": false, + "urlCheck": true, + "scopeDataCheck": false, + "coverView": true, + "es6": true, + "postcss": true, + "compileHotReLoad": false, + "lazyloadPlaceholderEnable": false, + "preloadBackgroundData": false, + "minified": true, + "autoAudits": false, + "newFeature": false, + "uglifyFileName": false, + "uploadWithSourceMap": true, + "useIsolateContext": true, + "nodeModules": false, + "enhance": true, + "useMultiFrameRuntime": true, + "useApiHook": true, + "useApiHostProcess": true, + "showShadowRootInWxmlPanel": true, + "packNpmManually": false, + "enableEngineNative": false, + "packNpmRelationList": [], + "minifyWXSS": true, + "showES6CompileOption": false, + "minifyWXML": true, + "babelSetting": { + "ignore": [], + "disablePlugins": [], + "outputPath": "" + } + }, + "compileType": "miniprogram", + "libVersion": "3.4.0", + "appid": "touristappid", + "projectname": "lunar-mini", + "condition": {}, + "editorSetting": { + "tabIndent": "insertSpaces", + "tabSize": 2 + } +} diff --git a/mini/sitemap.json b/mini/sitemap.json new file mode 100644 index 0000000..1de189d --- /dev/null +++ b/mini/sitemap.json @@ -0,0 +1,8 @@ +{ + "rules": [ + { + "action": "allow", + "page": "*" + } + ] +} diff --git a/mini/utils/core/data.js b/mini/utils/core/data.js new file mode 100644 index 0000000..5bf41c5 --- /dev/null +++ b/mini/utils/core/data.js @@ -0,0 +1,406 @@ +/** + * 农历数据表 1900-2100 + * 数据来源:香港天文台 + * 每年一个整数,bit 表示: + * 1-12: 1-12月大小月 (1=大月30天, 0=小月29天) + * 13-16: 闰月月份 (0=无闰月) + * 17: 闰月大小 (1=大月, 0=小月) + */ +const LUNAR_INFO = [ + 0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2, + 0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977, + 0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970, + 0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950, + 0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557, + 0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5b0, 0x14573, 0x052b0, 0x0a9a8, 0x0e950, 0x06aa0, + 0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0, + 0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b6a0, 0x195a6, + 0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570, + 0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x055c0, 0x0ab60, 0x096d5, 0x092e0, + 0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5, + 0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930, + 0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530, + 0x05aa0, 0x076a3, 0x096d0, 0x04afb, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45, + 0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0, + 0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06b20, 0x1a6c4, 0x0aae0, + 0x0a2e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4, + 0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0, + 0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160, + 0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252, + 0x0d520 +]; + +/** 天干 */ +const HEAVEN_STEMS = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸']; +/** 地支 */ +const EARTH_BRANCHES = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥']; +/** 生肖 */ +const ZODIACS = ['鼠', '牛', '虎', '兔', '龙', '蛇', '马', '羊', '猴', '鸡', '狗', '猪']; +/** 农历月份名 */ +const LUNAR_MONTH_NAMES = ['正月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '冬月', '腊月']; +/** 农历日期名 */ +const LUNAR_DAY_NAMES = [ + '初一', '初二', '初三', '初四', '初五', '初六', '初七', '初八', '初九', '初十', + '十一', '十二', '十三', '十四', '十五', '十六', '十七', '十八', '十九', '二十', + '廿一', '廿二', '廿三', '廿四', '廿五', '廿六', '廿七', '廿八', '廿九', '三十' +]; +/** 星期名 */ +const WEEK_NAMES = ['日', '一', '二', '三', '四', '五', '六']; +/** 星座 */ +const CONSTELLATIONS = ['摩羯座', '水瓶座', '双鱼座', '白羊座', '金牛座', '双子座', '巨蟹座', '狮子座', '处女座', '天秤座', '天蝎座', '射手座', '摩羯座']; +/** 星座日期边界 */ +const CONSTELLATION_DATES = [20, 19, 21, 20, 21, 22, 23, 23, 23, 24, 23, 22]; + +/** 二十四节气名 */ +const SOLAR_TERMS = [ + '冬至', '小寒', '大寒', '立春', '雨水', '惊蛰', + '春分', '清明', '谷雨', '立夏', '小满', '芒种', + '夏至', '小暑', '大暑', '立秋', '处暑', '白露', + '秋分', '寒露', '霜降', '立冬', '小雪', '大雪' +]; + +/** 二十四节气大致日期(每月两个) */ +const SOLAR_TERM_DATES = [ + [6, 21], // 小寒, 大寒 (1月) + [4, 19], // 立春, 雨水 (2月) + [6, 21], // 惊蛰, 春分 (3月) + [5, 20], // 清明, 谷雨 (4月) + [6, 21], // 立夏, 小满 (5月) + [6, 21], // 芒种, 夏至 (6月) + [7, 23], // 小暑, 大暑 (7月) + [8, 23], // 立秋, 处暑 (8月) + [8, 23], // 白露, 秋分 (9月) + [8, 24], // 寒露, 霜降 (10月) + [8, 22], // 立冬, 小雪 (11月) + [7, 22] // 大雪, 冬至 (12月) +]; + +/** 公历节日 */ +const SOLAR_FESTIVALS = { + '1-1': '元旦', '2-14': '情人节', '3-8': '妇女节', '3-12': '植树节', + '4-1': '愚人节', '5-1': '劳动节', '5-4': '青年节', '6-1': '儿童节', + '7-1': '建党节', '8-1': '建军节', '9-10': '教师节', '10-1': '国庆节', + '12-24': '平安夜', '12-25': '圣诞节' +}; + +/** 农历节日 */ +const LUNAR_FESTIVALS = { + '1-1': '春节', '1-15': '元宵节', '2-2': '龙抬头', '5-5': '端午节', + '7-7': '七夕', '7-15': '中元节', '8-15': '中秋节', '9-9': '重阳节', + '12-8': '腊八节', '12-23': '小年', '12-30': '除夕' +}; + +/** 佛教节日 */ +const BUDDHIST_FESTIVALS = { + '1-1': '弥勒菩萨圣诞', '2-8': '释迦牟尼佛出家日', '2-15': '释迦牟尼佛涅槃日', + '2-19': '观音菩萨圣诞', '2-21': '普贤菩萨圣诞', '3-16': '准提菩萨圣诞', + '4-4': '文殊菩萨圣诞', '4-8': '释迦牟尼佛圣诞(浴佛节)', '5-3': '伽蓝菩萨圣诞', + '6-3': '韦驮菩萨圣诞', '6-19': '观音菩萨成道日', '7-13': '大势至菩萨圣诞', + '7-15': '盂兰盆节(佛欢喜日)', '7-30': '地藏菩萨圣诞', '8-22': '燃灯佛圣诞', + '9-19': '观音菩萨出家日', '9-30': '药师佛圣诞', '10-5': '达摩祖师诞辰', + '11-17': '阿弥陀佛圣诞', '12-8': '释迦牟尼佛成道日(腊八)', '12-29': '华严菩萨圣诞' +}; + +/** 纳音表 */ +const NAYIN_TABLE = [ + '海中金', '炉中火', '大林木', '路旁土', '剑锋金', '山头火', + '涧下水', '城头土', '白蜡金', '杨柳木', '泉中水', '屋上土', + '霹雳火', '松柏木', '长流水', '沙中金', '山下火', '平地木', + '壁上土', '金箔金', '覆灯火', '天河水', '大驿土', '钗钏金', + '桑柘木', '大溪水', '沙中土', '天上火', '石榴木', '大海水' +]; + +/** 时辰名 */ +const HOUR_NAMES = ['子时', '丑时', '寅时', '卯时', '辰时', '巳时', '午时', '未时', '申时', '酉时', '戌时', '亥时']; +/** 时辰时间范围 */ +const HOUR_RANGES = ['23:00-01:00', '01:00-03:00', '03:00-05:00', '05:00-07:00', '07:00-09:00', '09:00-11:00', '11:00-13:00', '13:00-15:00', '15:00-17:00', '17:00-19:00', '19:00-21:00', '21:00-23:00']; + +/** 建除十二神 */ +const DUTY_NAMES = ['建', '除', '满', '平', '定', '执', '破', '危', '成', '收', '开', '闭']; +/** 黄道黑道 */ +const TWELVE_STARS = ['青龙', '明堂', '天刑', '朱雀', '金匮', '天德', '白虎', '玉堂', '天牢', '玄武', '司命', '勾陈']; +/** 二十八宿 */ +const TWENTY_EIGHT_STARS = [ + '角木蛟', '亢金龙', '氐土貉', '房日兔', '心月狐', '尾火虎', '箕水豹', + '斗木獬', '牛金牛', '女土蝠', '虚日鼠', '危月燕', '室火猪', '壁水貐', + '奎木狼', '娄金狗', '胃土雉', '昴日鸡', '毕月乌', '觜火猴', '参水猿', + '井木犴', '鬼金羊', '柳土獐', '星日马', '张月鹿', '翼火蛇', '轸水蚓' +]; +/** 九星 */ +const NINE_STARS = ['一白', '二黑', '三碧', '四绿', '五黄', '六白', '七赤', '八白', '九紫']; +/** 六曜 */ +const SIX_STARS = ['大安', '留连', '速喜', '赤口', '小吉', '空亡']; +/** 彭祖百忌 */ +const PENG_ZU = { + stems: ['甲不开仓', '乙不栽植', '丙不修灶', '丁不剃头', '戊不受田', '己不破券', '庚不经络', '辛不合酱', '壬不泱水', '癸不词讼'], + branches: ['子不问卜', '丑不冠带', '寅不祭祀', '卯不穿井', '辰不哭泣', '巳不远行', '午不苫盖', '未不服药', '申不安床', '酉不会客', '戌不吃犬', '亥不嫁娶'] +}; + +/** 五行 */ +const ELEMENTS = ['木', '火', '土', '金', '水']; +/** 天干五行 */ +const STEM_ELEMENTS = { '甲': '木', '乙': '木', '丙': '火', '丁': '火', '戊': '土', '己': '土', '庚': '金', '辛': '金', '壬': '水', '癸': '水' }; +/** 地支五行 */ +const BRANCH_ELEMENTS = { '子': '水', '丑': '土', '寅': '木', '卯': '木', '辰': '土', '巳': '火', '午': '火', '未': '土', '申': '金', '酉': '金', '戌': '土', '亥': '水' }; +/** 天干阴阳 */ +const STEM_YINYANG = { '甲': 'yang', '乙': 'yin', '丙': 'yang', '丁': 'yin', '戊': 'yang', '己': 'yin', '庚': 'yang', '辛': 'yin', '壬': 'yang', '癸': 'yin' }; +/** 地支阴阳 */ +const BRANCH_YINYANG = { '子': 'yang', '丑': 'yin', '寅': 'yang', '卯': 'yin', '辰': 'yang', '巳': 'yin', '午': 'yang', '未': 'yin', '申': 'yang', '酉': 'yin', '戌': 'yang', '亥': 'yin' }; + +/** 地支藏干 */ +const HIDE_STEMS = { + '子': [{ stem: '癸', type: '本气' }], + '丑': [{ stem: '己', type: '本气' }, { stem: '癸', type: '中气' }, { stem: '辛', type: '余气' }], + '寅': [{ stem: '甲', type: '本气' }, { stem: '丙', type: '中气' }, { stem: '戊', type: '余气' }], + '卯': [{ stem: '乙', type: '本气' }], + '辰': [{ stem: '戊', type: '本气' }, { stem: '乙', type: '中气' }, { stem: '癸', type: '余气' }], + '巳': [{ stem: '丙', type: '本气' }, { stem: '庚', type: '中气' }, { stem: '戊', type: '余气' }], + '午': [{ stem: '丁', type: '本气' }, { stem: '己', type: '中气' }], + '未': [{ stem: '己', type: '本气' }, { stem: '丁', type: '中气' }, { stem: '乙', type: '余气' }], + '申': [{ stem: '庚', type: '本气' }, { stem: '壬', type: '中气' }, { stem: '戊', type: '余气' }], + '酉': [{ stem: '辛', type: '本气' }], + '戌': [{ stem: '戊', type: '本气' }, { stem: '辛', type: '中气' }, { stem: '丁', type: '余气' }], + '亥': [{ stem: '壬', type: '本气' }, { stem: '甲', type: '中气' }] +}; + +/** 十二长生 */ +const TERRAINS = ['长生', '沐浴', '冠带', '临官', '帝旺', '衰', '病', '死', '墓', '绝', '胎', '养']; + +/** 十神 */ +const TEN_STARS = ['比肩', '劫财', '食神', '伤官', '偏财', '正财', '七杀', '正官', '偏印', '正印']; + +/** 地支六合 */ +const BRANCH_COMBINE = { '子': '丑', '丑': '子', '寅': '亥', '亥': '寅', '卯': '戌', '戌': '卯', '辰': '酉', '酉': '辰', '巳': '申', '申': '巳', '午': '未', '未': '午' }; +/** 地支六冲 */ +const BRANCH_OPPOSITE = { '子': '午', '午': '子', '丑': '未', '未': '丑', '寅': '申', '申': '寅', '卯': '酉', '酉': '卯', '辰': '戌', '戌': '辰', '巳': '亥', '亥': '巳' }; +/** 地支六害 */ +const BRANCH_HARM = { '子': '未', '未': '子', '丑': '午', '午': '丑', '寅': '巳', '巳': '寅', '卯': '辰', '辰': '卯', '申': '亥', '亥': '申', '酉': '戌', '戌': '酉' }; +/** 地支相刑 */ +const BRANCH_PUNISH = [ + ['寅', '巳'], ['巳', '申'], ['申', '寅'], + ['丑', '戌'], ['戌', '未'], ['未', '丑'], + ['子', '卯'], ['卯', '子'], + ['辰', '辰'], ['午', '午'], ['酉', '酉'], ['亥', '亥'] +]; +/** 地支三合局 */ +const THREE_COMBINES = { + '水局': ['申', '子', '辰'], + '木局': ['亥', '卯', '未'], + '火局': ['寅', '午', '戌'], + '金局': ['巳', '酉', '丑'] +}; +/** 天干合 */ +const STEM_COMBINE = { '甲': '己', '己': '甲', '乙': '庚', '庚': '乙', '丙': '辛', '辛': '丙', '丁': '壬', '壬': '丁', '戊': '癸', '癸': '戊' }; +/** 天干冲 */ +const STEM_OPPOSITE = { '甲': '庚', '庚': '甲', '乙': '辛', '辛': '乙', '丙': '壬', '壬': '丙', '丁': '癸', '癸': '丁' }; + +/** 五行相生 */ +const GENERATES = { '木': '火', '火': '土', '土': '金', '金': '水', '水': '木' }; +/** 五行相克 */ +const KILLS = { '木': '土', '土': '水', '水': '火', '火': '金', '金': '木' }; + +/** 八卦 */ +const TRIGRAMS = [ + null, + { index: 1, name: '乾', symbol: '☰', element: '金', direction: '西北', nature: '天', trait: '健', body: '首' }, + { index: 2, name: '兑', symbol: '☱', element: '金', direction: '西', nature: '泽', trait: '悦', body: '口' }, + { index: 3, name: '离', symbol: '☲', element: '火', direction: '南', nature: '火', trait: '丽', body: '目' }, + { index: 4, name: '震', symbol: '☳', element: '木', direction: '东', nature: '雷', trait: '动', body: '足' }, + { index: 5, name: '巽', symbol: '☴', element: '木', direction: '东南', nature: '风', trait: '入', body: '股' }, + { index: 6, name: '坎', symbol: '☵', element: '水', direction: '北', nature: '水', trait: '陷', body: '耳' }, + { index: 7, name: '艮', symbol: '☶', element: '土', direction: '东北', nature: '山', trait: '止', body: '手' }, + { index: 8, name: '坤', symbol: '☷', element: '土', direction: '西南', nature: '地', trait: '顺', body: '腹' } +]; + +/** 六十四卦数据 */ +const HEXAGRAMS = { + '1,1': { name: '乾为天', judgment: '大哉乾元,万物资始,乃统天。云行雨施,品物流形', image: '天行健,君子以自强不息', lines: ['潜龙勿用', '见龙在田,利见大人', '君子终日乾乾,夕惕若厉', '或跃在渊,无咎', '飞龙在天,利见大人', '亢龙有悔'] }, + '1,5': { name: '天风姤', judgment: '姤,遇也,柔遇刚也。天地相遇,品物咸章', image: '天下有风,姤。后以施命诰四方', lines: ['系于金柅,贞吉', '包有鱼,无咎', '臀无肤,其行次且', '包无鱼,起凶', '以杞包瓜,含章', '姤其角,吝'] }, + '1,7': { name: '天山遁', judgment: '遁亨,遁而亨也。刚当位而应,与时行也', image: '天下有山,遁。君子以远小人,不恶而严', lines: ['遁尾厉,勿用有攸往', '执之用黄牛之革', '系遁,有疾厉', '好遁,君子吉', '嘉遁,贞吉', '肥遁,无不利'] }, + '1,8': { name: '天地否', judgment: '否之匪人,不利君子贞。大往小来', image: '天地不交,否。君子以俭德辟难', lines: ['拔茅茹,以其汇,贞吉', '包承,小人吉,大人否', '包羞', '有命无咎,畴离祉', '休否,大人吉', '倾否,先否后喜'] }, + '5,8': { name: '风地观', judgment: '观,盥而不荐,有孚颙若。观天之神道而四时不忒', image: '风行地上,观。先王以省方观民设教', lines: ['童观,小人无咎', '窥观,利女贞', '观我生进退', '观国之光,利用宾于王', '观我生,君子无咎', '观其生,君子无咎'] }, + '7,8': { name: '山地剥', judgment: '剥,剥也,柔变刚也。不利有攸往', image: '山附于地,剥。上以厚下安宅', lines: ['剥床以足,蔑贞凶', '剥床以辨,蔑贞凶', '剥之无咎', '剥床以肤,凶', '贯鱼以宫人宠,无不利', '硕果不食,君子得舆'] }, + '3,8': { name: '火地晋', judgment: '晋,进也。明出地上,顺而丽乎大明', image: '明出地上,晋。君子以自昭明德', lines: ['晋如摧如,贞吉', '晋如愁如,贞吉', '众允,悔亡', '晋如鼫鼠,贞厉', '悔亡,失得勿恤', '晋其角,维用伐邑'] }, + '3,1': { name: '火天大有', judgment: '大有,柔得尊位,大中而上下应之', image: '火在天上,大有。君子以遏恶扬善,顺天休命', lines: ['无交害,匪咎', '大车以载,有攸往', '公用亨于天子', '匪其彭,无咎', '厥孚交如,威如', '自天佑之,吉无不利'] }, + '6,6': { name: '坎为水', judgment: '习坎,重险也。水流而不盈,行险而不失其信', image: '水洊至,习坎。君子以常德行习教事', lines: ['习坎,入于坎窞', '坎有险,求小得', '来之坎坎,险且枕', '樽酒簋贰,用缶', '坎不盈,祗既平', '系用徽纆,寘于丛棘'] }, + '6,2': { name: '水泽节', judgment: '节亨,苦节不可贞', image: '泽上有水,节。君子以制数度议德行', lines: ['不出户庭,无咎', '不出门庭,凶', '不节若,则嗟若', '安节,亨', '甘节,吉', '苦节,贞凶'] }, + '6,4': { name: '水雷屯', judgment: '屯,刚柔始交而难生。动乎险中,大亨贞', image: '云雷屯,君子以经纶', lines: ['磐桓,利居贞', '屯如邅如,乘马班如', '即鹿无虞,惟入于林中', '乘马班如,求婚媾', '屯其膏,小贞吉', '乘马班如,泣血涟如'] }, + '6,3': { name: '水火既济', judgment: '既济亨,小者亨也。利贞,初吉终乱', image: '水在火上,既济。君子以思患而豫防之', lines: ['曳其轮,濡其尾', '妇丧其茀,勿逐', '高宗伐鬼方,三年克之', '繻有衣袽,终日戒', '东邻杀牛,不如西邻', '濡其首,厉'] }, + '2,3': { name: '泽火革', judgment: '革,水火相息。天地革而四时成', image: '泽中有火,革。君子以治历明时', lines: ['巩用黄牛之革', '巳日乃革之,征吉', '征凶,贞厉', '悔亡,有孚改命', '大人虎变,未占有孚', '君子豹变,小人革面'] }, + '4,3': { name: '雷火丰', judgment: '丰,大也。明以动,故丰', image: '雷电皆至,丰。君子以折狱致刑', lines: ['遇其配主,虽旬无咎', '丰其蔀,日中见斗', '丰其沛,日中见沬', '丰其蔀,日中见斗', '来章,有庆誉', '丰其屋,蔀其家'] }, + '8,3': { name: '地火明夷', judgment: '明夷,利艰贞。明入地中,明夷', image: '明入地中,明夷。君子以莅众用晦而明', lines: ['明夷于飞,垂其翼', '明夷,夷于左股', '明夷于南狩,得其大首', '入于左腹,获明夷之心', '箕子之明夷,利贞', '不明晦,初登于天'] }, + '8,6': { name: '地水师', judgment: '师,众也。贞,丈人吉,无咎', image: '地中有水,师。君子以容民畜众', lines: ['师出以律,否臧凶', '在师中,吉无咎', '师或舆尸,凶', '师左次,无咎', '田有禽,利执言', '大君有命,开国承家'] }, + '7,7': { name: '艮为山', judgment: '艮,止也。时止则止,时行则行', image: '兼山,艮。君子以思不出其位', lines: ['艮其趾,无咎', '艮其腓,不拯其随', '艮其限,列其夤', '艮其身,无咎', '艮其辅,言有序', '敦艮,吉'] }, + '7,3': { name: '山火贲', judgment: '贲亨,柔来而文刚,故亨', image: '山下有火,贲。君子以明庶政无敢折狱', lines: ['贲其趾,舍车而徒', '贲其须', '贲如濡如,永贞吉', '贲如皤如,白马翰如', '贲于丘园,束帛戋戋', '白贲,无咎'] }, + '7,1': { name: '山天大畜', judgment: '大畜,刚健笃实辉光,日新其德', image: '天在山中,大畜。君子以多识前言往行', lines: ['有厉,利已', '舆说輹', '良马逐,利艰贞', '童牛之牿,元吉', '豮豕之牙,吉', '何天之衢,亨'] }, + '7,2': { name: '山泽损', judgment: '损,损下益上,其道上行', image: '山下有泽,损。君子以惩忿窒欲', lines: ['已事遄往,无咎', '利贞,征凶,弗损益之', '三人行则损一人', '损其疾,使遄有喜', '或益之十朋之龟', '弗损益之,无咎'] }, + '3,2': { name: '火泽睽', judgment: '睽,火动而上,泽动而下', image: '上火下泽,睽。君子以同而异', lines: ['悔亡,丧马勿逐', '遇主于巷,无咎', '见舆曳,其牛掣', '睽孤,遇元夫', '悔亡,厥宗噬肤', '睽孤,见豕负涂'] }, + '1,2': { name: '天泽履', judgment: '履,柔履刚也。说而应乎乾', image: '上天下泽,履。君子以辩上下定民志', lines: ['素履往,无咎', '履道坦坦,幽人贞吉', '眇能视,跛能履', '履虎尾,愬愬终吉', '夬履,贞厉', '视履考祥,其旋元吉'] }, + '5,2': { name: '风泽中孚', judgment: '中孚,柔在内而刚得中', image: '泽上有风,中孚。君子以议狱缓死', lines: ['虞吉,有它不燕', '鸣鹤在阴,其子和之', '得敌,或鼓或罢', '月几望,马匹亡', '有孚挛如,无咎', '翰音登于天,贞凶'] }, + '5,7': { name: '风山渐', judgment: '渐,女归吉也。进得位,往有功也', image: '山上有木,渐。君子以居贤德善俗', lines: ['鸿渐于干,小子厉', '鸿渐于磐,饮食衎衎', '鸿渐于陆,夫征不复', '鸿渐于木,或得其桷', '鸿渐于陵,妇三岁不孕', '鸿渐于逵,其羽可用为仪'] }, + '4,4': { name: '震为雷', judgment: '震亨。震来虩虩,笑言哑哑,震惊百里', image: '洊雷,震。君子以恐惧修省', lines: ['震来虩虩,后笑言哑哑', '震来厉,亿丧贝', '震苏苏,震行无眚', '震遂泥', '震往来厉,亿无丧', '震索索,视矍矍'] }, + '4,8': { name: '雷地豫', judgment: '豫,刚应而志行,顺以动', image: '雷出地奋,豫。先王以作乐崇德', lines: ['鸣豫,凶', '介于石,不终日', '盱豫悔,迟有悔', '由豫,大有得', '贞疾,恒不死', '冥豫,成有渝'] }, + '4,6': { name: '雷水解', judgment: '解,险以动,动而免乎险', image: '雷雨作,解。君子以赦过宥罪', lines: ['无咎', '田获三狐,得黄矢', '负且乘,致寇至', '解而拇,朋至斯孚', '君子维有解,吉', '公用射隼于高墉之上'] }, + '4,5': { name: '雷风恒', judgment: '恒,久也。刚上而柔下', image: '雷风,恒。君子以立不易方', lines: ['浚恒,贞凶', '悔亡', '不恒其德,或承之羞', '田无禽', '恒其德,贞妇人吉', '振恒,凶'] }, + '8,5': { name: '地风升', judgment: '柔以时升,巽而顺,刚中而应', image: '地中生木,升。君子以顺德积小以高大', lines: ['允升,大吉', '孚乃利用禴,无咎', '升虚邑', '王用亨于岐山', '贞吉,升阶', '冥升,利于不息之贞'] }, + '6,5': { name: '水风井', judgment: '井,改邑不改井,无丧无得', image: '木上有水,井。君子以劳民劝相', lines: ['井泥不食,旧井无禽', '井谷射鲋,瓮敝漏', '井渫不食,为我心恻', '井甃,无咎', '井洌,寒泉食', '井收勿幕,有孚元吉'] }, + '2,5': { name: '泽风大过', judgment: '大过,大者过也。栋桡,本末弱也', image: '泽灭木,大过。君子以独立不惧遁世无闷', lines: ['藉用白茅,无咎', '枯杨生稊,老夫得其女妻', '栋桡,凶', '栋隆,吉', '枯杨生华,老妇得士夫', '过涉灭顶,凶'] }, + '2,4': { name: '泽雷随', judgment: '随,刚来而下柔,动而说', image: '泽中有雷,随。君子以向晦入宴息', lines: ['官有渝,贞吉', '系小子,失丈夫', '系丈夫,失小子', '随有获,贞凶', '孚于嘉,吉', '拘系之,乃从维之'] }, + '5,5': { name: '巽为风', judgment: '重巽以申命,刚巽乎中正而志行', image: '随风,巽。君子以申命行事', lines: ['进退,利武人之贞', '巽在床下,用史巫纷若', '频巽,吝', '悔亡,田获三品', '贞吉,悔亡无不利', '巽在床下,丧其资斧'] }, + '5,1': { name: '风天小畜', judgment: '小畜,柔得位而上下应之', image: '风行天上,小畜。君子以懿文德', lines: ['复自道,何其咎', '牵复,吉', '舆说辐,夫妻反目', '有孚,血去惕出', '有孚挛如,富以其邻', '既雨既处,尚德载'] }, + '5,3': { name: '风火家人', judgment: '家人,女正位乎内,男正位乎外', image: '风自火出,家人。君子以言有物而行有恒', lines: ['闲有家,悔亡', '无攸遂,在中馈', '家人嗃嗃,悔厉吉', '富家,大吉', '王假有家,勿恤', '有孚威如,终吉'] }, + '5,4': { name: '风雷益', judgment: '益,损上益下,民说无疆', image: '风雷,益。君子以见善则迁有过则改', lines: ['利用为大作,元吉', '或益之十朋之龟', '益之用凶事,无咎', '中行告公从,利用为依迁国', '有孚惠心,勿问元吉', '莫益之,或击之'] }, + '1,4': { name: '天雷无妄', judgment: '无妄,刚自外来而为主于内', image: '天下雷行,无妄。先王以茂对时育万物', lines: ['无妄,往吉', '不耕获,不菑畬', '无妄之灾,或系之牛', '可贞,无咎', '无妄之疾,勿药有喜', '无妄,行有眚'] }, + '3,4': { name: '火雷噬嗑', judgment: '噬嗑亨,利用狱。刚柔分动而明', image: '雷电噬嗑,先王以明罚敕法', lines: ['屦校灭趾,无咎', '噬肤灭鼻,无咎', '噬腊肉,遇毒', '噬干胏,得金矢', '噬干肉,得黄金', '何校灭耳,凶'] }, + '7,4': { name: '山雷颐', judgment: '颐,贞吉。观颐,自求口实', image: '山下有雷,颐。君子以慎言语节饮食', lines: ['舍尔灵龟,观我朵颐', '颠颐,拂经于丘颐', '拂颐,贞凶', '颠颐,吉', '拂经,居贞吉', '由颐,厉吉,利涉大川'] }, + '7,5': { name: '山风蛊', judgment: '蛊,元亨。利涉大川,先甲三日后甲三日', image: '山下有风,蛊。君子以振民育德', lines: ['干父之蛊,有子考无咎', '干母之蛊,不可贞', '干父之蛊,小有悔', '裕父之蛊,往见吝', '干父之蛊,用誉', '不事王侯,高尚其事'] }, + '3,3': { name: '离为火', judgment: '离,丽也。日月丽乎天,百谷草木丽乎土', image: '明两作,离。大人以继明照于四方', lines: ['履错然,敬之无咎', '黄离,元吉', '日昃之离,不鼓缶而歌', '突如其来如,焚如死如弃如', '出涕沱若,戚嗟若', '王用出征,有嘉折首'] }, + '3,7': { name: '火山旅', judgment: '旅,小亨。旅贞吉', image: '山上有火,旅。君子以明慎用刑而不留狱', lines: ['旅琐琐,斯其所取灾', '旅即次,怀其资', '旅焚其次,丧其童仆', '旅于处,得其资斧', '射雉,一矢亡', '鸟焚其巢,旅人先笑后号咷'] }, + '3,5': { name: '火风鼎', judgment: '鼎,象也。以木巽火,亨饪也', image: '木上有火,鼎。君子以正位凝命', lines: ['鼎颠趾,利出否', '鼎有实,我仇有疾', '鼎耳革,其行塞', '鼎折足,覆公餗', '鼎黄耳金铉,利贞', '鼎玉铉,大吉'] }, + '3,6': { name: '火水未济', judgment: '未济亨,小狐汔济,濡其尾', image: '火在水上,未济。君子以慎辨物居方', lines: ['濡其尾,吝', '曳其轮,贞吉', '未济,征凶', '贞吉,悔亡', '贞吉,无悔', '有孚于饮酒,无咎'] }, + '7,6': { name: '山水蒙', judgment: '蒙亨。匪我求童蒙,童蒙求我', image: '山下出泉,蒙。君子以果行育德', lines: ['发蒙,利用刑人', '包蒙,吉', '勿用取女,见金夫', '困蒙,吝', '童蒙,吉', '击蒙,不利为寇'] }, + '5,6': { name: '风水涣', judgment: '涣亨。王假有庙,利涉大川', image: '风行水上,涣。先王以享于帝立庙', lines: ['用拯马壮,吉', '涣奔其机,悔亡', '涣其躬,无悔', '涣其群,元吉', '涣汗其大号', '涣其血,去逖出'] }, + '1,6': { name: '天水讼', judgment: '讼,上刚下险,险而健,讼', image: '天与水违行,讼。君子以作事谋始', lines: ['不永所事,小有言', '不克讼,归而逋', '食旧德,贞厉终吉', '不克讼,复即命渝', '讼,元吉', '或锡之鞶带,终朝三褫'] }, + '1,3': { name: '天火同人', judgment: '同人,柔得位得中而应乎乾', image: '天与火,同人。君子以类族辨物', lines: ['同人于门,无咎', '同人于宗,吝', '伏戎于莽,升其高陵', '乘其墉,弗克攻', '同人先号咷而后笑', '同人于郊,无悔'] }, + '8,8': { name: '坤为地', judgment: '至哉坤元,万物资生,乃顺承天', image: '地势坤,君子以厚德载物', lines: ['履霜,坚冰至', '直方大,不习无不利', '含章可贞,或从王事', '括囊,无咎无誉', '黄裳,元吉', '龙战于野,其血玄黄'] }, + '8,4': { name: '地雷复', judgment: '复亨。出入无疾,朋来无咎', image: '雷在地中,复。先王以至日闭关', lines: ['不远复,无祗悔', '休复,吉', '频复,厉', '中行独复', '敦复,无悔', '迷复,凶有灾眚'] }, + '8,2': { name: '地泽临', judgment: '临,刚浸而长,说而顺', image: '泽上有地,临。君子以教思无穷容保民无疆', lines: ['咸临,贞吉', '咸临,吉无不利', '甘临,无攸利', '至临,无咎', '知临,大君之宜', '敦临,吉无咎'] }, + '8,1': { name: '地天泰', judgment: '泰,小往大来,吉亨。天地交而万物通', image: '天地交,泰。后以财成天地之道', lines: ['拔茅茹,以其汇', '包荒,用冯河', '无平不陂,无往不复', '翩翩,不富以其邻', '帝乙归妹,以祉元吉', '城复于隍,勿用师'] }, + '4,1': { name: '雷天大壮', judgment: '大壮,大者壮也。刚以动,故壮', image: '雷在天上,大壮。君子以非礼勿履', lines: ['壮于趾,征凶', '贞吉', '小人用壮,君子用罔', '贞吉,悔亡', '丧羊于易,无悔', '羝羊触藩,不能退'] }, + '2,1': { name: '泽天夬', judgment: '夬,决也,刚决柔也。健而说,决而和', image: '泽上于天,夬。君子以施禄及下', lines: ['壮于前趾,往不胜为咎', '惕号,莫夜有戎', '壮于頄,有凶', '臀无肤,其行次且', '苋陆夬夬,中行无咎', '无号,终有凶'] }, + '6,1': { name: '水天需', judgment: '需,须也。险在前也,刚健而不陷', image: '云上于天,需。君子以饮食宴乐', lines: ['需于郊,利用恒', '需于沙,小有言', '需于泥,致寇至', '需于血,出自穴', '需于酒食,贞吉', '入于穴,有不速之客三人来'] }, + '6,8': { name: '水地比', judgment: '比,吉也。比,辅也,下顺从也', image: '地上有水,比。先王以建万国亲诸侯', lines: ['有孚比之,无咎', '比之自内,贞吉', '比之匪人', '外比之,贞吉', '显比,王用三驱', '比之无首,凶'] }, + '2,2': { name: '兑为泽', judgment: '兑,说也。刚中而柔外,说以利贞', image: '丽泽,兑。君子以朋友讲习', lines: ['和兑,吉', '孚兑,吉', '来兑,凶', '商兑未宁,介疾有喜', '孚于剥,有厉', '引兑'] }, + '2,6': { name: '泽水困', judgment: '困,刚掩也。险以说,困而不失其所', image: '泽无水,困。君子以致命遂志', lines: ['臀困于株木,入于幽谷', '困于酒食,朱绂方来', '困于石,据于蒺藜', '来徐徐,困于金车', '劓刖,困于赤绂', '困于葛藟,于臲兀'] }, + '2,8': { name: '泽地萃', judgment: '萃,聚也。顺以说,刚中而应', image: '泽上于地,萃。君子以除戎器戒不虞', lines: ['有孚不终,乃乱乃萃', '引吉,无咎', '萃如嗟如,无攸利', '大吉,无咎', '萃有位,无咎', '赍咨涕洟,无咎'] }, + '2,7': { name: '泽山咸', judgment: '咸,感也。柔上而刚下,二气感应以相与', image: '山上有泽,咸。君子以虚受人', lines: ['咸其拇', '咸其腓,凶', '咸其股,执其随', '贞吉,悔亡', '咸其脢,无悔', '咸其辅颊舌'] }, + '6,7': { name: '水山蹇', judgment: '蹇,难也,险在前也。见险而能止', image: '山上有水,蹇。君子以反身修德', lines: ['往蹇,来誉', '王臣蹇蹇,匪躬之故', '往蹇,来反', '往蹇,来连', '大蹇,朋来', '往蹇,来硕'] }, + '8,7': { name: '地山谦', judgment: '谦亨。天道下济而光明,地道卑而上行', image: '地中有山,谦。君子以裒多益寡称物平施', lines: ['谦谦君子,用涉大川', '鸣谦,贞吉', '劳谦君子,有终吉', '无不利,撝谦', '不富以其邻,利用侵伐', '鸣谦,利用行师'] }, + '4,7': { name: '雷山小过', judgment: '小过,小者过而亨也。过以利贞', image: '山上有雷,小过。君子以行过乎恭', lines: ['飞鸟以凶', '过其祖,遇其妣', '弗过防之,从或戕之', '无咎,弗过遇之', '密云不雨,自我西郊', '弗遇过之,飞鸟离之'] }, + '4,2': { name: '雷泽归妹', judgment: '归妹,天地之大义也。天地不交而万物不兴', image: '泽上有雷,归妹。君子以永终知敝', lines: ['归妹以娣,跛能履', '眇能视,利幽人之贞', '归妹以须,反归以娣', '归妹愆期,迟归有时', '帝乙归妹,其君之袂', '女承筐无实,士刲羊无血'] } +}; + +/** 称骨年表 */ +const YEAR_WEIGHTS = { + 0: 12, 1: 9, 2: 6, 3: 7, 4: 12, 5: 5, 6: 9, 7: 8, 8: 7, 9: 8, + 10: 15, 11: 9, 12: 16, 13: 8, 14: 8, 15: 19, 16: 12, 17: 6, 18: 8, 19: 7, + 20: 5, 21: 15, 22: 6, 23: 16, 24: 15, 25: 7, 26: 9, 27: 12, 28: 10, 29: 7, + 30: 15, 31: 6, 32: 5, 33: 14, 34: 14, 35: 9, 36: 7, 37: 7, 38: 9, 39: 12, + 40: 8, 41: 7, 42: 13, 43: 5, 44: 14, 45: 5, 46: 9, 47: 17, 48: 5, 49: 7, + 50: 12, 51: 8, 52: 8, 53: 6, 54: 19, 55: 6, 56: 8, 57: 16, 58: 10, 59: 6 +}; +/** 称骨月表 */ +const MONTH_WEIGHTS = { 1: 6, 2: 7, 3: 18, 4: 9, 5: 5, 6: 16, 7: 9, 8: 15, 9: 18, 10: 8, 11: 9, 12: 5 }; +/** 称骨日表 */ +const DAY_WEIGHTS = { + 1: 5, 2: 10, 3: 8, 4: 15, 5: 16, 6: 15, 7: 8, 8: 16, 9: 8, 10: 16, + 11: 9, 12: 17, 13: 8, 14: 17, 15: 10, 16: 8, 17: 9, 18: 18, 19: 5, 20: 15, + 21: 10, 22: 9, 23: 8, 24: 9, 25: 15, 26: 18, 27: 7, 28: 8, 29: 16, 30: 6 +}; +/** 称骨时表 */ +const HOUR_WEIGHTS = { 0: 16, 1: 6, 2: 7, 3: 10, 4: 9, 5: 16, 6: 10, 7: 8, 8: 8, 9: 9, 10: 6, 11: 6 }; +/** 称骨歌诀 */ +const BONE_INTERPRETATIONS = { + 21: { text: '短命非业谓大凶,平生灾难事重重,凶祸频临陷逆境,终世困苦事不成。', fortune: '下' }, + 22: { text: '身寒骨冷苦伶仃,此命推来行乞人,劳劳碌碌无度日,终年打拱过平生。', fortune: '下' }, + 23: { text: '此命推来骨格轻,求谋作事事难成,妻儿兄弟应难许,别处他乡作散人。', fortune: '下' }, + 24: { text: '此命推来福禄无,门庭困苦总难荣,六亲骨肉皆无靠,流浪他乡作老翁。', fortune: '下' }, + 25: { text: '此命推来祖业微,门庭营度似稀奇,六亲骨肉如冰炭,一世勤劳自把持。', fortune: '中下' }, + 26: { text: '平生衣禄苦中求,独自营谋事不休,离祖出门宜早计,晚来衣禄自无休。', fortune: '中下' }, + 27: { text: '一生作事少商量,难靠祖宗怎主张,独马单枪空做去,早年晚岁总无长。', fortune: '中下' }, + 28: { text: '一生行事似飘蓬,祖宗产业在梦中,若不过房改名姓,也当移徒二三通。', fortune: '中下' }, + 29: { text: '初年运限未曾亨,纵有功名在后成,须过四旬才可立,移居改姓始为良。', fortune: '中' }, + 30: { text: '劳劳碌碌苦中求,东奔西走何日休,若使终身勤与俭,老来稍可免忧愁。', fortune: '中' }, + 31: { text: '忙忙碌碌苦中求,何日云开见日头,难得祖基家可立,中年衣食渐无忧。', fortune: '中' }, + 32: { text: '初年运蹇事难谋,渐有财源如水流,到得中年衣食旺,那时名利一齐收。', fortune: '中上' }, + 33: { text: '早年做事事难成,百年勤劳枉费心,半世自如流水去,后来运到始得金。', fortune: '中上' }, + 34: { text: '此命福气果如何,僧道门中衣禄多,离祖出家方为妙,朝晚拜佛念弥陀。', fortune: '中上' }, + 35: { text: '生平福量不周全,祖业根基觉少传,营事生涯宜守旧,时来衣食胜从前。', fortune: '中' }, + 36: { text: '不须劳碌过平生,独自成家福不轻,早有福星常照命,任君行去百般成。', fortune: '上' }, + 37: { text: '此命般般事不成,弟兄少力自孤行,虽然祖业须微有,来得明时去不明。', fortune: '中下' }, + 38: { text: '一身骨肉最清高,早入簧门姓氏标,待到年将三十六,蓝衫脱去换红袍。', fortune: '上' }, + 39: { text: '此命终身运不通,劳劳作事尽皆空,苦心竭力成家计,到得那时在梦中。', fortune: '中下' }, + 40: { text: '平生衣禄是绵长,件件心中自主张,前面风霜多受过,后来必定享安康。', fortune: '上' }, + 41: { text: '此命推来自不同,为人能干异凡庸,中年还有逍遥福,不比前时运未通。', fortune: '上' }, + 42: { text: '得宽怀处且宽怀,何用双眉皱不开,若使中年命运济,那时名利一齐来。', fortune: '上' }, + 43: { text: '为人心性最聪明,作事轩昂近贵人,衣禄一生天注定,不须劳碌是丰亨。', fortune: '上上' }, + 44: { text: '万事由天莫苦求,须知福碌赖人修,当年财帛难如意,晚景欣然便不忧。', fortune: '中上' }, + 45: { text: '名利推求竟若何,前番辛苦后奔波,命中难养男和女,骨肉扶持也不多。', fortune: '中' }, + 46: { text: '东西南北尽皆通,出姓移居更觉隆,衣禄无穷无数定,中年晚景一般同。', fortune: '上' }, + 47: { text: '此命推求旺末年,妻荣子贵自怡然,平生原有滔滔福,可卜财源若水泉。', fortune: '上' }, + 48: { text: '初年运道未曾通,几许蹉跎命亦穷,兄弟六亲无依靠,一生事业晚来整。', fortune: '中' }, + 49: { text: '此命推来福不轻,自成自立显门庭,从来富贵人钦敬,使婢差奴过一生。', fortune: '上' }, + 50: { text: '为利为名终日劳,中年福禄也多遭,老来自有财星照,不比前番目下高。', fortune: '上' }, + 51: { text: '一世荣华事事通,不须劳碌自亨通,兄弟叔侄皆如意,家业成时福禄宏。', fortune: '上' }, + 52: { text: '一世亨通事事能,不须劳苦自然宁,宗族有光欣喜甚,家产丰盈自称心。', fortune: '上' }, + 53: { text: '此格推来福泽宏,兴家立业在其中,一生衣食安排定,却是人间一福翁。', fortune: '上' }, + 54: { text: '此命推来厚且清,诗书满腹看功成,丰衣足食自然稳,正是人间有福人。', fortune: '上上' }, + 55: { text: '走马扬鞭争利名,少年作事费筹论,一朝福禄源源至,富贵荣华显六亲。', fortune: '上' }, + 56: { text: '此格推来礼义通,一身福禄用无穷,甜酸苦辣皆尝过,滚滚财源稳且丰。', fortune: '上' }, + 57: { text: '福禄丰盈万事全,一身荣耀乐天年,名扬威震人争羡,此世逍遥宛似仙。', fortune: '上上' }, + 58: { text: '平生福禄自然来,名利双全福禄偕,雁塔题名为贵客,紫袍玉带走金阶。', fortune: '上' }, + 59: { text: '细推此格妙且清,必定财高礼义通,甲第之中应有分,扬鞭走马显威荣。', fortune: '上' }, + 60: { text: '一朝金榜快题名,显祖荣宗大器成,衣禄定然无欠缺,田园财帛更丰盈。', fortune: '上上' }, + 61: { text: '不作朝中金榜客,定为世上一财翁,聪明天赋经书熟,名显高科自是荣。', fortune: '上上' }, + 62: { text: '此命生来福不穷,读书必定显亲宗,紫衣玉带为卿相,富贵荣华孰与同。', fortune: '上上' }, + 63: { text: '命主为官福禄长,得来富贵定非常,名题雁塔传金榜,定中高科天下扬。', fortune: '上上' }, + 64: { text: '此命生成福不轻,读书必定有功名,果然富贵前生定,一世荣华事事成。', fortune: '上上' }, + 65: { text: '细推此命福不轻,安国安邦极品人,文纷雕梁徽富贵,威声照耀四方闻。', fortune: '上上' }, + 66: { text: '此命推来福且宏,荣华富贵自然通,命中注定衣禄足,一世亨通稳且丰。', fortune: '上上' }, + 67: { text: '此命生来福自宏,田园家业最高隆,平生衣禄丰盈足,一世荣华万事通。', fortune: '上上' }, + 68: { text: '富贵荣华莫强求,强求不出反成羞,有福之人还自至,无福之人反成忧。', fortune: '中' }, + 69: { text: '君是人间前禄星,一生富贵众人钦,纵然福禄由天定,安享荣华过一生。', fortune: '上上' }, + 70: { text: '此命推来福禄宏,不须劳碌过平生,妻儿和顺皆如意,家道兴隆福自成。', fortune: '上' }, + 71: { text: '此命生来大不同,公侯卿相在其中,一生自有逍遥福,富贵荣华极品隆。', fortune: '上上' }, + 72: { text: '此命生来福泽长,兴家立业有祯祥,一生自有逍遥福,富贵荣华极品良。', fortune: '上上' } +}; + +module.exports = { + LUNAR_INFO, + HEAVEN_STEMS, + EARTH_BRANCHES, + ZODIACS, + LUNAR_MONTH_NAMES, + LUNAR_DAY_NAMES, + WEEK_NAMES, + CONSTELLATIONS, + CONSTELLATION_DATES, + SOLAR_TERMS, + SOLAR_TERM_DATES, + SOLAR_FESTIVALS, + LUNAR_FESTIVALS, + BUDDHIST_FESTIVALS, + NAYIN_TABLE, + HOUR_NAMES, + HOUR_RANGES, + DUTY_NAMES, + TWELVE_STARS, + TWENTY_EIGHT_STARS, + NINE_STARS, + SIX_STARS, + PENG_ZU, + ELEMENTS, + STEM_ELEMENTS, + BRANCH_ELEMENTS, + STEM_YINYANG, + BRANCH_YINYANG, + HIDE_STEMS, + TERRAINS, + TEN_STARS, + BRANCH_COMBINE, + BRANCH_OPPOSITE, + BRANCH_HARM, + BRANCH_PUNISH, + THREE_COMBINES, + STEM_COMBINE, + STEM_OPPOSITE, + GENERATES, + KILLS, + TRIGRAMS, + HEXAGRAMS, + YEAR_WEIGHTS, + MONTH_WEIGHTS, + DAY_WEIGHTS, + HOUR_WEIGHTS, + BONE_INTERPRETATIONS +}; diff --git a/mini/utils/core/index.js b/mini/utils/core/index.js new file mode 100644 index 0000000..926c110 --- /dev/null +++ b/mini/utils/core/index.js @@ -0,0 +1,1272 @@ +const { + LUNAR_INFO, + HEAVEN_STEMS, + EARTH_BRANCHES, + ZODIACS, + LUNAR_MONTH_NAMES, + LUNAR_DAY_NAMES, + WEEK_NAMES, + CONSTELLATIONS, + CONSTELLATION_DATES, + SOLAR_TERMS, + SOLAR_TERM_DATES, + SOLAR_FESTIVALS, + LUNAR_FESTIVALS, + BUDDHIST_FESTIVALS, + NAYIN_TABLE, + HOUR_NAMES, + HOUR_RANGES, + DUTY_NAMES, + TWELVE_STARS, + TWENTY_EIGHT_STARS, + NINE_STARS, + SIX_STARS, + PENG_ZU, + STEM_ELEMENTS, + BRANCH_ELEMENTS, + STEM_YINYANG, + BRANCH_YINYANG, + HIDE_STEMS, + TERRAINS, + BRANCH_COMBINE, + BRANCH_OPPOSITE, + BRANCH_HARM, + BRANCH_PUNISH, + THREE_COMBINES, + STEM_COMBINE, + STEM_OPPOSITE, + GENERATES, + KILLS, + TRIGRAMS, + HEXAGRAMS, + YEAR_WEIGHTS, + MONTH_WEIGHTS, + DAY_WEIGHTS, + HOUR_WEIGHTS, + BONE_INTERPRETATIONS +} = require('./data.js'); + +/** 判断是否为闰年 */ +function isLeapYear(year) { + return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; +} + +/** 获取公历某月天数 */ +function getSolarMonthDays(year, month) { + const days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + if (month === 2 && isLeapYear(year)) return 29; + return days[month - 1]; +} + +/** 获取农历年天数 */ +function getLunarYearDays(year) { + let sum = 348; // 12个月,每月29天 + const info = LUNAR_INFO[year - 1900]; + for (let i = 0x8000; i > 0x8; i >>= 1) { + if (info & i) sum += 1; + } + return sum + getLeapMonthDays(year); +} + +/** 获取闰月月份(0表示无闰月) */ +function getLeapMonth(year) { + return LUNAR_INFO[year - 1900] & 0xf; +} + +/** 获取闰月天数 */ +function getLeapMonthDays(year) { + if (getLeapMonth(year) === 0) return 0; + return (LUNAR_INFO[year - 1900] & 0x10000) ? 30 : 29; +} + +/** 获取农历某月天数 */ +function getLunarMonthDays(year, month) { + return (LUNAR_INFO[year - 1900] & (0x10000 >> month)) ? 30 : 29; +} + +/** 公历转农历 */ +function solarToLunar(year, month, day) { + if (year < 1900 || year > 2100) return null; + + let offset = 0; + for (let y = 1900; y < year; y++) { + offset += getLunarYearDays(y); + } + + let leapMonth = getLeapMonth(year); + let isLeap = false; + let lunarMonth = 1; + let lunarDay = 1; + let daysInMonth = 0; + + // 计算从正月初一到目标日期的天数 + let targetOffset = offset; + for (let m = 1; m < month; m++) { + targetOffset += getSolarMonthDays(year, m); + } + targetOffset += day - 1; + + // 农历年剩余天数 + let daysInLunarYear = getLunarYearDays(year); + let remaining = targetOffset - offset; + + if (remaining < 0 || remaining >= daysInLunarYear) { + // 需要调整年份 + if (remaining < 0) { + year--; + remaining += getLunarYearDays(year); + } else { + remaining -= daysInLunarYear; + year++; + } + leapMonth = getLeapMonth(year); + } + + // 逐月计算 + for (let m = 1; m <= 12; m++) { + daysInMonth = getLunarMonthDays(year, m); + if (remaining < daysInMonth) { + lunarMonth = m; + lunarDay = remaining + 1; + break; + } + remaining -= daysInMonth; + + // 处理闰月 + if (m === leapMonth && !isLeap) { + m--; + isLeap = true; + daysInMonth = getLeapMonthDays(year); + if (remaining < daysInMonth) { + lunarMonth = m + 1; + lunarDay = remaining + 1; + isLeap = true; + break; + } + remaining -= daysInMonth; + } + } + + return { + year, + month: lunarMonth, + day: lunarDay, + isLeap: isLeap && lunarMonth === leapMonth + }; +} + +/** 农历转公历 */ +function lunarToSolar(year, month, day, isLeap = false) { + if (year < 1900 || year > 2100) return null; + + let offset = 0; + for (let y = 1900; y < year; y++) { + offset += getLunarYearDays(y); + } + + const leapMonth = getLeapMonth(year); + let days = 0; + + for (let m = 1; m < month; m++) { + days += getLunarMonthDays(year, m); + if (m === leapMonth && !isLeap) { + days += getLeapMonthDays(year); + } + } + + if (isLeap && month === leapMonth) { + days += getLunarMonthDays(year, month); + } + + days += day - 1; + offset += days; + + // 从1900年1月31日(农历正月初一)开始计算 + let solarYear = 1900; + let solarMonth = 1; + let solarDay = 31; + + let remaining = offset; + while (remaining > 0) { + const daysInMonth = getSolarMonthDays(solarYear, solarMonth); + if (remaining < daysInMonth) { + solarDay = remaining + 1; + break; + } + remaining -= daysInMonth; + solarMonth++; + if (solarMonth > 12) { + solarMonth = 1; + solarYear++; + } + } + + return { year: solarYear, month: solarMonth, day: solarDay }; +} + +/** 获取日期的干支 */ +function getGanzhi(year, month, day) { + // 以1900年1月1日为甲子日(实际上1900-01-01是甲戌日,这里用近似算法) + // 更准确的算法:以某个已知日期为基准 + const baseDate = new Date(1900, 0, 31); // 1900-01-31 是甲子日 + const targetDate = new Date(year, month - 1, day); + const diffDays = Math.floor((targetDate - baseDate) / (24 * 60 * 60 * 1000)); + + const stemIndex = (diffDays % 10 + 10) % 10; + const branchIndex = (diffDays % 12 + 12) % 12; + + return { + stem: HEAVEN_STEMS[stemIndex], + branch: EARTH_BRANCHES[branchIndex], + ganzhi: HEAVEN_STEMS[stemIndex] + EARTH_BRANCHES[branchIndex] + }; +} + +/** 获取年的干支 */ +function getYearGanzhi(year) { + const stemIndex = (year - 4) % 10; + const branchIndex = (year - 4) % 12; + return { + stem: HEAVEN_STEMS[stemIndex], + branch: EARTH_BRANCHES[branchIndex], + ganzhi: HEAVEN_STEMS[stemIndex] + EARTH_BRANCHES[branchIndex] + }; +} + +/** 获取月的干支(以节气划分) */ +function getMonthGanzhi(year, month, day) { + // 简化算法:以立春为正月起点 + const yearGz = getYearGanzhi(year); + const yearStemIndex = HEAVEN_STEMS.indexOf(yearGz.stem); + + // 月干 = (年干 * 2 + 月数) % 10 + const monthStemIndex = (yearStemIndex * 2 + month) % 10; + // 月支固定:正月寅,二月卯... + const monthBranchIndex = (month + 1) % 12; + + return { + stem: HEAVEN_STEMS[monthStemIndex], + branch: EARTH_BRANCHES[monthBranchIndex], + ganzhi: HEAVEN_STEMS[monthStemIndex] + EARTH_BRANCHES[monthBranchIndex] + }; +} + +/** 获取时辰的干支 */ +function getHourGanzhi(dayGanzhi, hour) { + const dayStemIndex = HEAVEN_STEMS.indexOf(dayGanzhi.stem); + const hourBranchIndex = Math.floor(((hour + 1) % 24) / 2); + + // 时干 = (日干 * 2 + 时支) % 10 + const hourStemIndex = (dayStemIndex * 2 + hourBranchIndex) % 10; + + return { + stem: HEAVEN_STEMS[hourStemIndex], + branch: EARTH_BRANCHES[hourBranchIndex], + ganzhi: HEAVEN_STEMS[hourStemIndex] + EARTH_BRANCHES[hourBranchIndex] + }; +} + +/** 获取生肖 */ +function getZodiac(year) { + return ZODIACS[(year - 4) % 12]; +} + +/** 获取星座 */ +function getConstellation(month, day) { + const dates = CONSTELLATION_DATES; + const index = month - 1; + if (day < dates[index]) { + return CONSTELLATIONS[index]; + } else { + return CONSTELLATIONS[index + 1]; + } +} + +/** 获取节气 */ +function getSolarTerm(year, month, day) { + const termDates = SOLAR_TERM_DATES[month - 1]; + if (day === termDates[0]) { + return SOLAR_TERMS[(month - 1) * 2]; + } else if (day === termDates[1]) { + return SOLAR_TERMS[(month - 1) * 2 + 1]; + } + return null; +} + +/** 获取纳音 */ +function getNayin(ganzhi) { + const stemIndex = HEAVEN_STEMS.indexOf(ganzhi[0]); + const branchIndex = EARTH_BRANCHES.indexOf(ganzhi[1]); + const index = Math.floor(stemIndex / 2) * 6 + Math.floor(branchIndex / 2); + return NAYIN_TABLE[index % 30]; +} + +/** 获取建除十二神 */ +function getDuty(monthGanzhi, dayGanzhi) { + const monthBranchIndex = EARTH_BRANCHES.indexOf(monthGanzhi.branch); + const dayBranchIndex = EARTH_BRANCHES.indexOf(dayGanzhi.branch); + const dutyIndex = (dayBranchIndex - monthBranchIndex + 12) % 12; + return DUTY_NAMES[dutyIndex]; +} + +/** 获取黄道黑道 */ +function getTwelveStar(dayGanzhi) { + const branchIndex = EARTH_BRANCHES.indexOf(dayGanzhi.branch); + const starIndex = branchIndex % 12; + const star = TWELVE_STARS[starIndex]; + const ecliptic = ['青龙', '明堂', '金匮', '天德', '玉堂', '司命'].includes(star) ? '黄道' : '黑道'; + return { name: star, ecliptic, luck: ecliptic === '黄道' ? 'good' : 'bad' }; +} + +/** 获取二十八宿 */ +function getTwentyEightStar(year, month, day) { + // 简化算法:以某已知日期为基准 + const baseDate = new Date(1900, 0, 1); + const targetDate = new Date(year, month - 1, day); + const diffDays = Math.floor((targetDate - baseDate) / (24 * 60 * 60 * 1000)); + const index = diffDays % 28; + const star = TWENTY_EIGHT_STARS[(index + 28) % 28]; + return { + name: star, + luck: Math.random() > 0.5 ? 'good' : 'bad', // 简化 + animal: star.slice(-1) + }; +} + +/** 获取九星 */ +function getNineStar(year, month, day) { + const baseDate = new Date(1900, 0, 1); + const targetDate = new Date(year, month - 1, day); + const diffDays = Math.floor((targetDate - baseDate) / (24 * 60 * 60 * 1000)); + const index = diffDays % 9; + return { + name: NINE_STARS[(index + 9) % 9], + color: '', + element: '' + }; +} + +/** 获取六曜 */ +function getSixStar(lunarMonth, lunarDay) { + const index = (lunarMonth + lunarDay) % 6; + return SIX_STARS[index]; +} + +/** 获取小六壬 */ +function getMinorRen(lunarMonth, lunarDay, hour) { + const index = (lunarMonth + lunarDay + Math.floor(hour / 2)) % 6; + const names = ['大安', '留连', '速喜', '赤口', '小吉', '空亡']; + const lucks = ['good', 'bad', 'good', 'bad', 'good', 'bad']; + const elements = ['木', '土', '火', '金', '水', '土']; + return { + name: names[index], + luck: lucks[index], + element: elements[index] + }; +} + +/** 获取月相 */ +function getMoonPhase(lunarDay) { + if (lunarDay === 1) return '朔月'; + if (lunarDay <= 7) return '上弦月'; + if (lunarDay === 15) return '满月'; + if (lunarDay <= 22) return '下弦月'; + return '残月'; +} + +/** 获取胎神方位 */ +function getFetus(dayGanzhi) { + const directions = ['东', '南', '西', '北', '中']; + const sides = ['房内', '房外']; + const index = HEAVEN_STEMS.indexOf(dayGanzhi.stem) % 5; + return { + direction: directions[index], + side: sides[index % 2], + position: `${directions[index]}方${sides[index % 2]}` + }; +} + +/** 获取宜忌 */ +function getRecommendsAvoids(duty) { + const recommendsMap = { + '建': ['出行', '上任', '会友', '上书', '见工'], + '除': ['除服', '疗病', '出行', '拆卸', '入宅'], + '满': ['嫁娶', '祈福', '移徙', '开市', '交易'], + '平': ['嫁娶', '修造', '动土', '竖柱', '上梁'], + '定': ['嫁娶', '祈福', '求嗣', '开光', '出行'], + '执': ['祭祀', '祈福', '求嗣', '开光', '嫁娶'], + '破': ['破屋', '坏垣', '求医', '治病'], + '危': ['祭祀', '祈福', '求嗣', '斋醮', '嫁娶'], + '成': ['嫁娶', '开市', '交易', '立券', '纳财'], + '收': ['纳财', '捕捉', '取渔', '纳畜'], + '开': ['嫁娶', '祈福', '求嗣', '开光', '出行'], + '闭': ['祭祀', '祈福', '求嗣', '斋醮', '安葬'] + }; + const avoidsMap = { + '建': ['动土', '开仓', '掘井', '开渠'], + '除': ['嫁娶', '开市', '交易', '立券'], + '满': ['安葬', '行丧', '伐木', '作梁'], + '平': ['开市', '交易', '立券', '纳财'], + '定': ['诉讼', '出行', '交涉'], + '执': ['开市', '交易', '立券', '纳财'], + '破': ['嫁娶', '开市', '交易', '立券', '纳财'], + '危': ['登高', '行船', '出行'], + '成': ['诉讼', '争讼'], + '收': ['嫁娶', '开市', '交易', '立券'], + '开': ['安葬', '行丧'], + '闭': ['嫁娶', '开市', '交易', '立券', '纳财'] + }; + return { + recommends: recommendsMap[duty] || [], + avoids: avoidsMap[duty] || [] + }; +} + +/** 获取吉神凶神 */ +function getGods(dayGanzhi) { + // 简化实现 + const goodGods = ['天德', '月德', '天恩', '母仓', '时阳', '生气', '益后', '青龙']; + const badGods = ['劫煞', '灾煞', '月煞', '月刑', '月害', '月厌', '大时', '大败']; + return { goodGods, badGods }; +} + +/** 获取冲煞 */ +function getClashHarmCombine(dayBranch) { + const clash = BRANCH_OPPOSITE[dayBranch]; + const harm = BRANCH_HARM[dayBranch]; + const combine = BRANCH_COMBINE[dayBranch]; + const zodiacMap = {}; + EARTH_BRANCHES.forEach((b, i) => { zodiacMap[b] = ZODIACS[i]; }); + return { + clash: clash ? `${zodiacMap[clash]}(${clash})` : '', + harm: harm ? `${zodiacMap[harm]}(${harm})` : '', + combine: combine ? `${zodiacMap[combine]}(${combine})` : '', + evilDirection: ['北', '南', '东', '西'][EARTH_BRANCHES.indexOf(dayBranch) % 4] + }; +} + +/** 获取彭祖百忌 */ +function getPengZu(dayGanzhi) { + const stemIndex = HEAVEN_STEMS.indexOf(dayGanzhi.stem); + const branchIndex = EARTH_BRANCHES.indexOf(dayGanzhi.branch); + return { + text: PENG_ZU.stems[stemIndex] + ' ' + PENG_ZU.branches[branchIndex], + stem: PENG_ZU.stems[stemIndex], + branch: PENG_ZU.branches[branchIndex] + }; +} + +/** 获取时辰黄历 */ +function getHourlyAlmanac(dayGanzhi) { + const result = []; + for (let i = 0; i < 12; i++) { + const hourGz = getHourGanzhi(dayGanzhi, i * 2); + result.push({ + branch: EARTH_BRANCHES[i], + name: HOUR_NAMES[i], + range: HOUR_RANGES[i], + ganzhi: hourGz.ganzhi, + recommends: [], + avoids: [], + twelveStar: TWELVE_STARS[i % 12], + nineStar: NINE_STARS[i % 9] + }); + } + return result; +} + +/** 获取日信息 */ +function getDayInfo(year, month, day) { + const date = new Date(year, month - 1, day); + const weekDay = date.getDay(); + const lunar = solarToLunar(year, month, day); + const dayGz = getGanzhi(year, month, day); + const yearGz = getYearGanzhi(year); + const monthGz = getMonthGanzhi(year, month, day); + const solarTerm = getSolarTerm(year, month, day); + const now = new Date(); + const isToday = year === now.getFullYear() && month === now.getMonth() + 1 && day === now.getDate(); + + return { + solarDate: `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`, + solarDay: day, + solarMonth: month, + solarYear: year, + weekDay: WEEK_NAMES[weekDay], + weekDayIndex: weekDay, + constellation: getConstellation(month, day), + solarTerm, + solarTermTime: null, + isTermDay: !!solarTerm, + currentSolarTerm: null, + season: ['冬季', '冬季', '春季', '春季', '春季', '夏季', '夏季', '夏季', '秋季', '秋季', '秋季', '冬季'][month - 1], + termDayIndex: null, + nextSolarTerm: null, + daysToNextTerm: null, + julianDay: null, + buddhistYear: year + 543, + hijriDate: null, + buddhistFestival: lunar ? BUDDHIST_FESTIVALS[`${lunar.month}-${lunar.day}`] || null : null, + phenology: null, + dogDay: null, + nineDay: null, + lunarYear: lunar ? lunar.year : 0, + lunarMonth: lunar ? lunar.month : 0, + lunarMonthName: lunar ? LUNAR_MONTH_NAMES[lunar.month - 1] : '', + lunarDay: lunar ? lunar.day : 0, + lunarDayName: lunar ? LUNAR_DAY_NAMES[lunar.day - 1] : '', + isLeapMonth: lunar ? lunar.isLeap : false, + lunarYearGanzhi: yearGz.ganzhi, + lunarMonthGanzhi: monthGz.ganzhi, + lunarDayGanzhi: dayGz.ganzhi, + zodiac: getZodiac(year), + lunarFestival: lunar ? LUNAR_FESTIVALS[`${lunar.month}-${lunar.day}`] || null : null, + solarFestival: SOLAR_FESTIVALS[`${month}-${day}`] || null, + legalHoliday: null, + moonPhase: lunar ? getMoonPhase(lunar.day) : null, + isToday, + isWeekend: weekDay === 0 || weekDay === 6, + dayOfWeek: weekDay + }; +} + +/** 获取今日信息 */ +function getTodayInfo() { + const now = new Date(); + return getDayInfo(now.getFullYear(), now.getMonth() + 1, now.getDate()); +} + +/** 获取月历 */ +function getMonthCalendar(year, month, weekStart = 0) { + const firstDay = new Date(year, month - 1, 1); + const lastDay = new Date(year, month, 0); + const daysInMonth = lastDay.getDate(); + const firstWeekDay = firstDay.getDay(); + + const weeks = []; + let currentWeek = []; + + // 填充上月天数 + const prevMonthDays = weekStart === 0 ? firstWeekDay : (firstWeekDay + 6) % 7; + if (prevMonthDays > 0) { + const prevMonth = month === 1 ? 12 : month - 1; + const prevYear = month === 1 ? year - 1 : year; + const prevMonthLastDay = getSolarMonthDays(prevYear, prevMonth); + for (let i = prevMonthDays - 1; i >= 0; i--) { + const d = prevMonthLastDay - i; + currentWeek.push(getDayInfo(prevYear, prevMonth, d)); + } + } + + // 填充当月天数 + for (let d = 1; d <= daysInMonth; d++) { + currentWeek.push(getDayInfo(year, month, d)); + if (currentWeek.length === 7) { + weeks.push(currentWeek); + currentWeek = []; + } + } + + // 填充下月天数 + if (currentWeek.length > 0) { + const nextMonth = month === 12 ? 1 : month + 1; + const nextYear = month === 12 ? year + 1 : year; + let d = 1; + while (currentWeek.length < 7) { + currentWeek.push(getDayInfo(nextYear, nextMonth, d)); + d++; + } + weeks.push(currentWeek); + } + + return weeks; +} + +/** 获取黄历信息 */ +function getAlmanacInfo(year, month, day) { + const dayInfo = getDayInfo(year, month, day); + const dayGz = { stem: dayInfo.lunarDayGanzhi[0], branch: dayInfo.lunarDayGanzhi[1], ganzhi: dayInfo.lunarDayGanzhi }; + const monthGz = { stem: dayInfo.lunarMonthGanzhi[0], branch: dayInfo.lunarMonthGanzhi[1], ganzhi: dayInfo.lunarMonthGanzhi }; + + const duty = getDuty(monthGz, dayGz); + const twelveStar = getTwelveStar(dayGz); + const twentyEightStar = getTwentyEightStar(year, month, day); + const nineStar = getNineStar(year, month, day); + const sixStar = getSixStar(dayInfo.lunarMonth, dayInfo.lunarDay); + const minorRen = getMinorRen(dayInfo.lunarMonth, dayInfo.lunarDay, 12); + const phase = dayInfo.moonPhase || ''; + const fetus = getFetus(dayGz); + const { recommends, avoids } = getRecommendsAvoids(duty); + const { goodGods, badGods } = getGods(dayGz); + const pengZu = getPengZu(dayGz); + const clashHarm = getClashHarmCombine(dayGz.branch); + const nayin = getNayin(dayGz.ganzhi); + const hourDetails = getHourlyAlmanac(dayGz); + + const luckyDuties = ['除', '执', '危', '成', '开']; + const unluckyDuties = ['建', '满', '平', '破', '收', '闭']; + let dutyLuck = 'neutral'; + if (luckyDuties.includes(duty)) dutyLuck = 'good'; + else if (unluckyDuties.includes(duty)) dutyLuck = 'bad'; + + return { + duty, + dutyLuck, + twelveStar, + twentyEightStar, + nineStar, + sixStar, + minorRen, + phase, + fetus, + recommends, + avoids, + goodGods, + badGods, + dayStem: dayGz.stem, + dayBranch: dayGz.branch, + dayGanzhi: dayGz.ganzhi, + pengZu: pengZu.text, + pengZuStem: pengZu.stem, + pengZuBranch: pengZu.branch, + clash: clashHarm.clash, + harm: clashHarm.harm, + combine: clashHarm.combine, + evilDirection: clashHarm.evilDirection, + nayin, + hourDetails + }; +} + +/** 八字排盘 */ +function birthInfoToBazi(params) { + const { year, month, day, hour, minute, gender, ziSect = 'lateZiNextDay' } = params; + + // 处理晚子时 + let y = year, m = month, d = day; + if (ziSect === 'lateZiNextDay' && hour >= 23) { + const nextDay = new Date(year, month - 1, day + 1); + y = nextDay.getFullYear(); + m = nextDay.getMonth() + 1; + d = nextDay.getDate(); + } + + const dayInfo = getDayInfo(y, m, d); + const dayGz = { stem: dayInfo.lunarDayGanzhi[0], branch: dayInfo.lunarDayGanzhi[1], ganzhi: dayInfo.lunarDayGanzhi }; + const yearGz = { stem: dayInfo.lunarYearGanzhi[0], branch: dayInfo.lunarYearGanzhi[1], ganzhi: dayInfo.lunarYearGanzhi }; + const monthGz = { stem: dayInfo.lunarMonthGanzhi[0], branch: dayInfo.lunarMonthGanzhi[1], ganzhi: dayInfo.lunarMonthGanzhi }; + const hourGz = getHourGanzhi(dayGz, hour); + + function extractPillarInfo(gz) { + const stem = gz.stem; + const branch = gz.branch; + const hideStems = (HIDE_STEMS[branch] || []).map(h => ({ + stem: h.stem, + type: h.type, + tenStar: null + })); + + const terrainIndex = (HEAVEN_STEMS.indexOf(stem) * 12 + EARTH_BRANCHES.indexOf(branch)) % 12; + const terrainName = TERRAINS[terrainIndex]; + const goodTerrain = ['长生', '冠带', '临官', '帝旺', '胎', '养']; + const badTerrain = ['死', '墓', '绝']; + let terrainFortune = 'neutral'; + if (goodTerrain.includes(terrainName)) terrainFortune = 'good'; + else if (badTerrain.includes(terrainName)) terrainFortune = 'bad'; + + return { + ganzhi: gz.ganzhi, + heavenStem: stem, + earthBranch: branch, + elementStem: STEM_ELEMENTS[stem], + elementBranch: BRANCH_ELEMENTS[branch], + yinYangStem: STEM_YINYANG[stem], + yinYangBranch: BRANCH_YINYANG[branch], + hideStems, + nayin: getNayin(gz.ganzhi), + terrain: { name: terrainName, fortune: terrainFortune }, + tenStar: null + }; + } + + const yearPillar = extractPillarInfo(yearGz); + const monthPillar = extractPillarInfo(monthGz); + const dayPillar = extractPillarInfo(dayGz); + const hourPillar = extractPillarInfo(hourGz); + + // 计算十神(相对于日主) + const dayStemIndex = HEAVEN_STEMS.indexOf(dayGz.stem); + function getTenStar(stem) { + const stemIndex = HEAVEN_STEMS.indexOf(stem); + const diff = (stemIndex - dayStemIndex + 10) % 10; + const stars = ['比肩', '劫财', '食神', '伤官', '偏财', '正财', '七杀', '正官', '偏印', '正印']; + return stars[diff]; + } + + yearPillar.tenStar = getTenStar(yearGz.stem); + monthPillar.tenStar = getTenStar(monthGz.stem); + hourPillar.tenStar = getTenStar(hourGz.stem); + + // 藏干十神 + [yearPillar, monthPillar, dayPillar, hourPillar].forEach(p => { + p.hideStems.forEach(h => { + h.tenStar = getTenStar(h.stem); + }); + }); + + // 胎元、胎息、命宫、身宫(简化) + const fetalOrigin = monthGz.ganzhi; + const fetalBreath = dayGz.ganzhi; + const ownSign = '命宫'; + const bodySign = '身宫'; + + // 空亡 + const emptyBranches = []; + const dayBranchIndex = EARTH_BRANCHES.indexOf(dayGz.branch); + const emptyIndex1 = (dayBranchIndex + 10) % 12; + const emptyIndex2 = (dayBranchIndex + 11) % 12; + emptyBranches.push(EARTH_BRANCHES[emptyIndex1], EARTH_BRANCHES[emptyIndex2]); + + // 起运(简化) + const childLimit = { + startTime: `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')} ${String(hour).padStart(2, '0')}:${String(minute).padStart(2, '0')}:00`, + endTime: '', + yearCount: 8, + monthCount: 0, + dayCount: 0, + hourCount: 0, + minuteCount: 0, + forward: gender === 'male', + startAge: 8, + endAge: 88 + }; + + // 大运(简化,10条) + const decadeFortunes = []; + for (let i = 0; i < 10; i++) { + const stemIndex = (HEAVEN_STEMS.indexOf(monthGz.stem) + i + 1) % 10; + const branchIndex = (EARTH_BRANCHES.indexOf(monthGz.branch) + i + 1) % 12; + const ganzhi = HEAVEN_STEMS[stemIndex] + EARTH_BRANCHES[branchIndex]; + decadeFortunes.push({ + index: i, + ganzhi, + startAge: 8 + i * 10, + endAge: 17 + i * 10, + startYear: year + 8 + i * 10, + endYear: year + 17 + i * 10, + heavenStem: HEAVEN_STEMS[stemIndex], + earthBranch: EARTH_BRANCHES[branchIndex], + nayin: getNayin(ganzhi) + }); + } + + // 流年(简化,10条) + const annualFortunes = []; + for (let i = 0; i < 10; i++) { + const y = year + i; + const yGz = getYearGanzhi(y); + annualFortunes.push({ + age: i + 1, + year: y, + ganzhi: yGz.ganzhi, + nayin: getNayin(yGz.ganzhi) + }); + } + + return { + eightChar: { + birthDate: `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`, + birthTime: `${String(hour).padStart(2, '0')}:${String(minute).padStart(2, '0')}`, + gender, + yearPillar, + monthPillar, + dayPillar, + hourPillar, + dayMaster: dayGz.stem + STEM_ELEMENTS[dayGz.stem], + dayMasterStem: dayGz.stem, + dayMasterElement: STEM_ELEMENTS[dayGz.stem], + fetalOrigin, + fetalBreath, + ownSign, + bodySign, + emptyBranches, + emptyTen: '' + }, + childLimit, + decadeFortunes, + annualFortunes + }; +} + +/** 获取地支关系 */ +function getBranchRelationship(branchA, branchB) { + const combine = BRANCH_COMBINE[branchA] === branchB; + const opposite = BRANCH_OPPOSITE[branchA] === branchB; + const harm = BRANCH_HARM[branchA] === branchB; + + let threeCombine = false; + let formation = null; + for (const [name, branches] of Object.entries(THREE_COMBINES)) { + if (branches.includes(branchA) && branches.includes(branchB)) { + threeCombine = true; + formation = name; + break; + } + } + + let punish = false; + for (const [a, b] of BRANCH_PUNISH) { + if ((a === branchA && b === branchB) || (a === branchB && b === branchA)) { + punish = true; + break; + } + } + + return { combine, threeCombine, opposite, harm, punish, formation }; +} + +/** 获取十神关系 */ +function getTenStarRelationship(subjectStem, objectStem) { + const sIndex = HEAVEN_STEMS.indexOf(subjectStem); + const oIndex = HEAVEN_STEMS.indexOf(objectStem); + const diff = (oIndex - sIndex + 10) % 10; + const stars = ['比肩', '劫财', '食神', '伤官', '偏财', '正财', '七杀', '正官', '偏印', '正印']; + return stars[diff]; +} + +/** 检查天干合 */ +function checkStemCombine(stemA, stemB) { + return STEM_COMBINE[stemA] === stemB; +} + +/** 检查天干冲 */ +function checkStemOpposite(stemA, stemB) { + return STEM_OPPOSITE[stemA] === stemB; +} + +/** 计算每日运势 */ +function calculateDailyFortune(userBazi, date) { + const year = date.getFullYear(); + const month = date.getMonth() + 1; + const day = date.getDate(); + const dayInfo = getDayInfo(year, month, day); + const dayGz = { stem: dayInfo.lunarDayGanzhi[0], branch: dayInfo.lunarDayGanzhi[1], ganzhi: dayInfo.lunarDayGanzhi }; + + const pillars = ['year', 'month', 'day', 'hour']; + const pillarLabels = { year: '年柱', month: '月柱', day: '日柱', hour: '时柱' }; + const relationships = []; + + for (const key of pillars) { + const userPillar = userBazi[`${key}Pillar`]; + const dayP = key === 'year' ? { stem: dayInfo.lunarYearGanzhi[0], branch: dayInfo.lunarYearGanzhi[1] } : + key === 'month' ? { stem: dayInfo.lunarMonthGanzhi[0], branch: dayInfo.lunarMonthGanzhi[1] } : + key === 'day' ? dayGz : { stem: dayGz.stem, branch: dayGz.branch }; + + const stemTenStar = getTenStarRelationship(userPillar.heavenStem, dayP.stem); + const stemCombine = checkStemCombine(userPillar.heavenStem, dayP.stem); + const stemOpposite = checkStemOpposite(userPillar.heavenStem, dayP.stem); + const branchRel = getBranchRelationship(userPillar.earthBranch, dayP.branch); + + let score = 0; + if (stemTenStar) { + const good = ['正印', '偏印', '食神', '正财', '偏财', '正官']; + const bad = ['七杀', '劫财', '伤官']; + if (good.includes(stemTenStar)) score += 4; + else if (bad.includes(stemTenStar)) score -= 3; + else score += 1; + } + if (stemCombine) score += 5; + if (stemOpposite) score -= 6; + if (branchRel.combine) score += 4; + if (branchRel.threeCombine) score += 3; + if (branchRel.opposite) score -= 5; + if (branchRel.harm) score -= 4; + if (branchRel.punish) score -= 3; + score = Math.max(-10, Math.min(10, score)); + + relationships.push({ + pillar: key, + pillarLabel: pillarLabels[key], + userGanzhi: userPillar.ganzhi, + dayGanzhi: dayP.stem + dayP.branch, + stemTenStar, + stemCombine, + stemOpposite, + branchCombine: branchRel.combine, + branchThreeCombine: branchRel.threeCombine, + branchOpposite: branchRel.opposite, + branchHarm: branchRel.harm, + branchPunish: branchRel.punish, + branchFormation: branchRel.formation, + score + }); + } + + const weightedScore = relationships[0].score * 0.20 + relationships[1].score * 0.25 + + relationships[2].score * 0.40 + relationships[3].score * 0.15; + const overallScore = Math.max(-100, Math.min(100, Math.round(weightedScore * 10))); + const scoreLevel = overallScore >= 50 ? 'great' : overallScore >= 20 ? 'good' : + overallScore >= -20 ? 'fair' : overallScore >= -50 ? 'poor' : 'bad'; + + const luckyAspects = []; + const unluckyAspects = []; + const suggestions = []; + const affectedAreas = ['综合运势']; + + // 简化生成 + if (overallScore >= 20) { + luckyAspects.push('今天运势不错,适合推进重要事项'); + suggestions.push('✅ 适合推进重要事项,果断决策会有好结果'); + } else if (overallScore <= -20) { + unluckyAspects.push('今天运势低迷,宜保守行事'); + suggestions.push('🛡️ 宜静不宜动重要决策能缓则缓'); + } else { + luckyAspects.push('今天整体运势平稳'); + suggestions.push('💡 运势平稳该做什么就做什么'); + } + + const de = userBazi.dayMasterElement; + const colorMap = { '木': ['绿色', '青色'], '火': ['红色', '紫色'], '土': ['黄色', '棕色'], '金': ['白色', '银色'], '水': ['蓝色', '黑色'] }; + const dirMap = { '木': '东方', '火': '南方', '土': '中央', '金': '西方', '水': '北方' }; + const colors = colorMap[de] || ['红色', '白色']; + const direction = dirMap[de] || '东方'; + + return { + date: `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`, + lunarDate: `${dayInfo.lunarYear}年${dayInfo.lunarMonthName}${dayInfo.lunarDayName}`, + dayGanzhi: dayGz.ganzhi, + overallScore, + scoreLevel, + pillarRelationships: relationships, + luckyAspects, + unluckyAspects, + suggestions, + affectedAreas, + luckyMeta: { + colors, + numbers: [3, 7], + direction, + element: de, + activity: '阅读' + }, + categoryScores: { + love: overallScore, + career: overallScore, + wealth: overallScore, + health: overallScore + } + }; +} + +/** 五行分析 */ +function analyzeElementBalance(bazi) { + const pillars = [bazi.yearPillar, bazi.monthPillar, bazi.dayPillar, bazi.hourPillar]; + const counts = { '木': 0, '火': 0, '土': 0, '金': 0, '水': 0 }; + + for (const pillar of pillars) { + if (pillar.elementStem) counts[pillar.elementStem] += 1; + if (pillar.elementBranch) counts[pillar.elementBranch] += 1; + for (const hs of pillar.hideStems) { + const el = STEM_ELEMENTS[hs.stem]; + if (el) counts[el] += 0.5; + } + } + + const total = Object.values(counts).reduce((a, b) => a + b, 0); + let dominant = '木', weakest = '木'; + let maxCount = 0, minCount = Infinity; + for (const [el, count] of Object.entries(counts)) { + if (count > maxCount) { maxCount = count; dominant = el; } + if (count < minCount) { minCount = count; weakest = el; } + } + + const ideal = total / 5; + let isBalanced = true; + for (const count of Object.values(counts)) { + if (Math.abs(count - ideal) > ideal * 0.4) { + isBalanced = false; + break; + } + } + + return { + wood: counts['木'], + fire: counts['火'], + earth: counts['土'], + metal: counts['金'], + water: counts['水'], + total, + dominant, + weakest, + isBalanced + }; +} + +/** 梅花易数 */ +function calculatePlumBlossom(year, month, day, hour = 12) { + const yearNum = year; + const monthNum = month; + const dayNum = day; + const hourIndex = Math.floor(((hour + 1) % 24) / 2); + + const upperIdx = ((yearNum + monthNum + dayNum) % 8) + 1; + const lowerIdx = ((monthNum + dayNum + hourIndex + 1) % 8) + 1; + const changingLine = ((yearNum + monthNum + dayNum + hourIndex + 1) % 6) + 1; + + const upperTrigram = TRIGRAMS[upperIdx]; + const lowerTrigram = TRIGRAMS[lowerIdx]; + + const originalKey = `${upperIdx},${lowerIdx}`; + const originalHexagram = HEXAGRAMS[originalKey] || { + number: (upperIdx - 1) * 8 + lowerIdx, + name: upperTrigram.name + lowerTrigram.name, + upperTrigram, + lowerTrigram, + changingLine: 0, + interpretation: '此卦象需结合具体事理参详', + judgment: '', + image: '', + lines: ['', '', '', '', '', ''] + }; + + // 变卦 + let transUpperIdx = upperIdx; + let transLowerIdx = lowerIdx; + if (changingLine >= 4) { + transUpperIdx = flipTrigramLine(upperIdx, changingLine - 3); + } else { + transLowerIdx = flipTrigramLine(lowerIdx, changingLine); + } + const transKey = `${transUpperIdx},${transLowerIdx}`; + const transformedHexagram = HEXAGRAMS[transKey] || null; + + // 互卦(简化) + const mutualKey = `${lowerIdx},${upperIdx}`; + const mutualHexagram = HEXAGRAMS[mutualKey] || null; + + const constitution = lowerTrigram.element; + const function_ = upperTrigram.element; + const relationship = getElementRelationship(constitution, function_); + + return { + originalHexagram: { ...originalHexagram, changingLine }, + transformedHexagram, + mutualHexagram, + upperTrigram, + lowerTrigram, + changingLine, + constitution, + function: function_, + relationship + }; +} + +function flipTrigramLine(trigramIdx, line) { + const encoding = [0, 7, 6, 5, 4, 3, 2, 1, 0]; + let bits = encoding[trigramIdx] || 0; + bits ^= (1 << (line - 1)); + const decoding = [8, 7, 6, 2, 5, 3, 4, 1]; + return decoding[bits] || trigramIdx; +} + +function getElementRelationship(body, func) { + if (body === func) return '比和(体用相同,诸事顺利)'; + if (GENERATES[body] === func) return '体生用(泄气,宜守不宜攻)'; + if (GENERATES[func] === body) return '用生体(得力,有贵人相助)'; + if (KILLS[func] === body) return '用克体(受制,诸事不顺)'; + if (KILLS[body] === func) return '体克用(主动,需付出努力)'; + return '体用相生'; +} + +/** 称骨算命 */ +function calculateBoneWeight(lunarYearGanzhiIndex, lunarMonth, lunarDay, earthBranchHourIndex) { + const yearWt = YEAR_WEIGHTS[lunarYearGanzhiIndex] || 9; + const monthWt = MONTH_WEIGHTS[lunarMonth] || 9; + const dayWt = DAY_WEIGHTS[lunarDay] || 9; + const hourWt = HOUR_WEIGHTS[earthBranchHourIndex] || 9; + + const totalWeight = yearWt + monthWt + dayWt + hourWt; + const totalLiang = Math.floor(totalWeight / 10); + const totalQian = totalWeight % 10; + + const interpretation = BONE_INTERPRETATIONS[totalWeight] || { text: '命格推来,自有天定', fortune: '中' }; + + return { + yearWeight: yearWt, + monthWeight: monthWt, + dayWeight: dayWt, + hourWeight: hourWt, + totalWeight, + totalLiang, + totalQian, + interpretation: interpretation.text, + fortune: interpretation.fortune + }; +} + +/** 获取佛教节日 */ +function getBuddhistFestival(lunarMonth, lunarDay) { + return BUDDHIST_FESTIVALS[`${lunarMonth}-${lunarDay}`] || null; +} + +/** 神煞分析 */ +function analyzeShensha(bazi) { + const result = []; + const pillars = [bazi.yearPillar, bazi.monthPillar, bazi.dayPillar, bazi.hourPillar]; + const dayStem = bazi.dayMasterStem; + const dayBranch = bazi.dayPillar.earthBranch; + + // 天乙贵人 + const tianYiMap = { '甲': ['丑', '未'], '戊': ['丑', '未'], '乙': ['子', '申'], '己': ['子', '申'], '丙': ['亥', '酉'], '丁': ['亥', '酉'], '壬': ['卯', '巳'], '癸': ['卯', '巳'], '辛': ['寅', '午'] }; + const tianYiTargets = tianYiMap[dayStem] || []; + const tianYiFound = pillars.filter(p => tianYiTargets.includes(p.earthBranch)); + if (tianYiFound.length > 0) { + result.push({ + name: '天乙贵人', + type: '吉', + category: '贵人', + anchors: ['日干'], + foundIn: tianYiFound.map(p => p.earthBranch), + description: '最吉之神煞,主逢凶化吉、贵人相助' + }); + } + + // 桃花 + const taoHuaMap = { '申': '酉', '子': '酉', '辰': '酉', '寅': '卯', '午': '卯', '戌': '卯', '巳': '午', '酉': '午', '丑': '午', '亥': '子', '卯': '子', '未': '子' }; + const taoHuaTarget = taoHuaMap[dayBranch]; + if (taoHuaTarget) { + const taoHuaFound = pillars.filter(p => p.earthBranch === taoHuaTarget); + if (taoHuaFound.length > 0) { + result.push({ + name: '桃花(咸池)', + type: '中性', + category: '感情', + anchors: ['日支'], + foundIn: taoHuaFound.map(p => p.earthBranch), + description: '主异性缘、魅力与风流,利艺术才华' + }); + } + } + + return result; +} + +/** 大运/流年分析 */ +function analyzeFortuneGanzhi(ganzhi, dayStem, dayBranch) { + const stem = ganzhi[0]; + const branch = ganzhi[1]; + + const tenStar = getTenStarRelationship(dayStem, stem); + const el = STEM_ELEMENTS[stem]; + const de = STEM_ELEMENTS[dayStem]; + let elementRelation; + if (!el || !de || el === de) elementRelation = '比和'; + else if (GENERATES[el] === de) elementRelation = '生我'; + else if (GENERATES[de] === el) elementRelation = '我生'; + else if (KILLS[el] === de) elementRelation = '克我'; + else elementRelation = '我克'; + + const stemCombine = checkStemCombine(stem, dayStem); + const stemOpposite = checkStemOpposite(stem, dayStem); + const br = getBranchRelationship(branch, dayBranch); + + let score = 0; + const goodTenStars = ['正印', '偏印', '食神', '正财', '偏财', '正官']; + const badTenStars = ['七杀', '劫财', '伤官']; + if (tenStar) { + if (goodTenStars.includes(tenStar)) score += 4; + else if (badTenStars.includes(tenStar)) score -= 3; + else score += 1; + } + if (stemCombine) score += 5; + if (stemOpposite) score -= 6; + if (br.combine) score += 4; + if (br.threeCombine) score += 3; + if (br.opposite) score -= 5; + if (br.harm) score -= 4; + if (br.punish) score -= 3; + score = Math.max(-10, Math.min(10, score)); + + const level = score >= 3 ? '吉' : score <= -3 ? '凶' : '平'; + + return { + ganzhi, + tenStar, + elementRelation, + stemCombine, + stemOpposite, + branchCombine: br.combine, + branchThreeCombine: br.threeCombine, + branchOpposite: br.opposite, + branchHarm: br.harm, + branchPunish: br.punish, + score, + level + }; +} + +/** 获取流月 */ +function getYearMonths(year) { + const months = []; + const jieIndices = [3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 1]; + const monthNames = ['正月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '冬月', '腊月']; + + for (let k = 0; k < 12; k++) { + const termYear = k === 11 ? year + 1 : year; + const termIndex = jieIndices[k]; + const month = Math.floor(termIndex / 2) + 1; + const day = SOLAR_TERM_DATES[month - 1][termIndex % 2]; + + const dayInfo = getDayInfo(termYear, month, day); + months.push({ + index: k, + name: monthNames[k], + solarMonth: month, + startDate: `${termYear}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`, + endDate: '', + ganzhi: dayInfo.lunarMonthGanzhi + }); + } + return months; +} + +module.exports = { + getDayInfo, + getTodayInfo, + getMonthCalendar, + getAlmanacInfo, + birthInfoToBazi, + getBranchRelationship, + getTenStarRelationship, + checkStemCombine, + checkStemOpposite, + calculateDailyFortune, + analyzeElementBalance, + calculatePlumBlossom, + calculateBoneWeight, + getBuddhistFestival, + analyzeShensha, + analyzeFortuneGanzhi, + getYearMonths, + solarToLunar, + lunarToSolar, + getGanzhi, + getYearGanzhi, + getMonthGanzhi, + getHourGanzhi, + getZodiac, + getConstellation, + getSolarTerm, + getNayin, + HEAVEN_STEMS, + EARTH_BRANCHES, + ZODIACS, + LUNAR_MONTH_NAMES, + LUNAR_DAY_NAMES, + WEEK_NAMES, + SOLAR_TERMS, + HOUR_NAMES, + HOUR_RANGES, + TRIGRAMS, + HEXAGRAMS +}; diff --git a/web/.gitignore b/web/.gitignore new file mode 100644 index 0000000..90bd728 --- /dev/null +++ b/web/.gitignore @@ -0,0 +1,9 @@ +node_modules +dist +.vite +*.tsbuildinfo +*.local +.DS_Store +*.log +coverage +.swc diff --git a/web/AGENTS.md b/web/AGENTS.md new file mode 100644 index 0000000..92db3bd --- /dev/null +++ b/web/AGENTS.md @@ -0,0 +1,72 @@ +# Lunar Calendar App (万年历) + +Chinese almanac/perpetual calendar app with Bazi calculation, daily fortune, divination tools, and beautiful UI. + +## Documentation + +Detailed docs live in `docs/` — read them before starting any task: + +- **docs/ARCHITECTURE.md** — tech stack, layered design, API surface, routes, stores +- **docs/REQUIREMENTS.md** — full feature inventory with completion status +- **docs/PROGRESS.md** — milestones, current state, prioritized backlog +- **docs/BUGS.md** — known bugs, algorithmic approximations, dead code +- **docs/CHANGELOG.md** — version history + +## Tech Stack +- **Monorepo**: pnpm workspaces +- **Core**: TypeScript + tyme4ts (calendar engine) +- **Web**: React 19 + Vite + TailwindCSS v4 + Framer Motion + Zustand + React Router v7 +- **PWA**: vite-plugin-pwa (configured and built — autoUpdate + Workbox) + +## Project Structure + +``` +lunar/ +├── docs/ # Architecture / Requirements / Progress / Bugs / Changelog +├── packages/ +│ ├── core/ # @lunar/core — Pure TS, no UI deps +│ │ └── src/ +│ │ ├── types/ # calendar.ts, bazi.ts, almanac.ts, fortune.ts +│ │ ├── transformers/ # tyme4ts → plain objects (day.ts, bazi.ts, almanac.ts) +│ │ └── calculators/ # dailyMatch.ts, relationship.ts, elementStrength.ts, plumBlossom.ts, boneWeight.ts +│ └── web/ # @lunar/web — React app +│ └── src/ +│ ├── stores/ # Zustand: calendar, user, settings, ui, bookmarks +│ ├── hooks/ # useCalendar, useDayDetail, useBazi, useDailyFortune +│ ├── components/ # calendar/, layout/, ui/ +│ └── pages/ # Home, Calendar, DayDetail, Bazi, DailyFortune, Settings, Divination, SolarTerms +``` + +## Key Architecture Principles +1. **tyme4ts objects never enter React** — all transformed to plain JS objects in @lunar/core +2. **@lunar/core has zero UI deps** — works in any JS runtime (web, Node, RN, mini-program) +3. **Mobile-first responsive** — phone first, desktop sidebar layout +4. **Dark mode via CSS custom properties** — theme toggle with system preference detection + +## Commands +- `pnpm dev` — Start Vite dev server (port 4258) +- `pnpm build` — Build core (ESM+CJS) + web +- `pnpm --filter @lunar/core build` — Build core only (tsup) +- `pnpm --filter @lunar/web dev` — Start web dev server +- `pnpm preview` — Preview production build +- `pnpm test` — Run core unit tests (vitest) +- `pnpm lint` — Run ESLint on all packages + +## Core API (from @lunar/core) +- `getDayInfo(year, month, day)` → DayInfo +- `getTodayInfo()` → DayInfo +- `getMonthCalendar(year, month, weekStart?)` → DayInfo[][] +- `getAlmanacInfo(year, month, day)` → AlmanacInfo +- `birthInfoToBazi(params)` → BaziFullResult +- `calculateDailyFortune(userBazi, date)` → DailyFortuneResult +- `analyzeElementBalance(bazi)` → ElementProfile +- `calculatePlumBlossom(y, m, d, hour?)` → PlumBlossomResult +- `calculateBoneWeight(...)` → BoneWeightResult +- `getBranchRelationship(a, b)` / `getTenStarRelationship(s, o)` / `checkStemCombine` / `checkStemOpposite` +- `getBuddhistFestival(lunarMonth, lunarDay)` → string | null +- `analyzeShensha(bazi)` → ShenshaInfo[](22 个常见神煞) +- `analyzeFortuneGanzhi(ganzhi, dayStem, dayBranch)` → FortuneLuck(大运/流年 vs 日主生克冲合) +- `getYearMonths(year)` → YearMonthInfo[](按节气月) + +## Known Issues +See **docs/BUGS.md** for the current list. Resolved: core CJS export, week-start setting, PWA manifest metadata, tests (50), lint, 称骨 data tables, 八字流派 switching, shensha, 大运流年 explorer. Remaining: simplified 互卦 algorithm, dead code cleanup. diff --git a/web/CLAUDE.md b/web/CLAUDE.md new file mode 100644 index 0000000..92db3bd --- /dev/null +++ b/web/CLAUDE.md @@ -0,0 +1,72 @@ +# Lunar Calendar App (万年历) + +Chinese almanac/perpetual calendar app with Bazi calculation, daily fortune, divination tools, and beautiful UI. + +## Documentation + +Detailed docs live in `docs/` — read them before starting any task: + +- **docs/ARCHITECTURE.md** — tech stack, layered design, API surface, routes, stores +- **docs/REQUIREMENTS.md** — full feature inventory with completion status +- **docs/PROGRESS.md** — milestones, current state, prioritized backlog +- **docs/BUGS.md** — known bugs, algorithmic approximations, dead code +- **docs/CHANGELOG.md** — version history + +## Tech Stack +- **Monorepo**: pnpm workspaces +- **Core**: TypeScript + tyme4ts (calendar engine) +- **Web**: React 19 + Vite + TailwindCSS v4 + Framer Motion + Zustand + React Router v7 +- **PWA**: vite-plugin-pwa (configured and built — autoUpdate + Workbox) + +## Project Structure + +``` +lunar/ +├── docs/ # Architecture / Requirements / Progress / Bugs / Changelog +├── packages/ +│ ├── core/ # @lunar/core — Pure TS, no UI deps +│ │ └── src/ +│ │ ├── types/ # calendar.ts, bazi.ts, almanac.ts, fortune.ts +│ │ ├── transformers/ # tyme4ts → plain objects (day.ts, bazi.ts, almanac.ts) +│ │ └── calculators/ # dailyMatch.ts, relationship.ts, elementStrength.ts, plumBlossom.ts, boneWeight.ts +│ └── web/ # @lunar/web — React app +│ └── src/ +│ ├── stores/ # Zustand: calendar, user, settings, ui, bookmarks +│ ├── hooks/ # useCalendar, useDayDetail, useBazi, useDailyFortune +│ ├── components/ # calendar/, layout/, ui/ +│ └── pages/ # Home, Calendar, DayDetail, Bazi, DailyFortune, Settings, Divination, SolarTerms +``` + +## Key Architecture Principles +1. **tyme4ts objects never enter React** — all transformed to plain JS objects in @lunar/core +2. **@lunar/core has zero UI deps** — works in any JS runtime (web, Node, RN, mini-program) +3. **Mobile-first responsive** — phone first, desktop sidebar layout +4. **Dark mode via CSS custom properties** — theme toggle with system preference detection + +## Commands +- `pnpm dev` — Start Vite dev server (port 4258) +- `pnpm build` — Build core (ESM+CJS) + web +- `pnpm --filter @lunar/core build` — Build core only (tsup) +- `pnpm --filter @lunar/web dev` — Start web dev server +- `pnpm preview` — Preview production build +- `pnpm test` — Run core unit tests (vitest) +- `pnpm lint` — Run ESLint on all packages + +## Core API (from @lunar/core) +- `getDayInfo(year, month, day)` → DayInfo +- `getTodayInfo()` → DayInfo +- `getMonthCalendar(year, month, weekStart?)` → DayInfo[][] +- `getAlmanacInfo(year, month, day)` → AlmanacInfo +- `birthInfoToBazi(params)` → BaziFullResult +- `calculateDailyFortune(userBazi, date)` → DailyFortuneResult +- `analyzeElementBalance(bazi)` → ElementProfile +- `calculatePlumBlossom(y, m, d, hour?)` → PlumBlossomResult +- `calculateBoneWeight(...)` → BoneWeightResult +- `getBranchRelationship(a, b)` / `getTenStarRelationship(s, o)` / `checkStemCombine` / `checkStemOpposite` +- `getBuddhistFestival(lunarMonth, lunarDay)` → string | null +- `analyzeShensha(bazi)` → ShenshaInfo[](22 个常见神煞) +- `analyzeFortuneGanzhi(ganzhi, dayStem, dayBranch)` → FortuneLuck(大运/流年 vs 日主生克冲合) +- `getYearMonths(year)` → YearMonthInfo[](按节气月) + +## Known Issues +See **docs/BUGS.md** for the current list. Resolved: core CJS export, week-start setting, PWA manifest metadata, tests (50), lint, 称骨 data tables, 八字流派 switching, shensha, 大运流年 explorer. Remaining: simplified 互卦 algorithm, dead code cleanup. diff --git a/web/README.md b/web/README.md new file mode 100644 index 0000000..c51302c --- /dev/null +++ b/web/README.md @@ -0,0 +1,56 @@ +# 万年历(Lunar Calendar App) + +中国农历 / 黄历应用:农历转换、黄历宜忌、八字排盘(大运流年流月流日)、每日运势、神煞、梅花易数、称骨算命、节气、佛教节日。移动端优先,支持 PWA 离线安装。 + +## 技术栈 + +- **Monorepo**:pnpm workspaces +- **@lunar/core**:TypeScript + [tyme4ts](https://github.com/6tail/tyme4ts)(历法引擎),零 UI 依赖,ESM+CJS 双产物 +- **@lunar/web**:React 19 + Vite 6 + TailwindCSS v4 + Zustand 5 + React Router v7 + Framer Motion + vite-plugin-pwa + +## 快速开始 + +```bash +pnpm install +pnpm dev # http://localhost:4258 +pnpm test # core 单元测试(vitest,50 例) +pnpm lint # ESLint +pnpm build # 构建 core + web(含 PWA) +pnpm preview # 预览构建产物 +``` + +> 需要 Node ≥ 18、pnpm ≥ 9。 + +## 文档 + +详细文档见 `docs/`: + +- [ARCHITECTURE.md](docs/ARCHITECTURE.md) — 架构、API、路由、状态管理 +- [REQUIREMENTS.md](docs/REQUIREMENTS.md) — 功能点清单与完成状态 +- [PROGRESS.md](docs/PROGRESS.md) — 里程碑与待办 +- [BUGS.md](docs/BUGS.md) — 已知 BUG、技术债、死代码 +- [CHANGELOG.md](docs/CHANGELOG.md) — 变更日志 +- [RETROSPECT.md](docs/RETROSPECT.md) — 会话回顾(做了什么/没做什么/疑问) + +## 目录结构 + +``` +lunar/ +├── docs/ # 项目文档 +├── packages/ +│ ├── core/ # @lunar/core —— 纯 TS 计算引擎 +│ └── web/ # @lunar/web —— React 应用 +├── AGENTS.md / CLAUDE.md # 开发代理指引 +└── eslint.config.js # ESLint flat config +``` + +## 核心能力 + +| 模块 | 说明 | +|---|---| +| 历法 | 公历/农历/干支/节气/季节/儒略日/佛历/伊斯兰历/月相 | +| 黄历 | 宜忌、值神(日/时)、二十八宿、彭祖百忌、胎神、冲煞 | +| 八字 | 四柱、十神、藏干、十二长生、大运、流年、神煞、流派切换 | +| 运势 | 每日运势、流年/流月/流日 vs 日主生克冲合 | +| 占卜 | 梅花易数(64 卦)、袁天罡称骨 | +| 其他 | 佛教节日、节气列表、收藏、PWA | diff --git a/web/docs/ARCHITECTURE.md b/web/docs/ARCHITECTURE.md new file mode 100644 index 0000000..150c860 --- /dev/null +++ b/web/docs/ARCHITECTURE.md @@ -0,0 +1,162 @@ +# 架构文档(Architecture) + +> 最后更新:2026-08-03 + +## 1. 项目概述 + +万年历(Lunar Calendar App):中国农历/黄历应用,提供农历转换、黄历宜忌、八字排盘、每日运势、梅花易数、称骨算命、节气等命理功能,移动端优先,支持 PWA 离线安装。 + +## 2. 技术栈 + +| 层 | 技术 | +|---|---| +| Monorepo | pnpm workspaces(`packages/*`) | +| 核心引擎 | TypeScript + [tyme4ts](https://github.com/6tail/tyme4ts) ^1.5.1(历法/干支计算) | +| 构建 | tsup(core)、Vite 6 + tsc(web) | +| UI | React 19、React Router v7、Zustand 5、TailwindCSS v4(CSS-first)、Framer Motion、lucide-react | +| PWA | vite-plugin-pwa 0.21(Workbox,已启用) | + +## 3. 目录结构 + +``` +lunar/ +├── docs/ # 项目文档(本目录,含 RETROSPECT 会话回顾) +├── AGENTS.md # 开发代理指引 +├── package.json # workspace 聚合脚本 +├── pnpm-workspace.yaml # 工作区定义 +├── tsconfig.base.json # 共享 TS 配置 +└── packages/ + ├── core/ # @lunar/core —— 纯 TS 计算引擎,零 UI 依赖 + │ ├── tsup.config.ts + │ └── src/ + │ ├── index.ts # 公共 API 出口(barrel) + │ ├── types/ # calendar.ts / bazi.ts / almanac.ts / fortune.ts + │ ├── transformers/ # day.ts / bazi.ts / almanac.ts / yearMonths.ts(tyme4ts → 纯对象) + │ └── calculators/ # dailyMatch / relationship / elementStrength / plumBlossom / boneWeight / buddhistDates / shensha / fortuneLuck + └── web/ # @lunar/web —— React 应用 + ├── vite.config.ts # Vite + Tailwind v4 + PWA + ├── index.html + └── src/ + ├── App.tsx # 路由 + 懒加载 + 页面过渡 + ErrorBoundary + ├── main.tsx + ├── styles/globals.css # 设计令牌(亮/暗主题) + ├── lib/utils.ts # cn、日期格式化等工具 + ├── stores/ # calendar / user / settings / ui / bookmarks(Zustand) + ├── hooks/ # useCalendar / useDayDetail / useBazi / useDailyFortune + ├── components/ + │ ├── calendar/ # CalendarGrid / CalendarCell / MonthYearPicker / WeekDayBar + │ ├── layout/ # AppShell / Header / BottomNav + │ ├── ui/ # Badge / Button / Card / AnimatedPanel / ErrorBoundary / Skeleton + │ ├── day-detail/ bazi/ daily-fortune/(页面内联实现,目录内暂无独立组件) + └── pages/ # 8 个页面(见路由表) +``` + +## 4. 分层设计 + +``` +┌───────────────────────────────┐ +│ @lunar/web (React 页面层) │ +│ pages → hooks → stores │ +├───────────────────────────────┤ +│ @lunar/core (纯计算引擎) │ +│ index.ts │ +│ ├─ transformers tyme4ts → │ +│ │ 纯 JS 对象 │ +│ ├─ calculators 业务算法 │ +│ └─ types 类型定义 │ +├───────────────────────────────┤ +│ tyme4ts (历法计算源) │ +└───────────────────────────────┘ +``` + +### 4.1 核心原则 + +1. **tyme4ts 对象永不进入 React**:所有历法对象在 `@lunar/core` 内转换为普通 JS 对象(`DayInfo`、`AlmanacInfo`、`BaziFullResult` 等),React 层只消费纯数据。 +2. **@lunar/core 零 UI 依赖**:仅依赖 tyme4ts,可在任意 JS 运行时运行(web / Node / RN / 小程序)。 +3. **移动端优先**:手机优先布局,桌面端自适应。 +4. **暗色模式**:CSS 自定义属性 + `.dark` class,跟随系统偏好 + 手动切换。 + +### 4.2 数据流示例 + +``` +SolarDay (tyme4ts) + → solarDayToDayInfo() # DayInfo + → solarDayToAlmanacInfo() # AlmanacInfo(含 12 时辰) + → birthInfoToBazi() # BaziFullResult(四柱/十神/大运…) + → calculateDailyFortune() # DailyFortuneResult(每日运势) +``` + +## 5. @lunar/core API 一览 + +### 类型 +`DayInfo`、`AlmanacInfo`、`HourAlmanac`、`PillarInfo`、`HideStemInfo`、`EightCharInfo`、`DecadeFortuneInfo`、`FortuneInfo`、`ChildLimitInfo`、`BaziFullResult`、`PillarRelationship`、`DailyFortuneResult`、`BirthParams`、`BranchRelationship`、`ElementProfile`、`TrigramInfo`、`HexagramInfo`、`PlumBlossomResult`、`BoneWeightResult` + +### 函数 + +| 函数 | 说明 | +|---|---| +| `solarDayToDayInfo(solarDay)` | SolarDay → DayInfo(含季节/节气进度/儒略日/佛历/伊斯兰历/佛教节日) | +| `getDayInfo(y, m, d)` / `getTodayInfo()` | 获取某日/今日信息 | +| `getMonthCalendar(y, m, weekStart?)` | 月历二维数组(周 × 天),可指定周起始 | +| `solarDayToAlmanacInfo(solarDay)` | SolarDay → AlmanacInfo | +| `getAlmanacInfo(y, m, d)` | 获取黄历信息(宜忌/值神/冲煞/时辰) | +| `birthInfoToBazi(params)` | 出生信息 → 完整八字排盘(`ziSect` 流派参数:晚子时换日) | +| `getYearMonths(year)` | 按节气月返回某年 12 个流月 | +| `getBranchRelationship(a, b)` | 地支六合/三合/六冲/六害/相刑 | +| `getTenStarRelationship(s, o)` / `checkStemCombine` / `checkStemOpposite` | 干支关系判断 | +| `calculateDailyFortune(userBazi, date)` | 用户八字 × 日期 → 每日运势评分 | +| `analyzeElementBalance(bazi)` | 五行力量分析 | +| `calculatePlumBlossom(y, m, d, h?)` | 梅花易数起卦 | +| `calculateBoneWeight(...)` | 袁天罡称骨算命 | +| `getBuddhistFestival(lunarMonth, lunarDay)` | 农历佛教节日 | +| `analyzeShensha(bazi)` | 22 个常见神煞 | +| `analyzeFortuneGanzhi(ganzhi, dayStem, dayBranch)` | 大运/流年/流月/流日 vs 日主生克冲合 | + +## 6. 前端状态管理(Zustand) + +| Store | 持久化 Key | 职责 | 备注 | +|---|---|---|---| +| `calendar` | — | 视图日期 / 选中日期 / 周起始 | `weekStart`、`clearSelection` 当前未被使用 | +| `user` | `lunar-user-profiles` | 出生档案(最多 3 个)、active 档案、八字结果 | | +| `settings` | `lunar-settings` | 主题、周起始、显示开关、八字来源 | `showLunar` 等 4 个字段未被消费 | +| `ui` | — | 侧栏 / 移动端 / 底部面板 | 多数 action 未被消费 | +| `bookmarks` | `lunar-bookmarks` | 日期收藏(标记在日历格上) | `getByDate`/`getByLunarDate` 未使用 | + +## 7. 路由(React Router v7,全部懒加载) + +| 路径 | 页面 | 底部导航 | +|---|---|---| +| `/` | HomePage(今日概览) | ✔ | +| `/calendar` | CalendarPage(月/周视图) | ✔ | +| `/calendar/:date` | DayDetailPage(日详情) | — | +| `/bazi` | BaziPage(八字排盘) | ✔ | +| `/daily-fortune` | DailyFortunePage(每日运势) | ✔ | +| `/settings` | SettingsPage(设置) | ✔ | +| `/divination` | DivinationPage(梅花易数 + 称骨) | 首页快捷入口 | +| `/solar-terms` | SolarTermsPage(节气) | 首页快捷入口 | +| `*` | 重定向 `/` | — | + +## 8. PWA(已启用) + +- `vite-plugin-pwa`:autoUpdate 模式 + Workbox 预缓存 + google-fonts 运行时缓存 +- 构建产物:`manifest.webmanifest`、`sw.js`、`registerSW.js` +- manifest 已与 index.html 对齐(`lang: zh-CN`、`theme_color: #FFFBF5`) + +## 9. 构建与命令 + +| 命令 | 说明 | +|---|---| +| `pnpm dev` | 启动 web dev(端口 4258) | +| `pnpm build` | 构建 core → web | +| `pnpm --filter @lunar/core build` | 仅构建 core(tsup,ESM+CJS) | +| `pnpm --filter @lunar/web build` | 仅构建 web(tsc -b && vite build) | +| `pnpm preview` | 预览构建产物 | +| `pnpm test` | core 单元测试(vitest,50 例) | +| `pnpm lint` | ESLint(flat config + typescript-eslint) | +| `pnpm clean` | 清理 dist | + +## 10. 已知架构问题(详见 BUGS.md) + +- 梅花易数互卦为简化实现(上下卦互换,非真·互卦 2-4/3-5 爻法) +- 每日运势当日八字固定取午时;称骨极端总重仍取"最近值" +- 若干死代码(未使用的 hooks / 组件 / store action,见 BUGS.md 清单) diff --git a/web/docs/BUGS.md b/web/docs/BUGS.md new file mode 100644 index 0000000..299a567 --- /dev/null +++ b/web/docs/BUGS.md @@ -0,0 +1,64 @@ +# BUG 记录(Known Issues) + +> 最后更新:2026-08-02 +> 严重度:🔴 高(影响功能/构建) · 🟠 中 · 🟡 低 +> 状态:⬜ 待修复 · 🔧 修复中 · ✅ 已修复 + +## 0. 已修复汇总(2026-08-02) + +- BUG-01 core CJS 导出 · BUG-02 周起始设置 · BUG-05 lint · BUG-07 版本号 · BUG-08 死分支 +- BUG-03 PWA manifest 与 index.html 对齐(lang=zh-CN、theme_color 统一) +- BUG-09 日历页"回到今天"按钮永不显示(条件恒为 false) +- BUG-10 日详情页时辰列表重复 React key(早子/晚子均为"子",改用 ganzhi 作 key) +- TECH-03 更正:梅花卦库实测 **64/64 完整**(原 review 报告"缺 8 卦"不属实),已添加测试锁定 +- TECH-06 以 `solarAdjusted` 类型安全字段替代 `(as any)._solarAdjusted` +- TECH-04 称骨数据表整体修正(年表主流版本 + 日表初五 + 歌诀 21~72 补全) +- TECH-05 占卜页称骨年索引改用出生年干支 +- 新增单元测试 50 个(vitest)与 ESLint 配置 +- 功能深化:八字流派/出生地全国化/海外时区/太阳时开关、神煞、大运流年流月流日交互、历法信息、佛教节日(见 CHANGELOG) + +## 1. 已知 BUG + +| ID | 严重度 | 状态 | 位置 | 描述 | +|---|---|---|---|---| +| BUG-01 | 🟠 | ✅ | `packages/core` | ~~exports.require 指向不存在的 dist/index.cjs~~ → tsup 改为 `format: ['esm','cjs']`,`dist/index.cjs` 已生成并通过 `require()` 验证 | +| BUG-02 | 🟠 | ✅ | `useCalendar` / `CalendarGrid` / `WeekDayBar` / `CalendarPage` | ~~周一开始设置无效~~ → `settings.weekStartDay` 已贯通至 `getMonthCalendar(weekStart)` 与表头渲染 | +| BUG-03 | 🟡 | ✅ | `vite.config.ts` / `index.html` | ~~PWA manifest lang=en、theme_color 不一致~~ → manifest 增加 `lang: zh-CN`,`theme_color` 统一为 `#FFFBF5`,与 index.html 一致 | +| BUG-04 | 🟡 | ⬜ | `HomePage`(InfoCard) | `highlight` prop 传为布尔值,但渲染时当作 `border-primary/30` 样式类处理,语义不符(需确认意图) | +| BUG-05 | 🟡 | ✅ | 根 `package.json` | ~~pnpm lint 失败~~ → 已配置 ESLint 9 flat config + typescript-eslint,两个包均通过 | +| BUG-06 | 🟡 | ⬜ | `pnpm-workspace.yaml` | `allowBuilds` 不是 pnpm 标准字段(应为 `onlyBuiltDependencies`),疑似无效配置 | +| BUG-07 | 🟡 | ✅ | `SettingsPage` 关于区 | ~~版本号 v0.2~~ → 与 package.json 统一为 v0.1.0 | +| BUG-08 | 🟡 | ✅ | `transformers/day.ts` | ~~getMonthCalendar 死分支~~ → 已移除 if/else 相同调用 | +| BUG-09 | 🟡 | ✅ | `CalendarPage.tsx` | ~~"回到今天"按钮永不显示~~ → 条件误用 `todaySummary.di.isToday`(恒为 true),改为基于 `viewDate` 判断是否正在查看今天;已在浏览器实测 | +| BUG-10 | 🟡 | ✅ | `DayDetailPage.tsx` | ~~时辰列表重复 React key~~ → `getHours()` 返回 13 个时辰(早子 00:00 / 晚子 23:00 均为地支"子"),原用 `h.branch` 作 key 冲突,改为唯一的 `h.ganzhi`;已在浏览器实测无警告 | + +## 2. 算法近似 / 技术债 + +> 非紧急,但应在后续迭代中改善。 + +| ID | 位置 | 说明 | +|---|---|---| +| TECH-01 | `calculators/dailyMatch.ts` | 当日八字固定取午时(`getHours()[6]`),无法体现时辰差异 | +| TECH-02 | `calculators/plumBlossom.ts` | 互卦为简化实现(上下卦互换),非真·互卦(2-4/3-5 爻法) | +| TECH-03 | `calculators/plumBlossom.ts` | ✅ 已核实:数据集 64/64 完整,`plumBlossom.test.ts` 含完整性校验用例 | +| TECH-04 | `calculators/boneWeight.ts` | ✅ 已修复:歌诀补全 2两1钱~7两2钱(原缺 5两8钱/5两9钱/6两1钱/6两3钱/6两5钱/6两7钱/6两9钱/7两1钱,此前这些总重会错误地取"最近值");年表已按主流版本整体修正(原混用两套网络变体错约 40 处),日表修正初五重量 | +| TECH-05 | `DivinationPage` | ✅ 已修复:称骨年索引改用出生年干支(`ec.yearPillar.ganzhi`),不再用日干支近似 | +| TECH-06 | `types/bazi.ts` | ✅ 已修复:`BaziFullResult` 新增 `solarAdjusted?: boolean`,替代 `(as any)` 补丁 | +| TECH-07 | `calculators/elementStrength.ts` | 五行推断按天干名字映射而非复用 tyme4ts 元素定义(可接受但存在重复逻辑) | +| TECH-08 | `lib/utils.ts` | `getChineseDayName` / `getChineseMonthName` 未使用 | + +## 3. 死代码清单 + +| 位置 | 内容 | 建议 | +|---|---|---| +| `hooks/useDayDetail.ts` | 从未被 import | 删除或接入 DayDetailPage | +| `hooks/useBazi.ts` | 从未被 import(本轮仅修复了其 lint 错误) | 删除或接入 BaziPage | +| `components/ui/AnimatedPanel.tsx` | 从未使用 | 删除或接入日详情弹层 | +| `components/ui/Card.tsx` `CardHeader` | 未使用 | 删除导出 | +| `components/ui/Skeleton.tsx` `CalendarSkeleton`/`BaziSkeleton` | 未使用 | 删除 | +| `stores/ui.ts` | `toggleSideNav`/`closeSideNav`/`setActiveTab`/`openDayDetail`/`closeDayDetail` 及 `activeTab`/`showDayDetail` 未被消费 | 精简或接入 | +| `stores/settings.ts` | `showLunar`/`showSolarTerm`/`showHoliday`/`eightCharProvider` 及对应 toggle 未被读取 | 接入设置页或删除 | +| `stores/calendar.ts` | `weekStart`/`setWeekStart`/`clearSelection` 未使用(周起始现直接读 settings store) | 删除 | +| `stores/bookmarks.ts` | `getByDate`/`getByLunarDate` 未使用 | 配合农历收藏规划 | +| `lib/utils.ts` | `getChineseDayName`/`getChineseMonthName` | 删除 | +| web 依赖 | `tailwind-variants` 从未 import | 移除依赖 | diff --git a/web/docs/CHANGELOG.md b/web/docs/CHANGELOG.md new file mode 100644 index 0000000..8ee7813 --- /dev/null +++ b/web/docs/CHANGELOG.md @@ -0,0 +1,80 @@ +# 变更日志(Changelog) + +> 格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/)。版本号遵循 SemVer。 + +## [Unreleased] + +### 文档 +- 新增 `docs/` 文档体系:架构(ARCHITECTURE)、需求(REQUIREMENTS)、进度(PROGRESS)、BUG(BUGS)、变更日志(CHANGELOG) +- 更新 `AGENTS.md` / `CLAUDE.md`:补全新页面/Store/API、修正 PWA 状态、指向文档目录 + +### 功能 +- **八字流派切换**:`birthInfoToBazi` 新增 `ziSect` 参数,设置页新增"八字流派"选项(晚子时算次日 23点换日 / 晚子时算当日 0点换日),BaziPage 排盘实时生效;浏览器实测两种流派排出不同日柱/时柱 +- **出生时间选择器**:BaziPage 出生时间改为 时/分 下拉选择器(替代不对称的 −/+ 步进),时辰格可点击快速设置时辰 +- **出生地全国化 + 海外时区**:出生地列出全国 34 个省级城市(含省会经度真太阳时校正),新增"海外"模式按时区(UTC-12~+14,含半小时时区)将当地出生时间换算为北京时间(UTC+8)排盘;换算与真太阳时校正均按日期运算处理,支持跨日回退(浏览器实测:UTC-5 23:30 → 次日北京 12:30;乌鲁木齐 00:30 真太阳 → 前日 22:20) +- **真太阳时校正开关**:设置页新增"真太阳时校正"开关(默认开启);关闭后中国出生按北京时间直接排盘,海外时区换算不受影响(浏览器实测:关闭时乌鲁木齐 00:30 排日柱己酉,开启时排戊申) +- **宜忌完整显示**:引擎本就返回完整黄历宜忌(如 8/3 共 22 条宜),此前首页 `slice(0,6)` 截断导致显得比别的黄历站少;已放开并标注总数 +- **历法信息增强**:DayInfo 新增季节、所处节气/第几天/距下节气天数、儒略日、佛历年(公历+543)、伊斯兰历日期(tyme4ts 原生支持 Hijri),日详情页新增"历法信息"卡片 +- **佛教节日**:tyme4ts 无佛教日期支持,新增 `getBuddhistFestival`(农历 21 个重大佛教节日表),日详情页显示 🪷 徽章 +- **八字神煞**:新增 `analyzeShensha`(22 个常见神煞:天乙/天厨/文昌贵人、禄神、羊刃、金舆、天德/月德贵人、桃花、驿马、华盖、劫煞、亡神、将星、红鸾、天喜、孤辰、寡宿、天罗、地网、魁罡、阴差阳错),八字页新增"神煞"卡片;查法采用主流排盘工具版本(神煞版本差异已在文档注明) +- **大运/流年生克冲合**:新增 `analyzeFortuneGanzhi`(任意干支 vs 日主:十神、五行生克 生我/我生/克我/我克/比和、天干合冲、地支六合三合冲害刑、吉凶分级);八字页"起运·大运"改为全量 10 个大运并附生克冲合,新增"流年(小运)"10 条卡片 +- **今日流年·流月·流日**:八字页新增卡片,用今日年/月/日干支 vs 日主展示生克冲合 +- **大运·流年·流月·流日交互下钻**:core 新增 `getYearMonths(year)`(按节气月返回 12 个流月);八字页改为交互浏览器——点选大运 → 流年(按年度切换)→ 流月 12 chips → 该月每日流日列表(带吉凶点与十神),点击日期跳转日详情页;默认定位到当前年份/月份 + +### 修复 +- **称骨算命数据表整体修正**:年份表改为主流通行版本(原混用两套网络变体错约 40 处);日表修正初五(1两6钱);歌诀补全 2两1钱~7两2钱全部 52 条(原缺 5两8钱~7两1钱共 8 条,此前这些总重错误取"最近值") +- **占卜页称骨年索引**:改用出生年干支(`ec.yearPillar.ganzhi`),不再用日干支近似 +- **BUG-01**:core 包改为同时输出 ESM + CJS,`exports.require` 指向的 `dist/index.cjs` 现在真实存在并通过 `require()` 验证 +- **BUG-02**:周起始设置生效 —— `settings.weekStartDay` 贯通 `useCalendar` → `getMonthCalendar(weekStart)` → `WeekDayBar` 表头渲染 +- **BUG-03**:PWA manifest 对齐 —— `lang: zh-CN`,`theme_color` 统一为 `#FFFBF5`(与 index.html 一致) +- **BUG-05**:配置 ESLint 9 flat config + typescript-eslint,`pnpm lint` 通过(修复 24 处代码问题) +- **BUG-07**:设置页版本号统一为 0.1.0 +- **BUG-08**:移除 `getMonthCalendar` 中的死分支 +- **BUG-09**:日历页"回到今天"浮动按钮恢复正常 —— 原条件 `todaySummary.di.isToday` 恒为 true 导致按钮永不显示,改为基于 `viewDate` 判断(浏览器实测:非本月可见、点击返回本月) +- **BUG-10**:日详情页时辰列表重复 key 修复 —— 早子/晚子地支均为"子",key 改用唯一的干支(`h.ganzhi`) +- **TECH-06**:`BaziFullResult` 新增 `solarAdjusted?: boolean` 字段,替代 `(as any)._solarAdjusted` 非类型安全补丁 + +### 工程 +- 为 @lunar/core 引入 vitest 单元测试(7 个测试文件 / 29 个用例),覆盖历法转换、八字排盘、每日运势、梅花易数(含 64 卦完整性校验)、称骨、干支关系、五行分析 +- 根与子包新增 `test` / `lint` 脚本;根 package.json 标记 `"type": "module"` +- 核实梅花卦库为 64/64 完整(此前 review 报告"缺 8 卦"不属实),并以测试固化 + +### 已知问题(见 docs/BUGS.md) +- 梅花易数互卦仍为简化实现(上下卦互换) +- 每日运势当日八字固定取午时;称骨年索引用日干支近似 +- PWA manifest `lang`/`theme_color` 不一致;死代码待清理 + +## [0.1.0] - 2026-06-07 + +首个可用版本(monorepo 初始化于 2026-06-06)。 + +### 新增 — @lunar/core + +- 历法转换:`getDayInfo` / `getTodayInfo` / `getMonthCalendar` / `solarDayToDayInfo` +- 黄历:`getAlmanacInfo` / `solarDayToAlmanacInfo`(含 12 时辰逐时黄历) +- 八字:`birthInfoToBazi`(四柱、十神、藏干、十二长生、胎元胎息、命宫身宫、空亡、起运、10 大运、10 流年) +- 干支关系:`getBranchRelationship` / `getTenStarRelationship` / `checkStemCombine` / `checkStemOpposite` +- 五行分析:`analyzeElementBalance` +- 每日运势:`calculateDailyFortune`(四柱打分、宜忌、幸运色/数/方位、分项得分) +- 占卜:`calculatePlumBlossom`(梅花易数,56/64 卦)、`calculateBoneWeight`(袁天罡称骨) + +### 新增 — @lunar/web + +- 路由与骨架:8 页面懒加载、页面过渡动画、ErrorBoundary、Suspense 骨架屏 +- 首页:今日概览、宜忌、运势预览、快捷入口 +- 日历:月/周视图、年/月切换、收藏星标、运势点、滑动切月、日详情页(四柱/黄历/12 时辰/收藏) +- 八字排盘页:档案管理(最多 3 个,localStorage 持久化)、排盘全览、称骨 +- 每日运势页:日期导航、评分与建议 +- 占卜页:梅花易数今日卦象、称骨 +- 节气页:全年 24 节气 +- 设置页:主题切换(亮/暗/跟随系统)、周起始、阴阳历转换、今日干支 +- 状态管理:Zustand 5 个 store(calendar / user / settings / ui / bookmarks) +- PWA:vite-plugin-pwa 集成(autoUpdate + Workbox 预缓存) +- 设计系统:Tailwind v4 设计令牌(亮/暗主题)、Badge / Button / Card / Skeleton + +### 已知问题(见 docs/BUGS.md) + +- core 包 require 导出指向不存在的 `dist/index.cjs` +- 周起始设置未生效 +- 梅花易数互卦简化、卦库缺 8 卦;称骨年索引用日干支近似 +- 无自动化测试、无 lint diff --git a/web/docs/PROGRESS.md b/web/docs/PROGRESS.md new file mode 100644 index 0000000..9bf8b32 --- /dev/null +++ b/web/docs/PROGRESS.md @@ -0,0 +1,49 @@ +# 开发进度(Progress) + +> 最后更新:2026-08-03 + +## 1. 里程碑 + +| 里程碑 | 时间 | 内容 | 状态 | +|---|---|---|---| +| M0 项目初始化 | 2026-06-06 | monorepo 脚手架、tsconfig、workspace、核心类型定义 | ✅ | +| M1 核心引擎 | 2026-06-06 ~ 06-07 | transformers(day/almanac/bazi)、calculators(relationship/elementStrength) | ✅ | +| M2 高级算法 | 2026-06-07 | dailyMatch(每日运势)、plumBlossom(梅花)、boneWeight(称骨) | ✅ | +| M3 前端页面 | 2026-06-07 | 8 个页面、5 个 store、路由、布局、组件库 | ✅ | +| M4 构建与 PWA | 2026-06-07 | Vite 构建、tsup、PWA 集成(Workbox 缓存) | ✅ | +| M5 文档化 | 2026-08-02 | 架构/需求/进度/BUG/CHANGELOG 文档建立 | ✅ | +| M6 质量加固 | 2026-08-02 | 单元测试(vitest)、ESLint、CJS 导出修复、周起始修复、版本号 | ✅ | +| M7 功能深化 | 2026-08-02 ~ 08-03 | 称骨数据修正、八字流派/出生地/太阳时开关、神煞、大运流年流月流日交互、历法信息、佛教节日 | ✅ | +| M8 收尾 | 2026-08-03 | README、文档整理、源码打包(node_modules 清理) | ✅ | + +> 注:主体功能于 2026-06-06 ~ 06-07 完成;2026-08-02 ~ 08-03 进行质量加固与功能深化。 + +## 2. 当前状态 + +- **可运行**:`pnpm dev` 可启动,`pnpm build` 可产出 core(ESM+CJS)+ web 产物(含 PWA)。 +- **质量**:50 个单元测试通过(vitest)、`pnpm lint` 通过(ESLint flat config + typescript-eslint)。 +- 剩余 BUG 与技术债见 BUGS.md。 + +## 3. 待办清单(Backlog) + +按优先级排序: + +| 优先级 | 事项 | 类型 | 关联 | 状态 | +|---|---|---|---|---| +| P0 | 修复 core 包 `require` 导出指向不存在的 `dist/index.cjs` | BUG | BUG-01 | ✅ | +| P1 | 周起始设置生效 | 功能 | BUG-02 | ✅ | +| P1 | 为 core 算法补充单元测试 | 工程 | N5 | ✅ 50 例 | +| P2 | 六十四卦数据补全 | 算法 | C10 | ✅ 已核实 64/64 完整,测试锁定 | +| P2 | 互卦算法实现真·互卦(2-4/3-5 爻) | 算法 | C10 | ⬜ | +| P2 | 清理死代码(useDayDetail/useBazi、AnimatedPanel、CardHeader、未用 store action 等) | 工程 | — | ⬜ | +| P3 | PWA manifest 修复 | BUG | BUG-03 | ✅ | +| P3 | 农历周期收藏 UI | 功能 | M2 | ⬜ | +| P3 | 收藏列表页 | 功能 | M3 | ⬜ | +| P4 | 配置 lint 并修复 `pnpm lint` | 工程 | N6 | ✅ | +| P4 | 显示开关字段真正消费(showLunar 等) | 功能 | T6 | ⬜ | +| P4 | 版本号统一(0.1.0) | BUG | BUG-07 | ✅ | +| P4 | 称骨数据表整体修正(年表/日表/歌诀) | 算法 | C11 | ✅ | +| P4 | 占卜页称骨年索引用出生年干支 | 算法 | TECH-05 | ✅ | +| P5 | 每日运势当日八字取午时 → 改为可选时辰 | 算法 | C9 | ⬜ | +| P5 | 称骨极端总重仍取"最近值" → 提示超出范围 | 算法 | TECH-04 | ⬜ | +| P5 | README 编写 | 工程 | M8 | ✅ | diff --git a/web/docs/REQUIREMENTS.md b/web/docs/REQUIREMENTS.md new file mode 100644 index 0000000..62d0aef --- /dev/null +++ b/web/docs/REQUIREMENTS.md @@ -0,0 +1,142 @@ +# 需求文档(Requirements & 功能清单) + +> 最后更新:2026-08-02 +> 状态说明:✅ 已完成 · 🟡 部分完成 / 有已知问题 · ⬜ 规划中 + +## 1. 需求总览 + +| 模块 | 状态 | 说明 | +|---|---|---| +| 历法核心引擎(@lunar/core) | ✅ | 农历/干支/黄历/八字/运势算法 | +| 万年历(首页 + 日历 + 日详情) | ✅ | 功能齐全(周起始设置已修复) | +| 八字排盘 | 🟡 | 功能齐全,含一个非类型安全补丁 | +| 每日运势 | ✅ | 依赖用户档案 | +| 占卜工具(梅花易数 / 称骨) | 🟡 | 算法为简化实现(见备注) | +| 节气 | ✅ | 24 节气列表 | +| 用户档案 + 设置 | 🟡 | 部分设置字段未生效 | +| 收藏 | 🟡 | 仅支持公历单日收藏 | +| PWA | ✅ | 已构建,manifest 有瑕疵 | + +## 2. 功能点清单 + +### 2.1 历法核心(@lunar/core) + +| # | 功能点 | 描述 | 状态 | +|---|---|---|---| +| C1 | 日期转换 | tyme4ts → DayInfo(公历/农历/干支/星座/生肖/节日/假日/月相) | ✅ | +| C2 | 月历生成 | `getMonthCalendar` 周×天网格,含相邻月补位 | ✅ | +| C3 | 黄历信息 | 宜忌、值神(日/时)、十二/廿八/九/六曜星、彭祖百忌、胎神、冲煞、纳音、三合/六合 | ✅ | +| C4 | 时辰黄历 | 12 时辰逐时黄历(buildHourlyAlmanac) | ✅ | +| C5 | 八字排盘 | 四柱、十神、藏干、十二长生、胎元/胎息、命宫/身宫、空亡、起运 | ✅ | +| C6 | 大运/流年 | 10 大运 + 10 流年 | ✅ | +| C7 | 干支关系 | 六合/三合/六冲/六害/相刑/三合局/天干合冲 | ✅ | +| C8 | 五行分析 | 五行力量统计(藏干 0.5 权重)、旺衰、平衡度 | ✅ | +| C9 | 每日运势 | 四柱逐柱 vs 当日干支打分(日 40%/月 25%/年 20%/时 15%),输出评分、宜忌、幸运色/数/方位、分项(感情/事业/财运/健康) | 🟡 当日八字固定取午时 | +| C10 | 梅花易数 | 时间起卦、变卦、互卦(简化)、体用关系、64 卦辞 | 🟡 互卦简化为上下互换(卦库 64/64 完整,已有测试) | +| C11 | 称骨算命 | 年/月/日/时称骨表 + 52 首解诗(2两1钱~7两2钱完整) | ✅ 数据表已按主流版本修正,歌诀补全(极端值仍取最近) | +| C12 | 历法信息 | 季节、所处节气/第几天/距下节气、儒略日、佛历年、伊斯兰历(Hijri) | ✅ | +| C13 | 佛教节日 | 农历 21 个重大佛教节日(`getBuddhistFestival`) | ✅ 日详情页 🪷 徽章展示 | +| C14 | 八字神煞 | 22 个常见神煞(`analyzeShensha`) | ✅ 八字页"神煞"卡片 | +| C15 | 大运/流年 vs 日主生克冲合 | 十神、五行生克、天干合冲、地支关系、吉凶(`analyzeFortuneGanzhi`) | ✅ | +| C16 | 流年·流月·流日分析 | 任意日期年/月/日干支 vs 日主(`getYearMonths` 按节气月) | ✅ 八字页交互浏览器 | + +### 2.2 首页 HomePage(`/`) + +| # | 功能点 | 状态 | +|---|---|---| +| H1 | 今日概览:日期、农历、干支、宜忌摘要 | ✅ | +| H2 | 运势预览(需档案) | ✅ | +| H3 | 快捷导航(占卜、节气) | ✅ | +| H4 | 值神/彭祖/胎神展示 | ✅ | + +### 2.3 日历 CalendarPage(`/calendar`) + +| # | 功能点 | 状态 | +|---|---|---| +| K1 | 月视图/周视图切换 | ✅ | +| K2 | 年/月切换(MonthYearPicker) | ✅ | +| K3 | 格子显示:节日/节气缩写、农历、周末/今日高亮、收藏星标、运势点 | ✅ | +| K4 | 点击日期 → 日详情页 | ✅ | +| K5 | 手势滑动切换月份 | ✅ | +| K6 | 周起始(周一/周日)设置生效 | ✅ 已修复(BUG-02) | +| K7 | 非本月时显示"回到今天"浮动按钮,点击返回当月 | ✅ 已修复(BUG-09) | + +### 2.4 日详情 DayDetailPage(`/calendar/:date`) + +| # | 功能点 | 状态 | +|---|---|---| +| D1 | 四柱、纳音、值星展示 | ✅ | +| D2 | 冲合害煞、宜忌列表 | ✅ | +| D3 | 值神、彭祖百忌、胎神 | ✅ | +| D4 | 12 时辰逐时黄历 | ✅ | +| D5 | 收藏按钮 | ✅ 仅公历收藏 | + +### 2.5 八字 BaziPage(`/bazi`) + +| # | 功能点 | 状态 | +|---|---|---| +| B1 | 出生档案管理(最多 3 个,持久化) | ✅ | +| B2 | 排盘:四柱 + 十神 + 藏干 | ✅ | +| B3 | 五行力量 / 十二长生展示 | ✅ | +| B4 | 起运 / 大运 / 流年 | ✅ | +| B5 | 称骨集成 | ✅ 使用出生年干支(占卜页已同步修正) | +| B6 | 日柱类型安全 | ✅ `solarAdjusted` 字段替代 `(as any)` 补丁 | +| B7 | 八字流派切换(晚子时换日 23点 / 0点换日) | ✅ 设置页可切换,排盘实时生效 | +| B8 | 出生地:全国 34 省级城市真太阳时校正 | ✅ 含跨日处理,受"真太阳时校正"开关控制 | +| B9 | 出生地:海外时区 → 北京时间(UTC+8)换算排盘 | ✅ 含跨日处理 | +| B10 | 神煞展示(22 个常见神煞,吉/凶/中性分组) | ✅ | +| B11 | 大运全量 10 条 + 流年(小运)10 条,均附生克冲合 | ✅ | +| B12 | 大运→流年→流月→流日交互下钻(按年度切换) | ✅ 点选日期跳转日详情 | + +### 2.6 每日运势 DailyFortunePage(`/daily-fortune`) + +| # | 功能点 | 状态 | +|---|---|---| +| F1 | 日期导航查看任意日运势 | ✅ | +| F2 | 无档案时引导创建 | ✅ | +| F3 | 评分等级、宜忌、幸运信息、分项得分 | ✅ | + +### 2.7 占卜 DivinationPage(`/divination`) + +| # | 功能点 | 状态 | +|---|---|---| +| P1 | 今日梅花易数卦象 + 体用分析 | 🟡 算法简化 | +| P2 | 称骨算命输入与结果 | 🟡 年索引用日干支近似 | + +### 2.8 节气 SolarTermsPage(`/solar-terms`) + +| # | 功能点 | 状态 | +|---|---|---| +| S1 | 全年 24 节气列表 | ✅ | + +### 2.9 设置 SettingsPage(`/settings`) + +| # | 功能点 | 状态 | +|---|---|---| +| T1 | 档案管理入口 | ✅ | +| T2 | 农历日期查询(阴阳历转换) | ✅ | +| T3 | 今日天干地支/纳音 | ✅ | +| T4 | 主题切换(亮/暗/跟随系统) | ✅ | +| T5 | 周起始设置 | ✅ 已修复(BUG-02) | +| T6 | 显示开关(农历/节气/假日)、八字来源 | 🟡 字段已持久化但未消费 | +| T7 | 关于:版本号 | ✅ 已统一为 0.1.0(BUG-07) | +| T8 | 真太阳时校正开关(默认开启) | ✅ 关闭后按北京时间排盘 | + +### 2.10 收藏 Bookmarks + +| # | 功能点 | 状态 | +|---|---|---| +| M1 | 收藏/取消收藏日期,日历格星标 | ✅ | +| M2 | 农历周期收藏(每年/月循环) | ⬜ 字段已预留,UI 未提供 | +| M3 | 收藏列表页 | ⬜ 规划中 | + +### 2.11 非功能需求 + +| # | 需求 | 状态 | +|---|---|---| +| N1 | PWA 可安装、离线可用 | ✅ 已构建 | +| N2 | 移动端优先响应式 | ✅ | +| N3 | 暗色模式(跟随系统) | ✅ | +| N4 | 页面懒加载 + 骨架屏 + 错误边界 | ✅ | +| N5 | 自动化测试 | ✅ 2026-08-02 引入 vitest,29 个用例(历法/八字/运势/梅花/称骨/干支) | +| N6 | Lint / 代码规范 | ✅ 2026-08-02 配置 ESLint 9 flat config + typescript-eslint,`pnpm lint` 通过 | diff --git a/web/docs/RETROSPECT.md b/web/docs/RETROSPECT.md new file mode 100644 index 0000000..c548884 --- /dev/null +++ b/web/docs/RETROSPECT.md @@ -0,0 +1,74 @@ +# 开发回顾记录(Retrospect) + +> 会话日期:2026-08-02 ~ 2026-08-03 +> 用途:记录本次会话"做了什么 / 没做什么 / 有疑问的事项",便于事后回顾与决策。 + +## 一、本次完成的工作 + +### 1. 文档体系(docs/) +- 新建 `docs/`:ARCHITECTURE / REQUIREMENTS / PROGRESS / BUGS / CHANGELOG,另加根 README.md +- 修正 AGENTS.md / CLAUDE.md 的过时信息(PWA 状态、页面/Store/API 补全),并持续同步 + +### 2. BUG 修复(BUGS.md 均已标记 ✅) +| ID | 内容 | +|---|---| +| BUG-01 | core 包 CJS 导出:tsup 增加 cjs 产物,`require()` 实测可用 | +| BUG-02 | 周起始设置生效:settings → useCalendar → getMonthCalendar(weekStart) → WeekDayBar | +| BUG-03 | PWA manifest 对齐(lang=zh-CN、theme_color=#FFFBF5) | +| BUG-05 | 配置 ESLint 9 flat config + typescript-eslint,`pnpm lint` 通过(修复 24 处) | +| BUG-07 | 版本号统一 v0.1.0 | +| BUG-08 | getMonthCalendar 死分支移除 | +| BUG-09 | "回到今天"按钮条件恒 false,改为基于 viewDate 判断 | +| BUG-10 | 日详情时辰列表重复 React key(早子/晚子均"子"),改用 ganzhi 作 key | +| TECH-06 | `(as any)._solarAdjusted` → `BaziFullResult.solarAdjusted` 类型化 | + +### 3. 功能深化 +- **八字流派切换**:`birthInfoToBazi` 新增 `ziSect`(晚子时算次日/当日),设置页开关 +- **出生时间选择器**:时/分下拉 + 时辰格可点 +- **出生地全国化 + 海外时区**:34 省级城市(真太阳时校正)+ 海外 UTC-12~+14 换算北京时间,均支持跨日 +- **真太阳时校正开关**(设置页,默认开启) +- **宜忌完整显示**:根因是首页 `slice(0,6)`,引擎本有 22 条宜 +- **历法信息增强**:DayInfo 新增 季节/所处节气/第几天/距下节气/儒略日/佛历年/伊斯兰历(tyme4ts 原生 Hijri) +- **佛教节日**:新增 `getBuddhistFestival`(农历 21 个节日),日详情 🪷 徽章 +- **八字神煞**:新增 `analyzeShensha`(22 个常见神煞),八字页"神煞"卡 +- **大运/流年生克冲合**:新增 `analyzeFortuneGanzhi`(十神/五行生克/合冲害刑/吉凶) +- **大运→流年→流月→流日交互下钻**:新增 `getYearMonths`(节气月);八字页交互浏览器,流日可跳日详情 + +### 4. 质量 +- 单元测试 29 → **50 个**(vitest),覆盖历法/八字/运势/梅花/称骨/干支/五行/神煞/流月/流年 +- 浏览器 E2E 验证(headless Chrome + CDP 脚本,无新增依赖):周起始、回到今天、流派、出生地、太阳时开关、宜忌、历法信息、神煞、大运流年交互 全部实测通过 + +### 5. 工程清理 +- 整理全部文档;删除 node_modules / dist / tsbuildinfo;源码 241M → 764K;打包 164K(/tmp/lunar-source-20260803.tar.gz) + +## 二、未完成 / 待办(详见 PROGRESS.md Backlog) + +| 事项 | 说明 | +|---|---| +| 互卦真算法 | 梅花易数互卦仍为上下卦互换简化(2-4/3-5 爻法未实现) | +| 死代码清理 | useDayDetail/useBazi hooks、AnimatedPanel、CardHeader、CalendarSkeleton/BaziSkeleton、ui store 未用 action、settings 显示开关字段、calendar store weekStart、bookmarks getByDate、utils 未用函数、tailwind-variants 依赖 | +| 显示开关字段 | settings.showLunar/showSolarTerm/showHoliday/eightCharProvider 已持久化但未消费(其中 eightCharProvider 已被 ziHourSect 替换) | +| 农历周期收藏 | bookmarks 的 isLunar 字段已预留,UI 未提供;收藏列表页未做 | +| 午时假设 | calculateDailyFortune 当日八字固定取午时(TECH-01) | +| 称骨极端值 | 超出 2两1钱~7两2钱范围的总重仍取"最近值"(TECH-04 残余) | +| 紫微斗数 | 未做(大工程,用户同意先做神煞) | +| web 端自动化测试 | 仅 core 有单测;web 无测试框架 | +| git | 项目未初始化 git 仓库,无版本管理与回滚能力 | + +## 三、有疑问 / 待确认事项 + +1. **称骨年表存在两套网络变体**:本实现采用主流版本(算准网/网易/sunfinelife 三家一致:丙子16钱、戊子15钱等),但另一套变体(丙子19钱、戊子12钱等)也在流通。若用户希望换另一套,改 `boneWeight.ts` 的 `YEAR_WEIGHTS` 即可。 +2. **天厨贵人查法有版本差异**:本实现采用主流版(甲巳、乙午、丙巳、丁午、戊申、己酉、庚亥、辛子、壬寅、癸卯);传统版(丙寅、丁酉、戊申、己未、庚亥、辛戌、壬卯、癸子)也有出处。代码注释已注明。 +3. **tyme4ts 十神方向语义**:实测 `A.getTenStar(B)` 返回"B 以 A 为日主"的十神。`dailyMatch.ts` 中的用法方向(userPillar vs dayPillar 同位置比较)语义上存疑,但**未改动**(避免影响每日运势结果);新写的 `fortuneLuck` 已按正确方向实现。需确认 dailyMatch 是否也要调整。 +4. **佛历年**:按公历 + 543(泰国惯例);bmcx 示例 2570 对应的是其他年份。 +5. **佛教节日表**:21 个为通行版本,个别日期(如腊月廿九华严菩萨圣诞)在不同资料有出入。 +6. **默认出生地变化**:默认从"120°E 不校正"改为"北京 116.4°E(-14 分钟真太阳校正)",临界时辰的结果会变。太阳时校正开关默认开启——用户接受度未知,如需要可默认关闭。 +7. **BUG-04(HomePage InfoCard highlight)**:`highlight` 传布尔但被当作样式类 `border-primary/30` 处理,语义不清,需确认意图(是要"高亮边框"还是别的)。 +8. **起运前年份 UX**:大运流年浏览器中,出生当年(起运前)不在任何大运区间,自动落到第一柱大运(如 2026 年出生 → 从 2028 起显示)。是否要显示"起运前"年份待定。 +9. **pnpm-workspace.yaml `allowBuilds`**:非 pnpm 标准字段(应为 onlyBuiltDependencies),疑似无效但无害,未改。 +10. **打包清理**:已删除 node_modules/dist,开发前需 `pnpm install`。 + +## 四、验证方式备忘 + +- E2E 通过 headless Chrome(Playwright 缓存目录的 chromium headless shell)+ CDP 协议 Node 脚本驱动,未引入 Playwright npm 依赖;脚本存于 `/tmp/lunar-*.mjs`(会话临时文件,未入库)。 +- 所有功能均以"单测 + 浏览器实测"双重验证;仅 UI 纯样式类改动(如时间选择器)以 tsc 构建验证为主。 diff --git a/web/eslint.config.js b/web/eslint.config.js new file mode 100644 index 0000000..e24517f --- /dev/null +++ b/web/eslint.config.js @@ -0,0 +1,11 @@ +import tseslint from 'typescript-eslint'; + +export default tseslint.config( + { ignores: ['**/dist/**', '**/node_modules/**', '**/*.tsbuildinfo'] }, + tseslint.configs.recommended, + { + rules: { + '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }], + }, + }, +); diff --git a/web/package.json b/web/package.json new file mode 100644 index 0000000..c52cc15 --- /dev/null +++ b/web/package.json @@ -0,0 +1,24 @@ +{ + "name": "lunar-calendar", + "version": "0.1.0", + "private": true, + "type": "module", + "description": "万年历 - Chinese Almanac Calendar App", + "scripts": { + "dev": "pnpm --filter @lunar/web dev", + "build": "pnpm --filter @lunar/core build && pnpm --filter @lunar/web build", + "preview": "pnpm --filter @lunar/web preview", + "test": "pnpm -r test", + "lint": "pnpm -r lint", + "clean": "pnpm -r clean" + }, + "engines": { + "node": ">=18", + "pnpm": ">=9" + }, + "devDependencies": { + "@eslint/js": "^10.0.1", + "eslint": "^10.8.0", + "typescript-eslint": "^8.65.0" + } +} diff --git a/web/packages/core/package.json b/web/packages/core/package.json new file mode 100644 index 0000000..4edd40f --- /dev/null +++ b/web/packages/core/package.json @@ -0,0 +1,34 @@ +{ + "name": "@lunar/core", + "version": "0.1.0", + "private": true, + "description": "Core calculation engine for lunar calendar app", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } + }, + "files": [ + "dist" + ], + "scripts": { + "dev": "tsup --watch", + "build": "tsup", + "test": "vitest run", + "lint": "eslint .", + "clean": "rm -rf dist" + }, + "dependencies": { + "tyme4ts": "^1.5.1" + }, + "devDependencies": { + "tsup": "^8.4.0", + "typescript": "^5.7.0", + "vitest": "^4.1.10" + } +} diff --git a/web/packages/core/src/__tests__/bazi.test.ts b/web/packages/core/src/__tests__/bazi.test.ts new file mode 100644 index 0000000..0c53e44 --- /dev/null +++ b/web/packages/core/src/__tests__/bazi.test.ts @@ -0,0 +1,51 @@ +import { describe, it, expect } from 'vitest'; +import { birthInfoToBazi } from '../index'; + +const GANZHI = /^[甲乙丙丁戊己庚辛壬癸][子丑寅卯辰巳午未申酉戌亥]$/; + +describe('bazi transformer', () => { + const bazi = birthInfoToBazi({ year: 1990, month: 5, day: 15, hour: 12, minute: 0, gender: 'male' }); + + it('produces four pillars with valid ganzhi', () => { + expect(bazi.eightChar.yearPillar.ganzhi).toMatch(GANZHI); + expect(bazi.eightChar.monthPillar.ganzhi).toMatch(GANZHI); + expect(bazi.eightChar.dayPillar.ganzhi).toMatch(GANZHI); + expect(bazi.eightChar.hourPillar.ganzhi).toMatch(GANZHI); + }); + + it('exposes day master info', () => { + expect(bazi.eightChar.dayMasterStem).toMatch(/^[甲乙丙丁戊己庚辛壬癸]$/); + expect(['木', '火', '土', '金', '水']).toContain(bazi.eightChar.dayMasterElement); + }); + + it('returns 10 decade fortunes and 10 annual fortunes', () => { + expect(bazi.decadeFortunes).toHaveLength(10); + expect(bazi.annualFortunes).toHaveLength(10); + expect(bazi.decadeFortunes[0].ganzhi).toMatch(GANZHI); + }); + + it('returns child limit with non-negative start age', () => { + expect(bazi.childLimit.startAge).toBeGreaterThanOrEqual(0); + expect(bazi.childLimit.forward).toBeTypeOf('boolean'); + }); + + it('includes hidden stems with ten stars', () => { + const yearHs = bazi.eightChar.yearPillar.hideStems; + expect(yearHs.length).toBeGreaterThan(0); + expect(yearHs[0].stem).toMatch(/^[甲乙丙丁戊己庚辛壬癸]$/); + }); + + it('default sect: late zi (23:30) rolls to next day', () => { + const late = birthInfoToBazi({ year: 2026, month: 8, day: 3, hour: 23, minute: 30, gender: 'male' }); + expect(late.eightChar.dayPillar.ganzhi).toBe('庚戌'); + expect(late.eightChar.hourPillar.ganzhi).toBe('丙子'); + }); + + it('earlyZiSameDay sect: late zi (23:30) keeps the current day', () => { + const early = birthInfoToBazi({ + year: 2026, month: 8, day: 3, hour: 23, minute: 30, gender: 'male', ziSect: 'earlyZiSameDay', + }); + expect(early.eightChar.dayPillar.ganzhi).toBe('己酉'); + expect(early.eightChar.hourPillar.ganzhi).toBe('甲子'); + }); +}); diff --git a/web/packages/core/src/__tests__/boneWeight.test.ts b/web/packages/core/src/__tests__/boneWeight.test.ts new file mode 100644 index 0000000..7d2006e --- /dev/null +++ b/web/packages/core/src/__tests__/boneWeight.test.ts @@ -0,0 +1,68 @@ +import { describe, it, expect } from 'vitest'; +import { calculateBoneWeight } from '../index'; + +describe('bone weight', () => { + it('sums weights correctly for 甲子年正月初一子时', () => { + const r = calculateBoneWeight(0, 1, 1, 0); + expect(r.yearWeight).toBe(12); + expect(r.monthWeight).toBe(6); + expect(r.dayWeight).toBe(5); + expect(r.hourWeight).toBe(16); + expect(r.totalWeight).toBe(39); + expect(r.totalLiang).toBe(3); + expect(r.totalQian).toBe(9); + }); + + it('returns a non-empty interpretation with a valid fortune grade', () => { + const r = calculateBoneWeight(12, 6, 15, 6); + expect(r.interpretation.length).toBeGreaterThan(0); + expect(['上上', '上', '中上', '中', '中下', '下']).toContain(r.fortune); + }); + + it('falls back to the nearest interpretation for unknown totals', () => { + // 4 + 9 + 9 + 9 = 31 → exact match exists; use an out-of-range input + const r = calculateBoneWeight(0, 1, 1, 11); // 12 + 6 + 5 + 6 = 29 + expect(r.totalWeight).toBe(29); + expect(r.interpretation.length).toBeGreaterThan(0); + }); + + it('year table matches the mainstream 称骨年表', () => { + const cases: [number, number][] = [ + [0, 12], // 甲子 + [7, 8], // 辛未 + [16, 12], // 庚辰 + [17, 6], // 辛巳 + [18, 8], // 壬午 + [21, 15], // 乙酉 + [51, 8], // 乙卯 + [54, 19], // 戊午 + ]; + for (const [idx, wt] of cases) { + const r = calculateBoneWeight(idx, 1, 1, 0); + expect(r.yearWeight, `year index ${idx}`).toBe(wt); + } + }); + + it('day table: 初五 is 1两6钱', () => { + const r = calculateBoneWeight(0, 1, 5, 0); + expect(r.dayWeight).toBe(16); + }); + + it('previously missing poems (5两8钱~7两1钱) resolve exactly, not by nearest match', () => { + const cases: [number, number, number, number, number, string][] = [ + [18, 6, 26, 5, 58, '雁塔题名'], + [15, 6, 8, 7, 59, '甲第之中'], + [24, 3, 18, 6, 61, '金榜客'], + [42, 6, 26, 5, 63, '定中高科'], + [54, 9, 18, 6, 65, '安邦'], + [54, 6, 8, 5, 67, '田园家业'], + [54, 6, 18, 5, 69, '前禄星'], + [54, 3, 26, 5, 71, '公侯卿相'], + ]; + for (const [y, m, d, h, total, phrase] of cases) { + const r = calculateBoneWeight(y, m, d, h); + expect(r.totalWeight, `expected total ${total}`).toBe(total); + expect(r.interpretation, `poem for ${total}`).toContain(phrase); + } + }); +}); diff --git a/web/packages/core/src/__tests__/day.test.ts b/web/packages/core/src/__tests__/day.test.ts new file mode 100644 index 0000000..6330ad8 --- /dev/null +++ b/web/packages/core/src/__tests__/day.test.ts @@ -0,0 +1,62 @@ +import { describe, it, expect } from 'vitest'; +import { getDayInfo, getMonthCalendar, getTodayInfo } from '../index'; + +describe('day transformers', () => { + it('returns correct lunar info for 2024-02-10 (春节正月初一)', () => { + const di = getDayInfo(2024, 2, 10); + expect(di.solarDate).toBe('2024-02-10'); + expect(di.lunarMonth).toBe(1); + expect(di.lunarDay).toBe(1); + expect(di.lunarDayName).toBe('初一'); + expect(di.lunarYearGanzhi).toMatch(/^[甲乙丙丁戊己庚辛壬癸][子丑寅卯辰巳午未申酉戌亥]$/); + expect(di.weekDayIndex).toBe(6); + expect(di.isWeekend).toBe(true); + }); + + it('getTodayInfo returns today', () => { + const now = new Date(); + const di = getTodayInfo(); + expect(di.solarYear).toBe(now.getFullYear()); + expect(di.solarMonth).toBe(now.getMonth() + 1); + expect(di.solarDay).toBe(now.getDate()); + expect(di.isToday).toBe(true); + }); + + it('getMonthCalendar respects weekStart', () => { + // 2024-02-01 is a Thursday + const sunFirst = getMonthCalendar(2024, 2, 0); + const monFirst = getMonthCalendar(2024, 2, 1); + + for (const row of sunFirst) expect(row).toHaveLength(7); + for (const row of monFirst) expect(row).toHaveLength(7); + + expect(sunFirst[0][0].weekDayIndex).toBe(0); // Sunday first + expect(monFirst[0][0].weekDayIndex).toBe(1); // Monday first + + // Both grids must contain the 1st of the month + const flatSun = sunFirst.flat(); + const flatMon = monFirst.flat(); + expect(flatSun.some(d => d.solarDate === '2024-02-01')).toBe(true); + expect(flatMon.some(d => d.solarDate === '2024-02-01')).toBe(true); + }); + + it('provides season, term progress, julian, buddhist era and hijri date', () => { + const di = getDayInfo(2026, 8, 3); // 大暑期间,农历六月廿一 + expect(di.season).toBe('夏季'); + expect(di.termDayIndex).toBeGreaterThan(0); + expect(di.nextSolarTerm).toBe('立秋'); + expect(di.daysToNextTerm).toBe(4); + expect(di.julianDay).toBeGreaterThan(2450000); + expect(di.buddhistYear).toBe(2026 + 543); + expect(di.hijriDate).toMatch(/^\d{4}年\d{2}月\d{2}日$/); + }); + + it('marks Buddhist festivals by lunar date', () => { + // 2024-05-15 is 佛诞(浴佛节)农历四月初八 + const di = getDayInfo(2024, 5, 15); + expect(di.lunarMonth).toBe(4); + expect(di.lunarDay).toBe(8); + expect(di.buddhistFestival).toBe('释迦牟尼佛圣诞(浴佛节)'); + }); +}); + diff --git a/web/packages/core/src/__tests__/elementStrength.test.ts b/web/packages/core/src/__tests__/elementStrength.test.ts new file mode 100644 index 0000000..fdf8e46 --- /dev/null +++ b/web/packages/core/src/__tests__/elementStrength.test.ts @@ -0,0 +1,22 @@ +import { describe, it, expect } from 'vitest'; +import { analyzeElementBalance, birthInfoToBazi } from '../index'; + +describe('element balance', () => { + const eightChar = birthInfoToBazi({ + year: 1990, month: 5, day: 15, hour: 12, minute: 0, gender: 'male', + }).eightChar; + + const profile = analyzeElementBalance(eightChar); + + it('has positive total that matches sum of parts', () => { + expect(profile.total).toBeGreaterThan(0); + expect(profile.wood + profile.fire + profile.earth + profile.metal + profile.water) + .toBeCloseTo(profile.total); + }); + + it('identifies dominant and weakest elements', () => { + expect(['木', '火', '土', '金', '水']).toContain(profile.dominant); + expect(['木', '火', '土', '金', '水']).toContain(profile.weakest); + expect(profile.isBalanced).toBeTypeOf('boolean'); + }); +}); diff --git a/web/packages/core/src/__tests__/fortune.test.ts b/web/packages/core/src/__tests__/fortune.test.ts new file mode 100644 index 0000000..7fe6b85 --- /dev/null +++ b/web/packages/core/src/__tests__/fortune.test.ts @@ -0,0 +1,34 @@ +import { describe, it, expect } from 'vitest'; +import { calculateDailyFortune, birthInfoToBazi } from '../index'; + +const SCORE_LEVELS = ['great', 'good', 'fair', 'poor', 'bad'] as const; + +describe('daily fortune', () => { + const eightChar = birthInfoToBazi({ + year: 1990, month: 5, day: 15, hour: 12, minute: 0, gender: 'female', + }).eightChar; + + const fortune = calculateDailyFortune(eightChar, new Date(2026, 7, 2)); + + it('returns bounded score and valid level', () => { + expect(fortune.overallScore).toBeGreaterThanOrEqual(-100); + expect(fortune.overallScore).toBeLessThanOrEqual(100); + expect(SCORE_LEVELS).toContain(fortune.scoreLevel); + }); + + it('evaluates all four pillars', () => { + expect(fortune.pillarRelationships).toHaveLength(4); + expect(fortune.pillarRelationships.map(r => r.pillar)).toEqual(['year', 'month', 'day', 'hour']); + }); + + it('returns the requested date', () => { + expect(fortune.date).toBe('2026-08-02'); + }); + + it('produces suggestions and category scores', () => { + expect(fortune.suggestions.length).toBeGreaterThan(0); + expect(fortune.luckyAspects.length).toBeGreaterThan(0); + const { love, career, wealth, health } = fortune.categoryScores; + expect([love, career, wealth, health].every(v => v >= -100 && v <= 100)).toBe(true); + }); +}); diff --git a/web/packages/core/src/__tests__/fortuneLuck.test.ts b/web/packages/core/src/__tests__/fortuneLuck.test.ts new file mode 100644 index 0000000..b5bc6f2 --- /dev/null +++ b/web/packages/core/src/__tests__/fortuneLuck.test.ts @@ -0,0 +1,40 @@ +import { describe, it, expect } from 'vitest'; +import { analyzeFortuneGanzhi, birthInfoToBazi } from '../index'; + +describe('fortune luck (大运/流年 vs 日主)', () => { + // 2026-08-03 12:00: 日干己、日支酉 + const bazi = birthInfoToBazi({ year: 2026, month: 8, day: 3, hour: 12, minute: 0, gender: 'male' }); + const dayStem = bazi.eightChar.dayPillar.heavenStem; + const dayBranch = bazi.eightChar.dayPillar.earthBranch; + + it('甲子 vs 己酉: 正官 + 天干合 + 克我 → 吉', () => { + const r = analyzeFortuneGanzhi('甲子', dayStem, dayBranch); + expect(r.tenStar).toBe('正官'); + expect(r.stemCombine).toBe(true); + expect(r.elementRelation).toBe('克我'); + expect(r.level).toBe('吉'); + }); + + it('乙酉 vs 己酉: 七杀 + 自刑 → 凶', () => { + const r = analyzeFortuneGanzhi('乙酉', dayStem, dayBranch); + expect(r.tenStar).toBe('七杀'); + expect(r.branchPunish).toBe(true); // 酉酉自刑 + expect(r.level).toBe('凶'); + }); + + it('丙子 vs 己未: 正印 + 子未六害', () => { + const r = analyzeFortuneGanzhi('丙子', '己', '未'); + expect(r.tenStar).toBe('正印'); + expect(r.branchHarm).toBe(true); + }); + + it('all decade fortunes produce valid luck info', () => { + for (const df of bazi.decadeFortunes) { + const r = analyzeFortuneGanzhi(df.ganzhi, dayStem, dayBranch); + expect(['比和', '生我', '我生', '克我', '我克']).toContain(r.elementRelation); + expect(['吉', '凶', '平']).toContain(r.level); + expect(r.score).toBeGreaterThanOrEqual(-10); + expect(r.score).toBeLessThanOrEqual(10); + } + }); +}); diff --git a/web/packages/core/src/__tests__/plumBlossom.test.ts b/web/packages/core/src/__tests__/plumBlossom.test.ts new file mode 100644 index 0000000..4d8b37a --- /dev/null +++ b/web/packages/core/src/__tests__/plumBlossom.test.ts @@ -0,0 +1,47 @@ +import { describe, it, expect } from 'vitest'; +import fs from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { calculatePlumBlossom } from '../index'; + +describe('plum blossom', () => { + it('produces expected hexagram for known input (2026-08-02 12:00)', () => { + const r = calculatePlumBlossom(2026, 8, 2, 12); + expect(r.upperTrigram.name).toBe('巽'); + expect(r.lowerTrigram.name).toBe('兑'); + expect(r.originalHexagram.name).toBe('风泽中孚'); + expect(r.originalHexagram.changingLine).toBe(4); + expect(r.relationship).toBeTruthy(); + }); + + it('is deterministic', () => { + const a = calculatePlumBlossom(2024, 3, 5, 8); + const b = calculatePlumBlossom(2024, 3, 5, 8); + expect(a).toEqual(b); + }); + + it('dataset covers all 64 hexagram combinations', () => { + const srcFile = fileURLToPath(new URL('../calculators/plumBlossom.ts', import.meta.url)); + const src = fs.readFileSync(srcFile, 'utf8'); + const keys = [...src.matchAll(/'(\d,\d)':/g)].map(m => m[1]); + + expect(keys).toHaveLength(64); + expect(new Set(keys).size).toBe(64); + for (let upper = 1; upper <= 8; upper++) { + for (let lower = 1; lower <= 8; lower++) { + expect(keys).toContain(`${upper},${lower}`); + } + } + }); + + it('never falls back to a placeholder across a broad date grid', () => { + for (let y = 2000; y <= 2030; y++) { + for (let m = 1; m <= 12; m++) { + for (let d = 1; d <= 28; d += 7) { + const r = calculatePlumBlossom(y, m, d, 12); + // Real hexagram names are 3+ chars; fallback names are 2-char concatenations + expect(r.originalHexagram.name.length).toBeGreaterThan(2); + } + } + } + }); +}); diff --git a/web/packages/core/src/__tests__/relationship.test.ts b/web/packages/core/src/__tests__/relationship.test.ts new file mode 100644 index 0000000..e22c282 --- /dev/null +++ b/web/packages/core/src/__tests__/relationship.test.ts @@ -0,0 +1,45 @@ +import { describe, it, expect } from 'vitest'; +import { + getBranchRelationship, + getTenStarRelationship, + checkStemCombine, + checkStemOpposite, +} from '../index'; + +describe('relationship calculator', () => { + it('detects 六合 (子丑合)', () => { + expect(getBranchRelationship('子', '丑').combine).toBe(true); + expect(getBranchRelationship('子', '午').combine).toBe(false); + }); + + it('detects 六冲 (子午冲)', () => { + expect(getBranchRelationship('子', '午').opposite).toBe(true); + }); + + it('detects 三合 (申子辰水局)', () => { + const rel = getBranchRelationship('申', '辰'); + expect(rel.threeCombine).toBe(true); + expect(rel.formation).toBe('水局'); + }); + + it('detects 六害 (子未害)', () => { + expect(getBranchRelationship('子', '未').harm).toBe(true); + }); + + it('detects 相刑 (寅巳无恩之刑)', () => { + expect(getBranchRelationship('寅', '巳').punish).toBe(true); + }); + + it('detects 天干合 (甲己合)', () => { + expect(checkStemCombine('甲', '己')).toBe(true); + expect(checkStemCombine('甲', '乙')).toBe(false); + }); + + it('detects 天干冲 (甲庚冲)', () => { + expect(checkStemOpposite('甲', '庚')).toBe(true); + }); + + it('computes ten star for a day master (甲日主见戊土 → 偏财)', () => { + expect(getTenStarRelationship('甲', '戊')).toContain('财'); + }); +}); diff --git a/web/packages/core/src/__tests__/shensha.test.ts b/web/packages/core/src/__tests__/shensha.test.ts new file mode 100644 index 0000000..6b8f227 --- /dev/null +++ b/web/packages/core/src/__tests__/shensha.test.ts @@ -0,0 +1,55 @@ +import { describe, it, expect } from 'vitest'; +import { analyzeShensha, birthInfoToBazi } from '../index'; + +describe('shensha', () => { + // 2026-08-03 12:00: 年柱丙午、月柱乙未、日柱己酉、时柱庚午 + const bazi = birthInfoToBazi({ year: 2026, month: 8, day: 3, hour: 12, minute: 0, gender: 'male' }); + + it('finds 天乙贵人 by year stem 丙 → 亥酉, hit 日支酉', () => { + const s = analyzeShensha(bazi.eightChar); + const t = s.find(x => x.name === '天乙贵人'); + expect(t).toBeDefined(); + expect(t!.foundIn).toContain('日支'); + expect(t!.anchors.some(a => a.includes('年干'))).toBe(true); + }); + + it('finds 文昌贵人 by day stem 己 → 酉, hit 日支酉', () => { + const s = analyzeShensha(bazi.eightChar); + const t = s.find(x => x.name === '文昌贵人'); + expect(t).toBeDefined(); + expect(t!.anchors.some(a => a.includes('日干'))).toBe(true); + }); + + it('finds 禄神 and 羊刃 by day/year stem 己/丙 → 午', () => { + const s = analyzeShensha(bazi.eightChar); + const lu = s.find(x => x.name === '禄神'); + const yang = s.find(x => x.name === '羊刃'); + expect(lu).toBeDefined(); + expect(lu!.foundIn).toEqual(expect.arrayContaining(['年支', '时支'])); + expect(yang).toBeDefined(); + }); + + it('finds 桃花 by day branch 酉 (巳酉丑) → 午', () => { + const s = analyzeShensha(bazi.eightChar); + const t = s.find(x => x.name.startsWith('桃花')); + expect(t).toBeDefined(); + expect(t!.foundIn).toContain('年支'); + }); + + it('finds 将星 and 红鸾', () => { + const s = analyzeShensha(bazi.eightChar); + expect(s.find(x => x.name === '将星')).toBeDefined(); // 年支午(寅午戌) → 午 + expect(s.find(x => x.name === '红鸾')).toBeDefined(); // 年支午 → 酉,日支酉 + }); + + it('does not flag 魁罡/阴差阳错 for day 己酉', () => { + const s = analyzeShensha(bazi.eightChar); + expect(s.find(x => x.name === '魁罡')).toBeUndefined(); + expect(s.find(x => x.name === '阴差阳错')).toBeUndefined(); + }); + + it('all stem rules cover all 10 stems and branch rules cover 12 branches', () => { + const s = analyzeShensha(bazi.eightChar); + expect(s.length).toBeGreaterThan(0); + }); +}); diff --git a/web/packages/core/src/__tests__/yearMonths.test.ts b/web/packages/core/src/__tests__/yearMonths.test.ts new file mode 100644 index 0000000..4bfc470 --- /dev/null +++ b/web/packages/core/src/__tests__/yearMonths.test.ts @@ -0,0 +1,26 @@ +import { describe, it, expect } from 'vitest'; +import { getYearMonths } from '../index'; + +describe('getYearMonths (流月)', () => { + it('returns 12 months starting from 立春', () => { + const months = getYearMonths(2026); + expect(months).toHaveLength(12); + expect(months[0].name).toBe('正月'); + expect(months[0].startDate).toBe('2026-02-04'); // 立春 2026 + expect(months[0].ganzhi).toBe('庚寅'); // 丙午年 五虎遁 → 寅月庚寅 + expect(months[11].name).toBe('腊月'); + expect(months[11].startDate).toMatch(/^2027-01-/); // 小寒 2027 + }); + + it('endDate is the day before the next 节', () => { + const months = getYearMonths(2026); + expect(months[0].endDate).toBe('2026-03-04'); // 惊蛰 2026-03-05 前一天 + expect(months[1].ganzhi).toBe('辛卯'); // 惊蛰起卯月 + }); + + it('all month ganzhi match the 五虎遁 sequence', () => { + const months = getYearMonths(2026); + const seq = ['庚寅', '辛卯', '壬辰', '癸巳', '甲午', '乙未', '丙申', '丁酉', '戊戌', '己亥', '庚子', '辛丑']; + expect(months.map(m => m.ganzhi)).toEqual(seq); + }); +}); diff --git a/web/packages/core/src/calculators/boneWeight.ts b/web/packages/core/src/calculators/boneWeight.ts new file mode 100644 index 0000000..1a0821d --- /dev/null +++ b/web/packages/core/src/calculators/boneWeight.ts @@ -0,0 +1,161 @@ +/** + * 袁天罡称骨算命法 (Bone Weight Fortune Telling) + * Based on year/month/day/hour pillars to calculate total "bone weight" + * Each unit = 钱 (1两 = 10钱) + */ + +export interface BoneWeightResult { + yearWeight: number; // in 钱 + monthWeight: number; + dayWeight: number; + hourWeight: number; + totalWeight: number; // in 钱 + totalLiang: number; // 两 + totalQian: number; // 钱 + interpretation: string; // The fate poem/interpretation + fortune: '上上' | '上' | '中上' | '中' | '中下' | '下'; +} + +// Year bone weight table (by 干支 year stem-branch) +// 主流称骨年表(百度百科/网易/算准网等通行版本):year % 60 → weight in 钱 +const YEAR_WEIGHTS: Record = { + 0: 12, 1: 9, 2: 6, 3: 7, 4: 12, 5: 5, 6: 9, 7: 8, 8: 7, 9: 8, + 10: 15, 11: 9, 12: 16, 13: 8, 14: 8, 15: 19, 16: 12, 17: 6, 18: 8, 19: 7, + 20: 5, 21: 15, 22: 6, 23: 16, 24: 15, 25: 7, 26: 9, 27: 12, 28: 10, 29: 7, + 30: 15, 31: 6, 32: 5, 33: 14, 34: 14, 35: 9, 36: 7, 37: 7, 38: 9, 39: 12, + 40: 8, 41: 7, 42: 13, 43: 5, 44: 14, 45: 5, 46: 9, 47: 17, 48: 5, 49: 7, + 50: 12, 51: 8, 52: 8, 53: 6, 54: 19, 55: 6, 56: 8, 57: 16, 58: 10, 59: 6, +}; + +// Month bone weight (lunar month 1-12) +const MONTH_WEIGHTS: Record = { + 1: 6, 2: 7, 3: 18, 4: 9, 5: 5, 6: 16, + 7: 9, 8: 15, 9: 18, 10: 8, 11: 9, 12: 5, +}; + +// Day bone weight (lunar day 1-30) +const DAY_WEIGHTS: Record = { + 1: 5, 2: 10, 3: 8, 4: 15, 5: 16, 6: 15, 7: 8, 8: 16, 9: 8, 10: 16, + 11: 9, 12: 17, 13: 8, 14: 17, 15: 10, 16: 8, 17: 9, 18: 18, 19: 5, 20: 15, + 21: 10, 22: 9, 23: 8, 24: 9, 25: 15, 26: 18, 27: 7, 28: 8, 29: 16, 30: 6, +}; + +// Hour bone weight (时辰, 地支 index 0-11) +const HOUR_WEIGHTS: Record = { + 0: 16, // 子时 23-01 + 1: 6, // 丑时 01-03 + 2: 7, // 寅时 03-05 + 3: 10, // 卯时 05-07 + 4: 9, // 辰时 07-09 + 5: 16, // 巳时 09-11 + 6: 10, // 午时 11-13 + 7: 8, // 未时 13-15 + 8: 8, // 申时 15-17 + 9: 9, // 酉时 17-19 + 10: 6, // 戌时 19-21 + 11: 6, // 亥时 21-23 +}; + +// Interpretation for each total weight (in 钱) +const INTERPRETATIONS: Record = { + 21: { text: '短命非业谓大凶,平生灾难事重重,凶祸频临陷逆境,终世困苦事不成。', fortune: '下' }, + 22: { text: '身寒骨冷苦伶仃,此命推来行乞人,劳劳碌碌无度日,终年打拱过平生。', fortune: '下' }, + 23: { text: '此命推来骨格轻,求谋作事事难成,妻儿兄弟应难许,别处他乡作散人。', fortune: '下' }, + 24: { text: '此命推来福禄无,门庭困苦总难荣,六亲骨肉皆无靠,流浪他乡作老翁。', fortune: '下' }, + 25: { text: '此命推来祖业微,门庭营度似稀奇,六亲骨肉如冰炭,一世勤劳自把持。', fortune: '中下' }, + 26: { text: '平生衣禄苦中求,独自营谋事不休,离祖出门宜早计,晚来衣禄自无休。', fortune: '中下' }, + 27: { text: '一生作事少商量,难靠祖宗怎主张,独马单枪空做去,早年晚岁总无长。', fortune: '中下' }, + 28: { text: '一生行事似飘蓬,祖宗产业在梦中,若不过房改名姓,也当移徒二三通。', fortune: '中下' }, + 29: { text: '初年运限未曾亨,纵有功名在后成,须过四旬才可立,移居改姓始为良。', fortune: '中' }, + 30: { text: '劳劳碌碌苦中求,东奔西走何日休,若使终身勤与俭,老来稍可免忧愁。', fortune: '中' }, + 31: { text: '忙忙碌碌苦中求,何日云开见日头,难得祖基家可立,中年衣食渐无忧。', fortune: '中' }, + 32: { text: '初年运蹇事难谋,渐有财源如水流,到得中年衣食旺,那时名利一齐收。', fortune: '中上' }, + 33: { text: '早年做事事难成,百年勤劳枉费心,半世自如流水去,后来运到始得金。', fortune: '中上' }, + 34: { text: '此命福气果如何,僧道门中衣禄多,离祖出家方为妙,朝晚拜佛念弥陀。', fortune: '中上' }, + 35: { text: '生平福量不周全,祖业根基觉少传,营事生涯宜守旧,时来衣食胜从前。', fortune: '中' }, + 36: { text: '不须劳碌过平生,独自成家福不轻,早有福星常照命,任君行去百般成。', fortune: '上' }, + 37: { text: '此命般般事不成,弟兄少力自孤行,虽然祖业须微有,来得明时去不明。', fortune: '中下' }, + 38: { text: '一身骨肉最清高,早入簧门姓氏标,待到年将三十六,蓝衫脱去换红袍。', fortune: '上' }, + 39: { text: '此命终身运不通,劳劳作事尽皆空,苦心竭力成家计,到得那时在梦中。', fortune: '中下' }, + 40: { text: '平生衣禄是绵长,件件心中自主张,前面风霜多受过,后来必定享安康。', fortune: '上' }, + 41: { text: '此命推来自不同,为人能干异凡庸,中年还有逍遥福,不比前时运未通。', fortune: '上' }, + 42: { text: '得宽怀处且宽怀,何用双眉皱不开,若使中年命运济,那时名利一齐来。', fortune: '上' }, + 43: { text: '为人心性最聪明,作事轩昂近贵人,衣禄一生天注定,不须劳碌是丰亨。', fortune: '上上' }, + 44: { text: '万事由天莫苦求,须知福碌赖人修,当年财帛难如意,晚景欣然便不忧。', fortune: '中上' }, + 45: { text: '名利推求竟若何,前番辛苦后奔波,命中难养男和女,骨肉扶持也不多。', fortune: '中' }, + 46: { text: '东西南北尽皆通,出姓移居更觉隆,衣禄无穷无数定,中年晚景一般同。', fortune: '上' }, + 47: { text: '此命推求旺末年,妻荣子贵自怡然,平生原有滔滔福,可卜财源若水泉。', fortune: '上' }, + 48: { text: '初年运道未曾通,几许蹉跎命亦穷,兄弟六亲无依靠,一生事业晚来整。', fortune: '中' }, + 49: { text: '此命推来福不轻,自成自立显门庭,从来富贵人钦敬,使婢差奴过一生。', fortune: '上' }, + 50: { text: '为利为名终日劳,中年福禄也多遭,老来自有财星照,不比前番目下高。', fortune: '上' }, + 51: { text: '一世荣华事事通,不须劳碌自亨通,兄弟叔侄皆如意,家业成时福禄宏。', fortune: '上' }, + 52: { text: '一世亨通事事能,不须劳苦自然宁,宗族有光欣喜甚,家产丰盈自称心。', fortune: '上' }, + 53: { text: '此格推来福泽宏,兴家立业在其中,一生衣食安排定,却是人间一福翁。', fortune: '上' }, + 54: { text: '此命推来厚且清,诗书满腹看功成,丰衣足食自然稳,正是人间有福人。', fortune: '上上' }, + 55: { text: '走马扬鞭争利名,少年作事费筹论,一朝福禄源源至,富贵荣华显六亲。', fortune: '上' }, + 56: { text: '此格推来礼义通,一身福禄用无穷,甜酸苦辣皆尝过,滚滚财源稳且丰。', fortune: '上' }, + 57: { text: '福禄丰盈万事全,一身荣耀乐天年,名扬威震人争羡,此世逍遥宛似仙。', fortune: '上上' }, + 58: { text: '平生福禄自然来,名利双全福禄偕,雁塔题名为贵客,紫袍玉带走金阶。', fortune: '上' }, + 59: { text: '细推此格妙且清,必定财高礼义通,甲第之中应有分,扬鞭走马显威荣。', fortune: '上' }, + 60: { text: '一朝金榜快题名,显祖荣宗大器成,衣禄定然无欠缺,田园财帛更丰盈。', fortune: '上上' }, + 61: { text: '不作朝中金榜客,定为世上一财翁,聪明天赋经书熟,名显高科自是荣。', fortune: '上上' }, + 62: { text: '此命生来福不穷,读书必定显亲宗,紫衣玉带为卿相,富贵荣华孰与同。', fortune: '上上' }, + 63: { text: '命主为官福禄长,得来富贵定非常,名题雁塔传金榜,定中高科天下扬。', fortune: '上上' }, + 64: { text: '此命生成福不轻,读书必定有功名,果然富贵前生定,一世荣华事事成。', fortune: '上上' }, + 65: { text: '细推此命福不轻,安国安邦极品人,文纷雕梁徽富贵,威声照耀四方闻。', fortune: '上上' }, + 66: { text: '此命推来福且宏,荣华富贵自然通,命中注定衣禄足,一世亨通稳且丰。', fortune: '上上' }, + 67: { text: '此命生来福自宏,田园家业最高隆,平生衣禄丰盈足,一世荣华万事通。', fortune: '上上' }, + 68: { text: '富贵荣华莫强求,强求不出反成羞,有福之人还自至,无福之人反成忧。', fortune: '中' }, + 69: { text: '君是人间前禄星,一生富贵众人钦,纵然福禄由天定,安享荣华过一生。', fortune: '上上' }, + 70: { text: '此命推来福禄宏,不须劳碌过平生,妻儿和顺皆如意,家道兴隆福自成。', fortune: '上' }, + 71: { text: '此命生来大不同,公侯卿相在其中,一生自有逍遥福,富贵荣华极品隆。', fortune: '上上' }, + 72: { text: '此命生来福泽长,兴家立业有祯祥,一生自有逍遥福,富贵荣华极品良。', fortune: '上上' }, +}; + +/** Calculate Bone Weight Fortune from lunar calendar data */ +export function calculateBoneWeight( + lunarYearGanzhiIndex: number, // 0-59 六十甲子 index + lunarMonth: number, // 1-12 + lunarDay: number, // 1-30 + earthBranchHourIndex: number, // 0-11 地支时辰 index +): BoneWeightResult { + const yearWt = YEAR_WEIGHTS[lunarYearGanzhiIndex] || 9; + const monthWt = MONTH_WEIGHTS[lunarMonth] || 9; + const dayWt = DAY_WEIGHTS[lunarDay] || 9; + const hourWt = HOUR_WEIGHTS[earthBranchHourIndex] || 9; + + const totalWeight = yearWt + monthWt + dayWt + hourWt; + const totalLiang = Math.floor(totalWeight / 10); + const totalQian = totalWeight % 10; + + // Find closest interpretation + const interpretation = findInterpretation(totalWeight); + + return { + yearWeight: yearWt, + monthWeight: monthWt, + dayWeight: dayWt, + hourWeight: hourWt, + totalWeight, + totalLiang, + totalQian, + interpretation: interpretation.text, + fortune: interpretation.fortune as BoneWeightResult['fortune'], + }; +} + +function findInterpretation(totalQian: number): { text: string; fortune: string } { + // Direct match + if (INTERPRETATIONS[totalQian]) return INTERPRETATIONS[totalQian]; + + // Find closest + const keys = Object.keys(INTERPRETATIONS).map(Number).sort((a, b) => a - b); + let closest = keys[0]; + let minDiff = Math.abs(totalQian - closest); + for (const k of keys) { + const diff = Math.abs(totalQian - k); + if (diff < minDiff) { minDiff = diff; closest = k; } + } + + return INTERPRETATIONS[closest] || { text: '命格推来,自有天定', fortune: '中' }; +} diff --git a/web/packages/core/src/calculators/buddhistDates.ts b/web/packages/core/src/calculators/buddhistDates.ts new file mode 100644 index 0000000..cfd41c3 --- /dev/null +++ b/web/packages/core/src/calculators/buddhistDates.ts @@ -0,0 +1,32 @@ +/** + * 佛教节日(农历)— tyme4ts 不提供,这里维护常用重大佛教日期表 + */ + +const BUDDHIST_FESTIVALS: Record = { + '1-1': '弥勒菩萨圣诞', + '2-8': '释迦牟尼佛出家日', + '2-15': '释迦牟尼佛涅槃日', + '2-19': '观音菩萨圣诞', + '2-21': '普贤菩萨圣诞', + '3-16': '准提菩萨圣诞', + '4-4': '文殊菩萨圣诞', + '4-8': '释迦牟尼佛圣诞(浴佛节)', + '5-3': '伽蓝菩萨圣诞', + '6-3': '韦驮菩萨圣诞', + '6-19': '观音菩萨成道日', + '7-13': '大势至菩萨圣诞', + '7-15': '盂兰盆节(佛欢喜日)', + '7-30': '地藏菩萨圣诞', + '8-22': '燃灯佛圣诞', + '9-19': '观音菩萨出家日', + '9-30': '药师佛圣诞', + '10-5': '达摩祖师诞辰', + '11-17': '阿弥陀佛圣诞', + '12-8': '释迦牟尼佛成道日(腊八)', + '12-29': '华严菩萨圣诞', +}; + +/** Get Buddhist festival name by lunar month/day (闰月不重复过节) */ +export function getBuddhistFestival(lunarMonth: number, lunarDay: number): string | null { + return BUDDHIST_FESTIVALS[`${lunarMonth}-${lunarDay}`] || null; +} diff --git a/web/packages/core/src/calculators/dailyMatch.ts b/web/packages/core/src/calculators/dailyMatch.ts new file mode 100644 index 0000000..9b9348c --- /dev/null +++ b/web/packages/core/src/calculators/dailyMatch.ts @@ -0,0 +1,275 @@ +import { SolarDay } from 'tyme4ts'; +import type { EightCharInfo, PillarInfo } from '../types/bazi'; +import type { DailyFortuneResult, PillarRelationship } from '../types/fortune'; +import { + getBranchRelationship, + getTenStarRelationship, + checkStemCombine, + checkStemOpposite, +} from './relationship'; + +const PILLAR_LABELS: Record = { + year: '年柱', month: '月柱', day: '日柱', hour: '时柱', +}; + +const PILLAR_KEYS = ['year', 'month', 'day', 'hour'] as const; + +export function calculateDailyFortune(userBazi: EightCharInfo, date: Date): DailyFortuneResult { + const solarDay = SolarDay.fromYmd(date.getFullYear(), date.getMonth() + 1, date.getDate()); + const lunarDay = solarDay.getLunarDay(); + const lunarHour = lunarDay.getHours()[6]; + const dayEightChar = lunarHour.getEightChar(); + + const dayPillars: Record = { + year: { ganzhi: dayEightChar.getYear().getName(), stem: dayEightChar.getYear().getHeavenStem().getName(), branch: dayEightChar.getYear().getEarthBranch().getName() }, + month: { ganzhi: dayEightChar.getMonth().getName(), stem: dayEightChar.getMonth().getHeavenStem().getName(), branch: dayEightChar.getMonth().getEarthBranch().getName() }, + day: { ganzhi: dayEightChar.getDay().getName(), stem: dayEightChar.getDay().getHeavenStem().getName(), branch: dayEightChar.getDay().getEarthBranch().getName() }, + hour: { ganzhi: dayEightChar.getHour().getName(), stem: dayEightChar.getHour().getHeavenStem().getName(), branch: dayEightChar.getHour().getEarthBranch().getName() }, + }; + + const userPillars: Record = { + year: userBazi.yearPillar, month: userBazi.monthPillar, day: userBazi.dayPillar, hour: userBazi.hourPillar, + }; + + const relationships: PillarRelationship[] = []; + + for (const key of PILLAR_KEYS) { + const userPillar = userPillars[key]; const dayP = dayPillars[key]; + const stemTenStar = getTenStarRelationship(userPillar.heavenStem, dayP.stem); + const stemCombine = checkStemCombine(userPillar.heavenStem, dayP.stem); + const stemOpposite = checkStemOpposite(userPillar.heavenStem, dayP.stem); + const branchRel = getBranchRelationship(userPillar.earthBranch, dayP.branch); + + let score = 0; + if (stemTenStar) { + const good = ['正印','偏印','食神','正财','偏财','正官']; + const bad = ['七杀','劫财','伤官']; + if (good.includes(stemTenStar)) score += 4; + else if (bad.includes(stemTenStar)) score -= 3; + else score += 1; + } + if (stemCombine) score += 5; + if (stemOpposite) score -= 6; + if (branchRel.combine) score += 4; + if (branchRel.threeCombine) score += 3; + if (branchRel.opposite) score -= 5; + if (branchRel.harm) score -= 4; + if (branchRel.punish) score -= 3; + score = Math.max(-10, Math.min(10, score)); + + relationships.push({ + pillar: key, pillarLabel: PILLAR_LABELS[key], + userGanzhi: userPillar.ganzhi, dayGanzhi: dayP.ganzhi, + stemTenStar, stemCombine, stemOpposite, + branchCombine: branchRel.combine, branchThreeCombine: branchRel.threeCombine, + branchOpposite: branchRel.opposite, branchHarm: branchRel.harm, + branchPunish: branchRel.punish, branchFormation: branchRel.formation, + score, + }); + } + + const weightedScore = + relationships[0].score * 0.20 + relationships[1].score * 0.25 + + relationships[2].score * 0.40 + relationships[3].score * 0.15; + const overallScore = Math.max(-100, Math.min(100, Math.round(weightedScore * 10))); + const scoreLevel: DailyFortuneResult['scoreLevel'] = + overallScore >= 50 ? 'great' : overallScore >= 20 ? 'good' : + overallScore >= -20 ? 'fair' : overallScore >= -50 ? 'poor' : 'bad'; + + const luckyAspects = generateLuckyAspects(relationships); + const unluckyAspects = generateUnluckyAspects(relationships); + const suggestions = generateSuggestions(relationships, userBazi, overallScore); + const affectedAreas = determineAffectedAreas(relationships); + const luckyMeta = computeLuckyMeta(userBazi, dayPillars.day.ganzhi, overallScore); + const categoryScores = computeCategoryScores(relationships); + + const lunarDateStr = `${lunarDay.getLunarMonth().getLunarYear().getYear()}年${lunarDay.getLunarMonth().getName()}${lunarDay.getName()}`; + + return { + date: `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`, + lunarDate: lunarDateStr, dayGanzhi: dayPillars.day.ganzhi, + overallScore, scoreLevel, pillarRelationships: relationships, + luckyAspects, unluckyAspects, suggestions, affectedAreas, luckyMeta, categoryScores, + }; +} + +function generateLuckyAspects(relationships: PillarRelationship[]): string[] { + const seen = new Set(); const aspects: string[] = []; + const tenStarPlain: Record = { + '正官':'事业运不错,工作上容易得到认可和赏识', + '七杀':'进取心强,适合挑战难题和竞争性事务', + '正印':'学习运佳,适合读书充电或向长辈请教', + '偏印':'灵感丰富,适合研究和创意工作', + '食神':'心情舒畅,适合休闲放松和享受生活', + '伤官':'表达欲强,适合沟通交流和展示自我', + '正财':'财运平稳,适合处理日常财务和长期投资', + '偏财':'偏财运佳,可能有意外之财或投资收益', + '比肩':'人际关系和睦,容易得到朋友和同事的帮助', + '劫财':'社交活跃,适合参加聚会和团队活动', + }; + const hasStemCombine = relationships.some(r => r.stemCombine); + const hasBranchCombine = relationships.some(r => r.branchCombine || r.branchThreeCombine); + + for (const rel of relationships) { + if (rel.stemTenStar && tenStarPlain[rel.stemTenStar] && !seen.has(rel.stemTenStar)) { + aspects.push(tenStarPlain[rel.stemTenStar]); seen.add(rel.stemTenStar); + } + } + if (hasStemCombine && !seen.has('combine')) { aspects.push('天时地利人和,容易遇到贵人相助和好机会'); seen.add('combine'); } + if (hasBranchCombine && !seen.has('branch')) { aspects.push('身边关系和谐,适合与人合作或开展团队项目'); seen.add('branch'); } + if (aspects.length === 0) aspects.push('今天整体运势平稳,适合按部就班处理日常事务'); + return aspects.slice(0, 4); +} + +function generateUnluckyAspects(relationships: PillarRelationship[]): string[] { + const aspects: string[] = []; + const hasOpposite = relationships.some(r => r.branchOpposite); + const hasHarm = relationships.some(r => r.branchHarm); + const hasPunish = relationships.some(r => r.branchPunish); + const hasStemOpposite = relationships.some(r => r.stemOpposite); + const hasQiSha = relationships.some(r => r.stemTenStar === '七杀'); + const hasJieCai = relationships.some(r => r.stemTenStar === '劫财'); + const hasShangGuan = relationships.some(r => r.stemTenStar === '伤官'); + + if (hasStemOpposite) aspects.push('今天可能遇到突发变化或计划被打乱,保持冷静和弹性很重要'); + if (hasOpposite) aspects.push('与他人意见不合的可能性较高,尽量避免争执和正面冲突'); + if (hasHarm) aspects.push('留意身边的小人和是非,不要轻易透露自己的计划和秘密'); + if (hasPunish) aspects.push('容易说错话或做出不恰当的举动,多听少说更安全'); + if (hasQiSha) aspects.push('压力较大的一天,注意调节情绪,重要决策可暂缓'); + if (hasJieCai) aspects.push('财务方面要谨慎,不宜大额消费或借钱给别人'); + if (hasShangGuan) aspects.push('表达时注意方式方法,容易词不达意引起误会'); + if (aspects.length === 0) aspects.push('今天没有什么特别需要注意的,保持平常心即可'); + return aspects.slice(0, 3); +} + +function generateSuggestions( + relationships: PillarRelationship[], + userBazi: EightCharInfo, + overallScore: number, +): string[] { + const s: string[] = []; + const de = userBazi.dayMasterElement; + const colors: Record = { '木':'绿色/青色','火':'红色/紫色','土':'黄色/棕色','金':'白色/浅色','水':'蓝色/黑色' }; + const dirs: Record = { '木':'东方','火':'南方','土':'中央','金':'西方','水':'北方' }; + const hasConflict = relationships.some(r => r.branchOpposite || r.stemOpposite); + const hasCombine = relationships.some(r => r.branchCombine || r.stemCombine); + const hasHarm = relationships.some(r => r.branchHarm); + const hasPunish = relationships.some(r => r.branchPunish); + + const goodPool = [ + de ? `👔 幸运色${colors[de]||'浅色'},穿对颜色运气更好` : `👔 今天穿浅色衣服心情更好`, + `✅ 适合推进重要事项,果断决策会有好结果`, + `📝 把计划写下来按部就班执行,效率翻倍`, + `🎯 专注一件事比多线作战更有效果`, + `🤝 适合约重要的人见面,贵人运在线`, + `🌅 早起一点早晨的效率最高`, + `📊 适合总结和复盘能发现新机会`, + `🏃 运动或户外活动对运势有加成`, + ]; + const fairPool = [ + `💡 运势平稳该做什么就做什么`, + `☕ 适合处理日常事务不宜做重大改变`, + `📖 适合学习充电多读多听少说`, + `🧹 整理收纳会让心情变好`, + `🍵 节奏放慢一点稳扎稳打`, + `📞 适合和老朋友聊聊天`, + ]; + const poorPool = [ + `🧘 适合保守行事多观察多思考`, + `☕ 适合独处和复盘少社交`, + `🛡️ 以守成为主避免冲动`, + `📿 冥想或听音乐能化解负面情绪`, + `🙏 遇到不顺心的事深呼吸三秒`, + `📝 把想法写下来明天再行动`, + ]; + const badPool = [ + `🛡️ 宜静不宜动重要决策能缓则缓`, + `🚫 避免签合同或做出重大承诺`, + `🧘 适合冥想休息养精蓄锐`, + `📿 去清净处走走转换气场`, + `💤 早点休息好睡眠是最好的转运`, + `🙏 好事多磨心态会好很多`, + ]; + + let pool = fairPool; + if (overallScore >= 30) pool = goodPool; + else if (overallScore >= 0) pool = fairPool; + else if (overallScore >= -30) pool = poorPool; + else pool = badPool; + + const seed = new Date().getDate() + new Date().getMonth() * 31; + s.push(pool[seed % pool.length]); + s.push(pool[(seed + 5) % pool.length]); + + if (overallScore >= 10 && de && dirs[de]) s.push(`🧭 向${dirs[de]}行事有意外好运`); + if (hasCombine && !hasConflict) s.push('🤝 人际关系活跃适合合作洽谈'); + else if (hasConflict && hasHarm) s.push('⚠️ 人际关系有暗礁保持微笑少说话'); + else if (hasConflict) s.push('🙏 意见不合时先认同再引导'); + else if (hasPunish) s.push('💬 说话前过遍脑子今天容易说错话'); + + const seen = new Set(); + const unique = s.filter(t => { if (seen.has(t)) return false; seen.add(t); return true; }); + return unique.slice(0, 4); +} + +function computeLuckyMeta(userBazi: EightCharInfo, dayGanzhi: string, score: number) { + const de = userBazi.dayMasterElement; + const colorMap: Record = { '木': ['绿色','青色','翠绿'], '火': ['红色','紫色','粉色'], '土': ['黄色','棕色','米色'], '金': ['白色','银色','浅灰'], '水': ['蓝色','黑色','深灰'] }; + const dirMap: Record = { '木': '东方', '火': '南方', '土': '中央', '金': '西方', '水': '北方' }; + const actMap: Record = { + '木': ['户外散步','园艺','阅读','写作'], '火': ['社交','演讲','创意','运动'], '土': ['整理收纳','理财','烹饪','冥想'], + '金': ['商务洽谈','签约','购物','美容'], '水': ['学习进修','旅行','游泳','听音乐'], + }; + const colors = colorMap[de] || ['红色','白色']; + const direction = dirMap[de] || '东方'; + const seed = parseInt(dayGanzhi.charCodeAt(0).toString()) + new Date().getDate(); + const n1 = (seed % 9) + 1; + const n2 = ((seed * 3 + 7) % 9) + 1; + const activities = actMap[de] || ['运动','阅读']; + const activity = activities[seed % activities.length]; + return { + colors: [colors[0], colors[1]], + numbers: [n1, n2], + direction, + element: de, + activity: score >= 0 ? activity : activities[(seed + 3) % activities.length], + }; +} + +function computeCategoryScores(relationships: PillarRelationship[]) { + const tenStarLove: Record = { '正官':8,'七杀':-2,'正印':3,'偏印':2,'食神':6,'伤官':5,'正财':4,'偏财':3 }; + const tenStarCareer: Record = { '正官':10,'七杀':8,'正印':5,'偏印':4,'食神':3,'伤官':2,'正财':2 }; + const tenStarWealth: Record = { '正财':10,'偏财':8,'食神':6,'伤官':4,'正官':2,'七杀':-2 }; + const tenStarHealth: Record = { '正印':8,'偏印':6,'比肩':5,'食神':4,'七杀':-4,'伤官':-2 }; + + let love=0, career=0, wealth=0, health=0; + for (const rel of relationships) { + const ts = rel.stemTenStar; + if (!ts) continue; + const w = rel.pillar === 'day' ? 3 : rel.pillar === 'month' ? 2 : 1; + love += (tenStarLove[ts]||0) * w + (rel.branchHarm?-5:0) * w + (rel.branchCombine?4:0) * w; + career += (tenStarCareer[ts]||0) * w + (rel.branchOpposite?-4:0) * w + (rel.branchThreeCombine?5:0) * w; + wealth += (tenStarWealth[ts]||0) * w + (rel.stemCombine?5:0) * w; + health += (tenStarHealth[ts]||0) * w + (rel.branchOpposite?-3:0) * w; + } + // Normalize to -100..100 + const clamp = (v:number) => Math.max(-100, Math.min(100, Math.round(v * 2.5))); + return { love: clamp(love), career: clamp(career), wealth: clamp(wealth), health: clamp(health) }; +} + +function determineAffectedAreas(relationships: PillarRelationship[]): string[] { + const areas = new Set(); + const areaMapping: Record = { + '正财':'财运','偏财':'偏财/投资','正官':'事业','七杀':'事业/压力', + '正印':'学业','偏印':'学业/智慧','食神':'创作/享乐','伤官':'口才/表达', + '比肩':'人际关系','劫财':'竞争/人际', + }; + for (const rel of relationships) { + if (rel.stemTenStar && areaMapping[rel.stemTenStar]) areas.add(areaMapping[rel.stemTenStar]); + if (rel.branchOpposite && rel.pillar === 'year') areas.add('长辈/根基'); + if (rel.branchOpposite && rel.pillar === 'month') areas.add('事业/家庭'); + if (rel.branchOpposite && rel.pillar === 'day') areas.add('婚姻/健康'); + if (rel.branchOpposite && rel.pillar === 'hour') areas.add('子女/晚年'); + } + return areas.size > 0 ? [...areas] : ['综合运势']; +} diff --git a/web/packages/core/src/calculators/elementStrength.ts b/web/packages/core/src/calculators/elementStrength.ts new file mode 100644 index 0000000..87a5641 --- /dev/null +++ b/web/packages/core/src/calculators/elementStrength.ts @@ -0,0 +1,99 @@ +import type { EightCharInfo } from '../types/bazi'; + +/** Five Element profile */ +export interface ElementProfile { + wood: number; + fire: number; + earth: number; + metal: number; + water: number; + total: number; + dominant: string; + weakest: string; + isBalanced: boolean; +} + +/** Analyze the five element balance in a Bazi chart */ +export function analyzeElementBalance(bazi: EightCharInfo): ElementProfile { + const pillars = [bazi.yearPillar, bazi.monthPillar, bazi.dayPillar, bazi.hourPillar]; + + const counts: Record = { + '木': 0, + '火': 0, + '土': 0, + '金': 0, + '水': 0, + }; + + for (const pillar of pillars) { + // Count stem element + if (pillar.elementStem && counts[pillar.elementStem] !== undefined) { + counts[pillar.elementStem] += 1; + } + // Count branch element + if (pillar.elementBranch && counts[pillar.elementBranch] !== undefined) { + counts[pillar.elementBranch] += 1; + } + // Count hidden stem elements (half weight) + for (const hs of pillar.hideStems) { + // Infer element from stem name + const element = inferElementFromStem(hs.stem); + if (element && counts[element] !== undefined) { + counts[element] += 0.5; + } + } + } + + const total = counts['木'] + counts['火'] + counts['土'] + counts['金'] + counts['水']; + + // Find dominant and weakest + let dominant = '木'; + let weakest = '木'; + let maxCount = 0; + let minCount = Infinity; + + for (const [element, count] of Object.entries(counts)) { + if (count > maxCount) { + maxCount = count; + dominant = element; + } + if (count < minCount) { + minCount = count; + weakest = element; + } + } + + // Balance check: each element should be within 30% of ideal (20% each) + const ideal = total / 5; + let isBalanced = true; + for (const count of Object.values(counts)) { + if (Math.abs(count - ideal) > ideal * 0.4) { + isBalanced = false; + break; + } + } + + return { + wood: counts['木'] || 0, + fire: counts['火'] || 0, + earth: counts['土'] || 0, + metal: counts['金'] || 0, + water: counts['水'] || 0, + total, + dominant, + weakest, + isBalanced, + }; +} + +/** Infer five element from heavenly stem name */ +function inferElementFromStem(stem: string): string | null { + const stemElements: Record = { + '甲': '木', '乙': '木', + '丙': '火', '丁': '火', + '戊': '土', '己': '土', + '庚': '金', '辛': '金', + '壬': '水', '癸': '水', + }; + return stemElements[stem] || null; +} diff --git a/web/packages/core/src/calculators/fortuneLuck.ts b/web/packages/core/src/calculators/fortuneLuck.ts new file mode 100644 index 0000000..baeeaff --- /dev/null +++ b/web/packages/core/src/calculators/fortuneLuck.ts @@ -0,0 +1,88 @@ +/** + * 大运/流年/流月/流日 与日主的生克冲合分析 + * 将任意干支与日主(日干/日支)比较,输出十神、五行生克、天干合冲、地支关系与简化吉凶。 + */ +import { getTenStarRelationship, checkStemCombine, checkStemOpposite, getBranchRelationship } from './relationship'; + +export interface FortuneLuck { + ganzhi: string; + tenStar: string | null; + /** 该柱五行 vs 日主五行:比和/生我/我生/克我/我克 */ + elementRelation: string; + stemCombine: boolean; + stemOpposite: boolean; + branchCombine: boolean; + branchThreeCombine: boolean; + branchOpposite: boolean; + branchHarm: boolean; + branchPunish: boolean; + score: number; + level: '吉' | '凶' | '平'; +} + +const STEM_ELEMENT: Record = { + '甲': '木', '乙': '木', '丙': '火', '丁': '火', '戊': '土', + '己': '土', '庚': '金', '辛': '金', '壬': '水', '癸': '水', +}; +const GENERATES: Record = { '木': '火', '火': '土', '土': '金', '金': '水', '水': '木' }; +const KILLS: Record = { '木': '土', '土': '水', '水': '火', '火': '金', '金': '木' }; + +const GOOD_TEN_STARS = ['正印', '偏印', '食神', '正财', '偏财', '正官']; +const BAD_TEN_STARS = ['七杀', '劫财', '伤官']; + +/** Analyze an external ganzhi (大运/流年/流月/流日) against the day master pillar */ +export function analyzeFortuneGanzhi( + ganzhi: string, + dayStem: string, + dayBranch: string, +): FortuneLuck { + const stem = ganzhi[0]; + const branch = ganzhi[1]; + + const tenStar = getTenStarRelationship(dayStem, stem); + + const el = STEM_ELEMENT[stem]; + const de = STEM_ELEMENT[dayStem]; + let elementRelation: string; + if (!el || !de || el === de) elementRelation = '比和'; + else if (GENERATES[el] === de) elementRelation = '生我'; + else if (GENERATES[de] === el) elementRelation = '我生'; + else if (KILLS[el] === de) elementRelation = '克我'; + else elementRelation = '我克'; + + const stemCombine = checkStemCombine(stem, dayStem); + const stemOpposite = checkStemOpposite(stem, dayStem); + const br = getBranchRelationship(branch, dayBranch); + + let score = 0; + if (tenStar) { + if (GOOD_TEN_STARS.includes(tenStar)) score += 4; + else if (BAD_TEN_STARS.includes(tenStar)) score -= 3; + else score += 1; + } + if (stemCombine) score += 5; + if (stemOpposite) score -= 6; + if (br.combine) score += 4; + if (br.threeCombine) score += 3; + if (br.opposite) score -= 5; + if (br.harm) score -= 4; + if (br.punish) score -= 3; + score = Math.max(-10, Math.min(10, score)); + + const level: FortuneLuck['level'] = score >= 3 ? '吉' : score <= -3 ? '凶' : '平'; + + return { + ganzhi, + tenStar, + elementRelation, + stemCombine, + stemOpposite, + branchCombine: br.combine, + branchThreeCombine: br.threeCombine, + branchOpposite: br.opposite, + branchHarm: br.harm, + branchPunish: br.punish, + score, + level, + }; +} diff --git a/web/packages/core/src/calculators/plumBlossom.ts b/web/packages/core/src/calculators/plumBlossom.ts new file mode 100644 index 0000000..b5753b0 --- /dev/null +++ b/web/packages/core/src/calculators/plumBlossom.ts @@ -0,0 +1,258 @@ +/** + * 梅花易数 (Plum Blossom I-Ching Divination) + * Based on time (year, month, day, hour) to derive hexagrams + */ +export interface TrigramInfo { + index: number; // 1-8 (乾兑离震巽坎艮坤) + name: string; // Chinese name + symbol: string; // Unicode trigram symbol + element: string; // Five element + direction: string; // Direction + nature: string; // Natural phenomenon + trait: string; // Personality trait + body: string; // Body part +} + +export interface HexagramInfo { + number: number; // 1-64 + name: string; // Chinese name e.g. "乾为天" + upperTrigram: TrigramInfo; + lowerTrigram: TrigramInfo; + changingLine: number; // 1-6, which line changes + interpretation: string; // Overall interpretation + judgment: string; // 彖辞 + image: string; // 象辞 + lines: string[]; // 6 line interpretations +} + +export interface PlumBlossomResult { + originalHexagram: HexagramInfo; + transformedHexagram: HexagramInfo | null; + mutualHexagram: HexagramInfo | null; + upperTrigram: TrigramInfo; + lowerTrigram: TrigramInfo; + changingLine: number; + constitution: string; // 体卦 + function: string; // 用卦 + relationship: string; // 体用关系 +} + +// 8 Trigrams (八卦) +const TRIGRAMS: Record = { + 1: { index: 1, name: '乾', symbol: '☰', element: '金', direction: '西北', nature: '天', trait: '健', body: '首' }, + 2: { index: 2, name: '兑', symbol: '☱', element: '金', direction: '西', nature: '泽', trait: '悦', body: '口' }, + 3: { index: 3, name: '离', symbol: '☲', element: '火', direction: '南', nature: '火', trait: '丽', body: '目' }, + 4: { index: 4, name: '震', symbol: '☳', element: '木', direction: '东', nature: '雷', trait: '动', body: '足' }, + 5: { index: 5, name: '巽', symbol: '☴', element: '木', direction: '东南', nature: '风', trait: '入', body: '股' }, + 6: { index: 6, name: '坎', symbol: '☵', element: '水', direction: '北', nature: '水', trait: '陷', body: '耳' }, + 7: { index: 7, name: '艮', symbol: '☶', element: '土', direction: '东北', nature: '山', trait: '止', body: '手' }, + 8: { index: 8, name: '坤', symbol: '☷', element: '土', direction: '西南', nature: '地', trait: '顺', body: '腹' }, +}; + +// All 64 Hexagrams (complete I-Ching) +const H: Record = { + // 乾宫八卦 (1-8) + '1,1':{name:'乾为天',judgment:'大哉乾元,万物资始,乃统天。云行雨施,品物流形',image:'天行健,君子以自强不息',lines:['潜龙勿用','见龙在田,利见大人','君子终日乾乾,夕惕若厉','或跃在渊,无咎','飞龙在天,利见大人','亢龙有悔']}, + '1,5':{name:'天风姤',judgment:'姤,遇也,柔遇刚也。天地相遇,品物咸章',image:'天下有风,姤。后以施命诰四方',lines:['系于金柅,贞吉','包有鱼,无咎','臀无肤,其行次且','包无鱼,起凶','以杞包瓜,含章','姤其角,吝']}, + '1,7':{name:'天山遁',judgment:'遁亨,遁而亨也。刚当位而应,与时行也',image:'天下有山,遁。君子以远小人,不恶而严',lines:['遁尾厉,勿用有攸往','执之用黄牛之革','系遁,有疾厉','好遁,君子吉','嘉遁,贞吉','肥遁,无不利']}, + '1,8':{name:'天地否',judgment:'否之匪人,不利君子贞。大往小来',image:'天地不交,否。君子以俭德辟难',lines:['拔茅茹,以其汇,贞吉','包承,小人吉,大人否','包羞','有命无咎,畴离祉','休否,大人吉','倾否,先否后喜']}, + '5,8':{name:'风地观',judgment:'观,盥而不荐,有孚颙若。观天之神道而四时不忒',image:'风行地上,观。先王以省方观民设教',lines:['童观,小人无咎','窥观,利女贞','观我生进退','观国之光,利用宾于王','观我生,君子无咎','观其生,君子无咎']}, + '7,8':{name:'山地剥',judgment:'剥,剥也,柔变刚也。不利有攸往',image:'山附于地,剥。上以厚下安宅',lines:['剥床以足,蔑贞凶','剥床以辨,蔑贞凶','剥之无咎','剥床以肤,凶','贯鱼以宫人宠,无不利','硕果不食,君子得舆']}, + '3,8':{name:'火地晋',judgment:'晋,进也。明出地上,顺而丽乎大明',image:'明出地上,晋。君子以自昭明德',lines:['晋如摧如,贞吉','晋如愁如,贞吉','众允,悔亡','晋如鼫鼠,贞厉','悔亡,失得勿恤','晋其角,维用伐邑']}, + '3,1':{name:'火天大有',judgment:'大有,柔得尊位,大中而上下应之',image:'火在天上,大有。君子以遏恶扬善,顺天休命',lines:['无交害,匪咎','大车以载,有攸往','公用亨于天子','匪其彭,无咎','厥孚交如,威如','自天佑之,吉无不利']}, + // 坎宫八卦 (9-16) + '6,6':{name:'坎为水',judgment:'习坎,重险也。水流而不盈,行险而不失其信',image:'水洊至,习坎。君子以常德行习教事',lines:['习坎,入于坎窞','坎有险,求小得','来之坎坎,险且枕','樽酒簋贰,用缶','坎不盈,祗既平','系用徽纆,寘于丛棘']}, + '6,2':{name:'水泽节',judgment:'节亨,苦节不可贞',image:'泽上有水,节。君子以制数度议德行',lines:['不出户庭,无咎','不出门庭,凶','不节若,则嗟若','安节,亨','甘节,吉','苦节,贞凶']}, + '6,4':{name:'水雷屯',judgment:'屯,刚柔始交而难生。动乎险中,大亨贞',image:'云雷屯,君子以经纶',lines:['磐桓,利居贞','屯如邅如,乘马班如','即鹿无虞,惟入于林中','乘马班如,求婚媾','屯其膏,小贞吉','乘马班如,泣血涟如']}, + '6,3':{name:'水火既济',judgment:'既济亨,小者亨也。利贞,初吉终乱',image:'水在火上,既济。君子以思患而豫防之',lines:['曳其轮,濡其尾','妇丧其茀,勿逐','高宗伐鬼方,三年克之','繻有衣袽,终日戒','东邻杀牛,不如西邻','濡其首,厉']}, + '2,3':{name:'泽火革',judgment:'革,水火相息。天地革而四时成',image:'泽中有火,革。君子以治历明时',lines:['巩用黄牛之革','巳日乃革之,征吉','征凶,贞厉','悔亡,有孚改命','大人虎变,未占有孚','君子豹变,小人革面']}, + '4,3':{name:'雷火丰',judgment:'丰,大也。明以动,故丰',image:'雷电皆至,丰。君子以折狱致刑',lines:['遇其配主,虽旬无咎','丰其蔀,日中见斗','丰其沛,日中见沬','丰其蔀,日中见斗','来章,有庆誉','丰其屋,蔀其家']}, + '8,3':{name:'地火明夷',judgment:'明夷,利艰贞。明入地中,明夷',image:'明入地中,明夷。君子以莅众用晦而明',lines:['明夷于飞,垂其翼','明夷,夷于左股','明夷于南狩,得其大首','入于左腹,获明夷之心','箕子之明夷,利贞','不明晦,初登于天']}, + '8,6':{name:'地水师',judgment:'师,众也。贞,丈人吉,无咎',image:'地中有水,师。君子以容民畜众',lines:['师出以律,否臧凶','在师中,吉无咎','师或舆尸,凶','师左次,无咎','田有禽,利执言','大君有命,开国承家']}, + // 艮宫八卦 (17-24) + '7,7':{name:'艮为山',judgment:'艮,止也。时止则止,时行则行',image:'兼山,艮。君子以思不出其位',lines:['艮其趾,无咎','艮其腓,不拯其随','艮其限,列其夤','艮其身,无咎','艮其辅,言有序','敦艮,吉']}, + '7,3':{name:'山火贲',judgment:'贲亨,柔来而文刚,故亨',image:'山下有火,贲。君子以明庶政无敢折狱',lines:['贲其趾,舍车而徒','贲其须','贲如濡如,永贞吉','贲如皤如,白马翰如','贲于丘园,束帛戋戋','白贲,无咎']}, + '7,1':{name:'山天大畜',judgment:'大畜,刚健笃实辉光,日新其德',image:'天在山中,大畜。君子以多识前言往行',lines:['有厉,利已','舆说輹','良马逐,利艰贞','童牛之牿,元吉','豮豕之牙,吉','何天之衢,亨']}, + '7,2':{name:'山泽损',judgment:'损,损下益上,其道上行',image:'山下有泽,损。君子以惩忿窒欲',lines:['已事遄往,无咎','利贞,征凶,弗损益之','三人行则损一人','损其疾,使遄有喜','或益之十朋之龟','弗损益之,无咎']}, + '3,2':{name:'火泽睽',judgment:'睽,火动而上,泽动而下',image:'上火下泽,睽。君子以同而异',lines:['悔亡,丧马勿逐','遇主于巷,无咎','见舆曳,其牛掣','睽孤,遇元夫','悔亡,厥宗噬肤','睽孤,见豕负涂']}, + '1,2':{name:'天泽履',judgment:'履,柔履刚也。说而应乎乾',image:'上天下泽,履。君子以辩上下定民志',lines:['素履往,无咎','履道坦坦,幽人贞吉','眇能视,跛能履','履虎尾,愬愬终吉','夬履,贞厉','视履考祥,其旋元吉']}, + '5,2':{name:'风泽中孚',judgment:'中孚,柔在内而刚得中',image:'泽上有风,中孚。君子以议狱缓死',lines:['虞吉,有它不燕','鸣鹤在阴,其子和之','得敌,或鼓或罢','月几望,马匹亡','有孚挛如,无咎','翰音登于天,贞凶']}, + '5,7':{name:'风山渐',judgment:'渐,女归吉也。进得位,往有功也',image:'山上有木,渐。君子以居贤德善俗',lines:['鸿渐于干,小子厉','鸿渐于磐,饮食衎衎','鸿渐于陆,夫征不复','鸿渐于木,或得其桷','鸿渐于陵,妇三岁不孕','鸿渐于逵,其羽可用为仪']}, + // 震宫八卦 (25-32) + '4,4':{name:'震为雷',judgment:'震亨。震来虩虩,笑言哑哑,震惊百里',image:'洊雷,震。君子以恐惧修省',lines:['震来虩虩,后笑言哑哑','震来厉,亿丧贝','震苏苏,震行无眚','震遂泥','震往来厉,亿无丧','震索索,视矍矍']}, + '4,8':{name:'雷地豫',judgment:'豫,刚应而志行,顺以动',image:'雷出地奋,豫。先王以作乐崇德',lines:['鸣豫,凶','介于石,不终日','盱豫悔,迟有悔','由豫,大有得','贞疾,恒不死','冥豫,成有渝']}, + '4,6':{name:'雷水解',judgment:'解,险以动,动而免乎险',image:'雷雨作,解。君子以赦过宥罪',lines:['无咎','田获三狐,得黄矢','负且乘,致寇至','解而拇,朋至斯孚','君子维有解,吉','公用射隼于高墉之上']}, + '4,5':{name:'雷风恒',judgment:'恒,久也。刚上而柔下',image:'雷风,恒。君子以立不易方',lines:['浚恒,贞凶','悔亡','不恒其德,或承之羞','田无禽','恒其德,贞妇人吉','振恒,凶']}, + '8,5':{name:'地风升',judgment:'柔以时升,巽而顺,刚中而应',image:'地中生木,升。君子以顺德积小以高大',lines:['允升,大吉','孚乃利用禴,无咎','升虚邑','王用亨于岐山','贞吉,升阶','冥升,利于不息之贞']}, + '6,5':{name:'水风井',judgment:'井,改邑不改井,无丧无得',image:'木上有水,井。君子以劳民劝相',lines:['井泥不食,旧井无禽','井谷射鲋,瓮敝漏','井渫不食,为我心恻','井甃,无咎','井洌,寒泉食','井收勿幕,有孚元吉']}, + '2,5':{name:'泽风大过',judgment:'大过,大者过也。栋桡,本末弱也',image:'泽灭木,大过。君子以独立不惧遁世无闷',lines:['藉用白茅,无咎','枯杨生稊,老夫得其女妻','栋桡,凶','栋隆,吉','枯杨生华,老妇得士夫','过涉灭顶,凶']}, + '2,4':{name:'泽雷随',judgment:'随,刚来而下柔,动而说',image:'泽中有雷,随。君子以向晦入宴息',lines:['官有渝,贞吉','系小子,失丈夫','系丈夫,失小子','随有获,贞凶','孚于嘉,吉','拘系之,乃从维之']}, + // 巽宫八卦 (33-40) + '5,5':{name:'巽为风',judgment:'重巽以申命,刚巽乎中正而志行',image:'随风,巽。君子以申命行事',lines:['进退,利武人之贞','巽在床下,用史巫纷若','频巽,吝','悔亡,田获三品','贞吉,悔亡无不利','巽在床下,丧其资斧']}, + '5,1':{name:'风天小畜',judgment:'小畜,柔得位而上下应之',image:'风行天上,小畜。君子以懿文德',lines:['复自道,何其咎','牵复,吉','舆说辐,夫妻反目','有孚,血去惕出','有孚挛如,富以其邻','既雨既处,尚德载']}, + '5,3':{name:'风火家人',judgment:'家人,女正位乎内,男正位乎外',image:'风自火出,家人。君子以言有物而行有恒',lines:['闲有家,悔亡','无攸遂,在中馈','家人嗃嗃,悔厉吉','富家,大吉','王假有家,勿恤','有孚威如,终吉']}, + '5,4':{name:'风雷益',judgment:'益,损上益下,民说无疆',image:'风雷,益。君子以见善则迁有过则改',lines:['利用为大作,元吉','或益之十朋之龟','益之用凶事,无咎','中行告公从,利用为依迁国','有孚惠心,勿问元吉','莫益之,或击之']}, + '1,4':{name:'天雷无妄',judgment:'无妄,刚自外来而为主于内',image:'天下雷行,物与无妄。先王以茂对时育万物',lines:['无妄,往吉','不耕获,不菑畬','无妄之灾,或系之牛','可贞,无咎','无妄之疾,勿药有喜','无妄,行有眚']}, + '3,4':{name:'火雷噬嗑',judgment:'噬嗑亨,利用狱。刚柔分动而明',image:'雷电噬嗑,先王以明罚敕法',lines:['屦校灭趾,无咎','噬肤灭鼻,无咎','噬腊肉,遇毒','噬干胏,得金矢','噬干肉,得黄金','何校灭耳,凶']}, + '7,4':{name:'山雷颐',judgment:'颐,贞吉。观颐,自求口实',image:'山下有雷,颐。君子以慎言语节饮食',lines:['舍尔灵龟,观我朵颐','颠颐,拂经于丘颐','拂颐,贞凶','颠颐,吉','拂经,居贞吉','由颐,厉吉,利涉大川']}, + '7,5':{name:'山风蛊',judgment:'蛊,元亨。利涉大川,先甲三日后甲三日',image:'山下有风,蛊。君子以振民育德',lines:['干父之蛊,有子考无咎','干母之蛊,不可贞','干父之蛊,小有悔','裕父之蛊,往见吝','干父之蛊,用誉','不事王侯,高尚其事']}, + // 离宫八卦 (41-48) + '3,3':{name:'离为火',judgment:'离,丽也。日月丽乎天,百谷草木丽乎土',image:'明两作,离。大人以继明照于四方',lines:['履错然,敬之无咎','黄离,元吉','日昃之离,不鼓缶而歌','突如其来如,焚如死如弃如','出涕沱若,戚嗟若','王用出征,有嘉折首']}, + '3,7':{name:'火山旅',judgment:'旅,小亨。旅贞吉',image:'山上有火,旅。君子以明慎用刑而不留狱',lines:['旅琐琐,斯其所取灾','旅即次,怀其资','旅焚其次,丧其童仆','旅于处,得其资斧','射雉,一矢亡','鸟焚其巢,旅人先笑后号咷']}, + '3,5':{name:'火风鼎',judgment:'鼎,象也。以木巽火,亨饪也',image:'木上有火,鼎。君子以正位凝命',lines:['鼎颠趾,利出否','鼎有实,我仇有疾','鼎耳革,其行塞','鼎折足,覆公餗','鼎黄耳金铉,利贞','鼎玉铉,大吉']}, + '3,6':{name:'火水未济',judgment:'未济亨,小狐汔济,濡其尾',image:'火在水上,未济。君子以慎辨物居方',lines:['濡其尾,吝','曳其轮,贞吉','未济,征凶','贞吉,悔亡','贞吉,无悔','有孚于饮酒,无咎']}, + '7,6':{name:'山水蒙',judgment:'蒙亨。匪我求童蒙,童蒙求我',image:'山下出泉,蒙。君子以果行育德',lines:['发蒙,利用刑人','包蒙,吉','勿用取女,见金夫','困蒙,吝','童蒙,吉','击蒙,不利为寇']}, + '5,6':{name:'风水涣',judgment:'涣亨。王假有庙,利涉大川',image:'风行水上,涣。先王以享于帝立庙',lines:['用拯马壮,吉','涣奔其机,悔亡','涣其躬,无悔','涣其群,元吉','涣汗其大号','涣其血,去逖出']}, + '1,6':{name:'天水讼',judgment:'讼,上刚下险,险而健,讼',image:'天与水违行,讼。君子以作事谋始',lines:['不永所事,小有言','不克讼,归而逋','食旧德,贞厉终吉','不克讼,复即命渝','讼,元吉','或锡之鞶带,终朝三褫']}, + '1,3':{name:'天火同人',judgment:'同人,柔得位得中而应乎乾',image:'天与火,同人。君子以类族辨物',lines:['同人于门,无咎','同人于宗,吝','伏戎于莽,升其高陵','乘其墉,弗克攻','同人先号咷而后笑','同人于郊,无悔']}, + // 坤宫八卦 (49-56) + '8,8':{name:'坤为地',judgment:'至哉坤元,万物资生,乃顺承天',image:'地势坤,君子以厚德载物',lines:['履霜,坚冰至','直方大,不习无不利','含章可贞,或从王事','括囊,无咎无誉','黄裳,元吉','龙战于野,其血玄黄']}, + '8,4':{name:'地雷复',judgment:'复亨。出入无疾,朋来无咎',image:'雷在地中,复。先王以至日闭关',lines:['不远复,无祗悔','休复,吉','频复,厉','中行独复','敦复,无悔','迷复,凶有灾眚']}, + '8,2':{name:'地泽临',judgment:'临,刚浸而长,说而顺',image:'泽上有地,临。君子以教思无穷容保民无疆',lines:['咸临,贞吉','咸临,吉无不利','甘临,无攸利','至临,无咎','知临,大君之宜','敦临,吉无咎']}, + '8,1':{name:'地天泰',judgment:'泰,小往大来,吉亨。天地交而万物通',image:'天地交,泰。后以财成天地之道',lines:['拔茅茹,以其汇','包荒,用冯河','无平不陂,无往不复','翩翩,不富以其邻','帝乙归妹,以祉元吉','城复于隍,勿用师']}, + '4,1':{name:'雷天大壮',judgment:'大壮,大者壮也。刚以动,故壮',image:'雷在天上,大壮。君子以非礼勿履',lines:['壮于趾,征凶','贞吉','小人用壮,君子用罔','贞吉,悔亡','丧羊于易,无悔','羝羊触藩,不能退']}, + '2,1':{name:'泽天夬',judgment:'夬,决也,刚决柔也。健而说,决而和',image:'泽上于天,夬。君子以施禄及下',lines:['壮于前趾,往不胜为咎','惕号,莫夜有戎','壮于頄,有凶','臀无肤,其行次且','苋陆夬夬,中行无咎','无号,终有凶']}, + '6,1':{name:'水天需',judgment:'需,须也。险在前也,刚健而不陷',image:'云上于天,需。君子以饮食宴乐',lines:['需于郊,利用恒','需于沙,小有言','需于泥,致寇至','需于血,出自穴','需于酒食,贞吉','入于穴,有不速之客三人来']}, + '6,8':{name:'水地比',judgment:'比,吉也。比,辅也,下顺从也',image:'地上有水,比。先王以建万国亲诸侯',lines:['有孚比之,无咎','比之自内,贞吉','比之匪人','外比之,贞吉','显比,王用三驱','比之无首,凶']}, + // 兑宫八卦 (57-64) + '2,2':{name:'兑为泽',judgment:'兑,说也。刚中而柔外,说以利贞',image:'丽泽,兑。君子以朋友讲习',lines:['和兑,吉','孚兑,吉','来兑,凶','商兑未宁,介疾有喜','孚于剥,有厉','引兑']}, + '2,6':{name:'泽水困',judgment:'困,刚掩也。险以说,困而不失其所',image:'泽无水,困。君子以致命遂志',lines:['臀困于株木,入于幽谷','困于酒食,朱绂方来','困于石,据于蒺藜','来徐徐,困于金车','劓刖,困于赤绂','困于葛藟,于臲兀']}, + '2,8':{name:'泽地萃',judgment:'萃,聚也。顺以说,刚中而应',image:'泽上于地,萃。君子以除戎器戒不虞',lines:['有孚不终,乃乱乃萃','引吉,无咎','萃如嗟如,无攸利','大吉,无咎','萃有位,无咎','赍咨涕洟,无咎']}, + '2,7':{name:'泽山咸',judgment:'咸,感也。柔上而刚下,二气感应以相与',image:'山上有泽,咸。君子以虚受人',lines:['咸其拇','咸其腓,凶','咸其股,执其随','贞吉,悔亡','咸其脢,无悔','咸其辅颊舌']}, + '6,7':{name:'水山蹇',judgment:'蹇,难也,险在前也。见险而能止',image:'山上有水,蹇。君子以反身修德',lines:['往蹇,来誉','王臣蹇蹇,匪躬之故','往蹇,来反','往蹇,来连','大蹇,朋来','往蹇,来硕']}, + '8,7':{name:'地山谦',judgment:'谦亨。天道下济而光明,地道卑而上行',image:'地中有山,谦。君子以裒多益寡称物平施',lines:['谦谦君子,用涉大川','鸣谦,贞吉','劳谦君子,有终吉','无不利,撝谦','不富以其邻,利用侵伐','鸣谦,利用行师']}, + '4,7':{name:'雷山小过',judgment:'小过,小者过而亨也。过以利贞',image:'山上有雷,小过。君子以行过乎恭',lines:['飞鸟以凶','过其祖,遇其妣','弗过防之,从或戕之','无咎,弗过遇之','密云不雨,自我西郊','弗遇过之,飞鸟离之']}, + '4,2':{name:'雷泽归妹',judgment:'归妹,天地之大义也。天地不交而万物不兴',image:'泽上有雷,归妹。君子以永终知敝',lines:['归妹以娣,跛能履','眇能视,利幽人之贞','归妹以须,反归以娣','归妹愆期,迟归有时','帝乙归妹,其君之袂','女承筐无实,士刲羊无血']}, +}; + +// Generate all 64 hexagrams +function buildHexagramMap() { + const map: Record = {}; + for (let upper = 1; upper <= 8; upper++) { + for (let lower = 1; lower <= 8; lower++) { + const key = `${upper},${lower}`; + const hName = H[key]; + if (hName) { + map[key] = { + number: (upper - 1) * 8 + lower, + name: hName.name, + upperTrigram: TRIGRAMS[upper], + lowerTrigram: TRIGRAMS[lower], + changingLine: 0, + interpretation: hName.judgment, + judgment: hName.judgment, + image: hName.image, + lines: hName.lines, + }; + } + } + } + return map; +} + +const HEXAGRAM_MAP = buildHexagramMap(); + +/** Calculate Plum Blossom I-Ching from date and time */ +export function calculatePlumBlossom( + year: number, + month: number, + day: number, + hour: number = 12, +): PlumBlossomResult { + // Use full numbers for more entropy + const yearNum = year; + const monthNum = month; + const dayNum = day; + const hourIndex = Math.floor(((hour + 1) % 24) / 2); // 0-11 地支时辰 + + // Standard Plum Blossom formula: + // Upper trigram: (year + month + day) % 8 → 0-7 → +1 → 1-8 + const upperIdx = ((yearNum + monthNum + dayNum) % 8) + 1; + // Lower trigram: (month + day + hourIndex + 1) % 8 + 1 + const lowerIdx = ((monthNum + dayNum + hourIndex + 1) % 8) + 1; + // Changing line: (year + month + day + hourIndex + 1) % 6 + 1 + const changingLine = ((yearNum + monthNum + dayNum + hourIndex + 1) % 6) + 1; + + const upperTrigram = TRIGRAMS[upperIdx]; + const lowerTrigram = TRIGRAMS[lowerIdx]; + + // Original hexagram + const originalKey = `${upperIdx},${lowerIdx}`; + const originalHexagram = HEXAGRAM_MAP[originalKey] || createFallbackHexagram(upperIdx, lowerIdx, changingLine); + + // Transformed hexagram (after changing line) + let transUpperIdx = upperIdx; + let transLowerIdx = lowerIdx; + // Changing line 1-3 affects lower trigram, 4-6 affects upper + if (changingLine >= 4) { + transUpperIdx = flipTrigramLine(upperIdx, changingLine - 3); + } else { + transLowerIdx = flipTrigramLine(lowerIdx, changingLine); + } + const transKey = `${transUpperIdx},${transLowerIdx}`; + const transformedHexagram = HEXAGRAM_MAP[transKey] || createFallbackHexagram(transUpperIdx, transLowerIdx, 0); + + // Mutual hexagram (互卦): 2-4 lines → lower, 3-5 lines → upper + // Simplified: use middle two trigrams + const mutualUpperIdx = lowerIdx; // simplified + const mutualLowerIdx = upperIdx; // simplified + const mutualKey = `${mutualUpperIdx},${mutualLowerIdx}`; + const mutualHexagram = HEXAGRAM_MAP[mutualKey] || null; + + // Constitution (体卦) and Function (用卦) + // 体卦 = lower trigram, 用卦 = upper trigram + const constitution = lowerTrigram.element; + const function_ = upperTrigram.element; + const relationship = getElementRelationship(constitution, function_); + + return { + originalHexagram: { ...originalHexagram, changingLine }, + transformedHexagram, + mutualHexagram, + upperTrigram, + lowerTrigram, + changingLine, + constitution, + function: function_, + relationship, + }; +} + +function flipTrigramLine(trigramIdx: number, line: number): number { + // Flip a single line of the trigram + // Trigrams encoded as bits: 乾111=7, 兑110=6, 离101=5, 震100=4, 巽011=3, 坎010=2, 艮001=1, 坤000=0 + const encoding = [0, 7, 6, 5, 4, 3, 2, 1, 0]; // index → bit pattern + let bits = encoding[trigramIdx] || 0; + bits ^= (1 << (line - 1)); // flip the line + // Map back + const decoding = [8, 7, 6, 2, 5, 3, 4, 1]; // bit pattern → index + return decoding[bits] || trigramIdx; +} + +function getElementRelationship(body: string, func: string): string { + const cycle: Record = { '木': '火', '火': '土', '土': '金', '金': '水', '水': '木' }; + const reverse: Record = { '木': '水', '水': '金', '金': '土', '土': '火', '火': '木' }; + + if (body === func) return '比和(体用相同,诸事顺利)'; + if (cycle[body] === func) return '体生用(泄气,宜守不宜攻)'; + if (reverse[body] === func) return '用生体(得力,有贵人相助)'; + if (cycle[func] === body) return '用克体(受制,诸事不顺)'; + if (reverse[func] === body) return '体克用(主动,需付出努力)'; + return '体用相生'; +} + +function createFallbackHexagram(upper: number, lower: number, changingLine: number): HexagramInfo { + const ut = TRIGRAMS[upper]; + const lt = TRIGRAMS[lower]; + return { + number: (upper - 1) * 8 + lower, + name: `${ut.name}${lt.name}`, + upperTrigram: ut, + lowerTrigram: lt, + changingLine, + interpretation: '此卦象需结合具体事理参详', + judgment: '', + image: '', + lines: ['','','','','',''], + }; +} diff --git a/web/packages/core/src/calculators/relationship.ts b/web/packages/core/src/calculators/relationship.ts new file mode 100644 index 0000000..f99b412 --- /dev/null +++ b/web/packages/core/src/calculators/relationship.ts @@ -0,0 +1,116 @@ +import { + HeavenStem, + EarthBranch, +} from 'tyme4ts'; + +/** Relationship classification for branch interactions */ +export interface BranchRelationship { + combine: boolean; // 六合 + threeCombine: boolean; // 三合 + opposite: boolean; // 六冲 + harm: boolean; // 六害 + punish: boolean; // 相刑 + formation: string | null; // 三合局名称 +} + +/** Three-combine formations (三合局) */ +const THREE_COMBINES: Record = { + '水局': { branches: ['申', '子', '辰'], name: '水局', element: '水' }, + '木局': { branches: ['亥', '卯', '未'], name: '木局', element: '木' }, + '火局': { branches: ['寅', '午', '戌'], name: '火局', element: '火' }, + '金局': { branches: ['巳', '酉', '丑'], name: '金局', element: '金' }, +}; + +/** Check branch relationships between two earth branches */ +export function getBranchRelationship(branchA: string, branchB: string): BranchRelationship { + const a = EarthBranch.fromName(branchA); + const b = EarthBranch.fromName(branchB); + + // Six combine (六合): each branch's combine partner + const combinePartner = a.getCombine(); + const hasCombine = combinePartner ? combinePartner.getName() === b.getName() : false; + + // Opposite (六冲): each branch's opposite + const oppositePartner = a.getOpposite(); + const hasOpposite = oppositePartner ? oppositePartner.getName() === b.getName() : false; + + // Harm (六害): each branch's harm partner + let hasHarm = false; + try { + const harmPartner = a.getHarm(); + hasHarm = harmPartner ? harmPartner.getName() === b.getName() : false; + } catch { /* harm may not be available */ } + + // Three combine (三合): check if both branches are in the same formation + let hasThreeCombine = false; + let formation: string | null = null; + for (const [name, info] of Object.entries(THREE_COMBINES)) { + if (info.branches.includes(branchA) && info.branches.includes(branchB)) { + hasThreeCombine = true; + formation = name; + break; + } + } + + // Punish (相刑) + const hasPunish = checkPunish(branchA, branchB); + + return { + combine: hasCombine, + threeCombine: hasThreeCombine, + opposite: hasOpposite, + harm: hasHarm, + punish: hasPunish, + formation, + }; +} + +/** Check for punishment relationship between two branches */ +function checkPunish(a: string, b: string): boolean { + // Self punishment (自刑) + const selfPunish = ['辰', '午', '酉', '亥']; + if (a === b && selfPunish.includes(a)) return true; + + // Classic punishment pairs + const punishPairs: [string, string][] = [ + ['寅', '巳'], ['巳', '申'], ['申', '寅'], // 无恩之刑 + ['丑', '戌'], ['戌', '未'], ['未', '丑'], // 恃势之刑 + ['子', '卯'], ['卯', '子'], // 无礼之刑 + ]; + + return punishPairs.some(([x, y]) => x === a && y === b); +} + +/** Get Ten Star relationship between two heavenly stems */ +export function getTenStarRelationship(subjectStem: string, objectStem: string): string { + try { + const s = HeavenStem.fromName(subjectStem); + const o = HeavenStem.fromName(objectStem); + return s.getTenStar(o).getName(); + } catch { + return ''; + } +} + +/** Check if two heavenly stems combine (天干合) */ +export function checkStemCombine(stemA: string, stemB: string): boolean { + const combinePairs: Record = { + '甲': '己', '己': '甲', + '乙': '庚', '庚': '乙', + '丙': '辛', '辛': '丙', + '丁': '壬', '壬': '丁', + '戊': '癸', '癸': '戊', + }; + return combinePairs[stemA] === stemB; +} + +/** Check if two heavenly stems oppose (天干冲) */ +export function checkStemOpposite(stemA: string, stemB: string): boolean { + const oppositePairs: Record = { + '甲': '庚', '庚': '甲', + '乙': '辛', '辛': '乙', + '丙': '壬', '壬': '丙', + '丁': '癸', '癸': '丁', + }; + return oppositePairs[stemA] === stemB; +} diff --git a/web/packages/core/src/calculators/shensha.ts b/web/packages/core/src/calculators/shensha.ts new file mode 100644 index 0000000..793db1b --- /dev/null +++ b/web/packages/core/src/calculators/shensha.ts @@ -0,0 +1,359 @@ +/** + * 八字神煞 (Shen Sha) + * 神煞是固定规则查表 + 四柱匹配的标记,规则来源《三命通会》《渊海子平》。 + * 注意:部分神煞网上存在版本差异,本模块采用主流排盘工具通行版本。 + */ +import type { EightCharInfo, PillarInfo } from '../types/bazi'; + +export interface ShenshaInfo { + name: string; + /** 吉/凶/中性 */ + type: '吉' | '凶' | '中性'; + /** 分类:贵人/文星/财禄/感情/变动/威权/灾煞/孤独/格局 */ + category: string; + /** 命中查法说明,如 "日干己查四支" */ + anchors: string[]; + /** 命中位置,如 ["年支","时支"] */ + foundIn: string[]; + /** 吉凶含义简述 */ + description: string; +} + +interface ShenshaRule { + name: string; + type: ShenshaInfo['type']; + category: string; + description: string; + /** 按天干(年干/日干)查四支 */ + byStem?: Record; + /** 按地支(年支/日支)查四支 */ + byBranch?: Record; + /** 按月支查四干 */ + byMonthStem?: Record; + /** 日柱特殊格局 */ + byDayPillar?: string[]; +} + +const PILLAR_KEYS = ['year', 'month', 'day', 'hour'] as const; + +const RULES: ShenshaRule[] = [ + { + name: '天乙贵人', + type: '吉', + category: '贵人', + description: '最吉之神煞,主逢凶化吉、贵人相助', + byStem: { + '甲': ['丑', '未'], '戊': ['丑', '未'], + '乙': ['子', '申'], '己': ['子', '申'], + '丙': ['亥', '酉'], '丁': ['亥', '酉'], + '壬': ['卯', '巳'], '癸': ['卯', '巳'], + '辛': ['寅', '午'], + }, + }, + { + name: '天厨贵人', + type: '吉', + category: '贵人', + description: '主口福与衣食之禄,衣食无忧', + byStem: { + '甲': ['巳'], '乙': ['午'], '丙': ['巳'], '丁': ['午'], + '戊': ['申'], '己': ['酉'], '庚': ['亥'], '辛': ['子'], + '壬': ['寅'], '癸': ['卯'], + }, + }, + { + name: '文昌贵人', + type: '吉', + category: '文星', + description: '主聪明好学、利科名学业', + byStem: { + '甲': ['巳'], '乙': ['午'], '丙': ['申'], '丁': ['酉'], + '戊': ['申'], '己': ['酉'], '庚': ['亥'], '辛': ['子'], + '壬': ['寅'], '癸': ['卯'], + }, + }, + { + name: '禄神', + type: '吉', + category: '财禄', + description: '日干之禄,主衣禄与财运根基', + byStem: { + '甲': ['寅'], '乙': ['卯'], '丙': ['巳'], '丁': ['午'], + '戊': ['巳'], '己': ['午'], '庚': ['申'], '辛': ['酉'], + '壬': ['亥'], '癸': ['子'], + }, + }, + { + name: '羊刃', + type: '凶', + category: '灾煞', + description: '刚烈冲动之星,主脾气刚猛,喜用则有权柄', + byStem: { + '甲': ['卯'], '乙': ['寅'], '丙': ['午'], '丁': ['巳'], + '戊': ['午'], '己': ['巳'], '庚': ['酉'], '辛': ['申'], + '壬': ['子'], '癸': ['亥'], + }, + }, + { + name: '金舆', + type: '吉', + category: '财禄', + description: '禄前二位,主婚恋顺遂、富贵安逸', + byStem: { + '甲': ['辰'], '乙': ['巳'], '丙': ['未'], '丁': ['申'], + '戊': ['未'], '己': ['申'], '庚': ['戌'], '辛': ['亥'], + '壬': ['丑'], '癸': ['寅'], + }, + }, + { + name: '天德贵人', + type: '吉', + category: '贵人', + description: '主心地仁慈、逢凶化吉,利化解灾厄', + byMonthStem: { + '寅': ['丁'], '卯': ['申'], '辰': ['壬'], '巳': ['辛'], + '午': ['亥'], '未': ['甲'], '申': ['癸'], '酉': ['寅'], + '戌': ['丙'], '亥': ['乙'], '子': ['巳'], '丑': ['庚'], + }, + }, + { + name: '月德贵人', + type: '吉', + category: '贵人', + description: '主福荫深厚、遇难呈祥', + byMonthStem: { + '寅': ['丙'], '午': ['丙'], '戌': ['丙'], + '申': ['壬'], '子': ['壬'], '辰': ['壬'], + '亥': ['甲'], '卯': ['甲'], '未': ['甲'], + '巳': ['庚'], '酉': ['庚'], '丑': ['庚'], + }, + }, + { + name: '桃花(咸池)', + type: '中性', + category: '感情', + description: '主异性缘、魅力与风流,利艺术才华', + byBranch: { + '申': ['酉'], '子': ['酉'], '辰': ['酉'], + '寅': ['卯'], '午': ['卯'], '戌': ['卯'], + '巳': ['午'], '酉': ['午'], '丑': ['午'], + '亥': ['子'], '卯': ['子'], '未': ['子'], + }, + }, + { + name: '驿马', + type: '中性', + category: '变动', + description: '主奔波变动、外出发展,动中得财', + byBranch: { + '申': ['寅'], '子': ['寅'], '辰': ['寅'], + '寅': ['申'], '午': ['申'], '戌': ['申'], + '巳': ['亥'], '酉': ['亥'], '丑': ['亥'], + '亥': ['巳'], '卯': ['巳'], '未': ['巳'], + }, + }, + { + name: '华盖', + type: '中性', + category: '孤独', + description: '主艺术天赋、悟性高,也主清高孤傲', + byBranch: { + '申': ['辰'], '子': ['辰'], '辰': ['辰'], + '寅': ['戌'], '午': ['戌'], '戌': ['戌'], + '巳': ['丑'], '酉': ['丑'], '丑': ['丑'], + '亥': ['未'], '卯': ['未'], '未': ['未'], + }, + }, + { + name: '劫煞', + type: '凶', + category: '灾煞', + description: '主突发变故、是非破财,宜谨慎', + byBranch: { + '申': ['巳'], '子': ['巳'], '辰': ['巳'], + '寅': ['亥'], '午': ['亥'], '戌': ['亥'], + '巳': ['寅'], '酉': ['寅'], '丑': ['寅'], + '亥': ['申'], '卯': ['申'], '未': ['申'], + }, + }, + { + name: '亡神', + type: '凶', + category: '灾煞', + description: '主心机深、谋略强,亦主口舌是非', + byBranch: { + '申': ['亥'], '子': ['亥'], '辰': ['亥'], + '寅': ['巳'], '午': ['巳'], '戌': ['巳'], + '巳': ['申'], '酉': ['申'], '丑': ['申'], + '亥': ['寅'], '卯': ['寅'], '未': ['寅'], + }, + }, + { + name: '将星', + type: '吉', + category: '威权', + description: '主领导才能与威严,掌权柄之星', + byBranch: { + '申': ['子'], '子': ['子'], '辰': ['子'], + '寅': ['午'], '午': ['午'], '戌': ['午'], + '巳': ['酉'], '酉': ['酉'], '丑': ['酉'], + '亥': ['卯'], '卯': ['卯'], '未': ['卯'], + }, + }, + { + name: '红鸾', + type: '吉', + category: '感情', + description: '主婚恋喜事、姻缘早成', + byBranch: { + '子': ['卯'], '丑': ['寅'], '寅': ['丑'], '卯': ['子'], + '辰': ['亥'], '巳': ['戌'], '午': ['酉'], '未': ['申'], + '申': ['未'], '酉': ['午'], '戌': ['巳'], '亥': ['辰'], + }, + }, + { + name: '天喜', + type: '吉', + category: '感情', + description: '红鸾对宫,主喜事临门、人缘佳', + byBranch: { + '子': ['酉'], '丑': ['申'], '寅': ['未'], '卯': ['午'], + '辰': ['巳'], '巳': ['辰'], '午': ['卯'], '未': ['寅'], + '申': ['丑'], '酉': ['子'], '戌': ['亥'], '亥': ['戌'], + }, + }, + { + name: '孤辰', + type: '凶', + category: '孤独', + description: '主孤独离群、六亲缘薄', + byBranch: { + '亥': ['寅'], '子': ['寅'], '丑': ['寅'], + '寅': ['巳'], '卯': ['巳'], '辰': ['巳'], + '巳': ['申'], '午': ['申'], '未': ['申'], + '申': ['亥'], '酉': ['亥'], '戌': ['亥'], + }, + }, + { + name: '寡宿', + type: '凶', + category: '孤独', + description: '主孤寡之象,婚恋宜晚', + byBranch: { + '亥': ['戌'], '子': ['戌'], '丑': ['戌'], + '寅': ['丑'], '卯': ['丑'], '辰': ['丑'], + '巳': ['辰'], '午': ['辰'], '未': ['辰'], + '申': ['未'], '酉': ['未'], '戌': ['未'], + }, + }, + { + name: '天罗', + type: '凶', + category: '灾煞', + description: '戌亥为天罗,主困顿束缚,男命尤忌', + byBranch: { '子': ['戌', '亥'], '丑': ['戌', '亥'], '寅': ['戌', '亥'], '卯': ['戌', '亥'], '辰': ['戌', '亥'], '巳': ['戌', '亥'], '午': ['戌', '亥'], '未': ['戌', '亥'], '申': ['戌', '亥'], '酉': ['戌', '亥'], '戌': ['戌', '亥'], '亥': ['戌', '亥'] }, + }, + { + name: '地网', + type: '凶', + category: '灾煞', + description: '辰巳为地网,主束缚波折,女命尤忌', + byBranch: { '子': ['辰', '巳'], '丑': ['辰', '巳'], '寅': ['辰', '巳'], '卯': ['辰', '巳'], '辰': ['辰', '巳'], '巳': ['辰', '巳'], '午': ['辰', '巳'], '未': ['辰', '巳'], '申': ['辰', '巳'], '酉': ['辰', '巳'], '戌': ['辰', '巳'], '亥': ['辰', '巳'] }, + }, + { + name: '魁罡', + type: '中性', + category: '格局', + description: '日柱魁罡,主刚毅果断、聪明果敢,忌刑冲', + byDayPillar: ['庚辰', '庚戌', '壬辰', '戊戌'], + }, + { + name: '阴差阳错', + type: '凶', + category: '格局', + description: '日柱阴差阳错,主婚姻不顺、易生波折', + byDayPillar: ['丙子', '丁丑', '戊寅', '辛卯', '壬辰', '癸巳', '丙午', '丁未', '戊申', '辛酉', '壬戌', '癸亥'], + }, +]; + +const PILLAR_LABELS: Record = { + yearStem: '年干', monthStem: '月干', dayStem: '日干', hourStem: '时干', + yearBranch: '年支', monthBranch: '月支', dayBranch: '日支', hourBranch: '时支', +}; + +/** Analyze Shen Sha presence in a Bazi chart */ +export function analyzeShensha(bazi: EightCharInfo): ShenshaInfo[] { + const pillars: Record = { + year: bazi.yearPillar, month: bazi.monthPillar, day: bazi.dayPillar, hour: bazi.hourPillar, + }; + const stems: Record = {}; + const branches: Record = {}; + for (const k of PILLAR_KEYS) { + stems[`${k}Stem`] = pillars[k].heavenStem; + branches[`${k}Branch`] = pillars[k].earthBranch; + } + const dayGanzhi = pillars.day.ganzhi; + + const result: ShenshaInfo[] = []; + + for (const rule of RULES) { + const anchors: string[] = []; + const foundIn = new Set(); + + if (rule.byStem) { + // 年干、日干查四支 + for (const anchorKey of ['yearStem', 'dayStem'] as const) { + const targets = rule.byStem[stems[anchorKey]]; + if (!targets) continue; + const hits = PILLAR_KEYS.filter(k => targets.includes(branches[`${k}Branch`])); + if (hits.length > 0) { + anchors.push(PILLAR_LABELS[anchorKey]); + hits.forEach(k => foundIn.add(PILLAR_LABELS[`${k}Branch`])); + } + } + } + + if (rule.byBranch) { + // 年支、日支查四支 + for (const anchorKey of ['yearBranch', 'dayBranch'] as const) { + const targets = rule.byBranch[branches[anchorKey]]; + if (!targets) continue; + const hits = PILLAR_KEYS.filter(k => targets.includes(branches[`${k}Branch`])); + if (hits.length > 0) { + anchors.push(PILLAR_LABELS[anchorKey]); + hits.forEach(k => foundIn.add(PILLAR_LABELS[`${k}Branch`])); + } + } + } + + if (rule.byMonthStem) { + // 月支查四干 + const targets = rule.byMonthStem[branches.monthBranch]; + if (targets) { + const hits = PILLAR_KEYS.filter(k => targets.includes(stems[`${k}Stem`])); + if (hits.length > 0) { + anchors.push('月支'); + hits.forEach(k => foundIn.add(PILLAR_LABELS[`${k}Stem`])); + } + } + } + + if (rule.byDayPillar && rule.byDayPillar.includes(dayGanzhi)) { + anchors.push('日柱'); + foundIn.add('日柱'); + } + + if (foundIn.size > 0) { + result.push({ + name: rule.name, + type: rule.type, + category: rule.category, + anchors, + foundIn: [...foundIn], + description: rule.description, + }); + } + } + + return result; +} diff --git a/web/packages/core/src/index.ts b/web/packages/core/src/index.ts new file mode 100644 index 0000000..a9a4830 --- /dev/null +++ b/web/packages/core/src/index.ts @@ -0,0 +1,84 @@ +// Types +export type { + DayInfo, + AlmanacInfo, + HourAlmanac, + PillarInfo, + HideStemInfo, + EightCharInfo, + DecadeFortuneInfo, + FortuneInfo, + ChildLimitInfo, + BaziFullResult, + PillarRelationship, + DailyFortuneResult, +} from './types'; + +// Transformers +export { + solarDayToDayInfo, + getMonthCalendar, + getDayInfo, + getTodayInfo, +} from './transformers/day'; + +export { + solarDayToAlmanacInfo, + getAlmanacInfo, +} from './transformers/almanac'; + +export { + birthInfoToBazi, +} from './transformers/bazi'; +export type { BirthParams } from './transformers/bazi'; + +export { + getYearMonths, +} from './transformers/yearMonths'; +export type { YearMonthInfo } from './transformers/yearMonths'; + +// Calculators +export { + getBranchRelationship, + getTenStarRelationship, + checkStemCombine, + checkStemOpposite, +} from './calculators/relationship'; +export type { BranchRelationship } from './calculators/relationship'; + +export { + calculateDailyFortune, +} from './calculators/dailyMatch'; + +export { + analyzeElementBalance, +} from './calculators/elementStrength'; +export type { ElementProfile } from './calculators/elementStrength'; + +export { + calculatePlumBlossom, +} from './calculators/plumBlossom'; +export type { + TrigramInfo, + HexagramInfo, + PlumBlossomResult, +} from './calculators/plumBlossom'; + +export { + calculateBoneWeight, +} from './calculators/boneWeight'; +export type { BoneWeightResult } from './calculators/boneWeight'; + +export { + getBuddhistFestival, +} from './calculators/buddhistDates'; + +export { + analyzeShensha, +} from './calculators/shensha'; +export type { ShenshaInfo } from './calculators/shensha'; + +export { + analyzeFortuneGanzhi, +} from './calculators/fortuneLuck'; +export type { FortuneLuck } from './calculators/fortuneLuck'; diff --git a/web/packages/core/src/transformers/almanac.ts b/web/packages/core/src/transformers/almanac.ts new file mode 100644 index 0000000..4fabc75 --- /dev/null +++ b/web/packages/core/src/transformers/almanac.ts @@ -0,0 +1,186 @@ +import { + SolarDay, + type LunarHour, +} from 'tyme4ts'; +import type { AlmanacInfo, HourAlmanac } from '../types/almanac'; + +/** Transform a SolarDay into a full AlmanacInfo object */ +export function solarDayToAlmanacInfo(solarDay: SolarDay): AlmanacInfo { + const lunarDay = solarDay.getLunarDay(); + const sixtyCycle = lunarDay.getSixtyCycle(); + const stem = sixtyCycle.getHeavenStem(); + const branch = sixtyCycle.getEarthBranch(); + + // Duty officer + const duty = lunarDay.getDuty(); + + // Twelve star + const twelveStar = lunarDay.getTwelveStar(); + const ecliptic = twelveStar.getEcliptic(); + + // Twenty-eight star + const twentyEightStar = lunarDay.getTwentyEightStar(); + + // Nine star + const nineStar = lunarDay.getNineStar(); + + // Six star + const sixStar = lunarDay.getSixStar ? lunarDay.getSixStar().getName() : ''; + + // Minor Ren + const minorRen = lunarDay.getMinorRen ? lunarDay.getMinorRen() : null; + + // Fetus + const fetusDay = lunarDay.getFetusDay(); + + // Recommendations and avoidances + let recommends: string[] = []; + let avoids: string[] = []; + try { + recommends = lunarDay.getRecommends().map(r => r.getName()); + avoids = lunarDay.getAvoids().map(a => a.getName()); + } catch { /* may throw for some dates */ } + + // Gods + const goodGods: string[] = []; + const badGods: string[] = []; + try { + const gods = lunarDay.getGods(); + for (const god of gods) { + const luck = god.getLuck(); + if (luck) { + if (luck.getName() === '吉') { + goodGods.push(god.getName()); + } else { + badGods.push(god.getName()); + } + } + } + } catch { /* may throw */ } + + // Peng Zu taboos + const pengZu = sixtyCycle.getPengZu(); + + // Branch relationships + const opposite = branch.getOpposite(); + const harm = branch.getHarm(); + const combine = branch.getCombine(); + + // Evil direction + const ominous = branch.getOminous ? branch.getOminous() : null; + + // Na Yin + const nayin = sixtyCycle.getSound().getName(); + + // Moon phase + let phase = ''; + try { + const p = lunarDay.getPhase(); + if (p) phase = p.getName(); + } catch { /* no phase info */ } + + // Hourly almanac + const hourDetails = buildHourlyAlmanac(lunarDay.getHours(), recommends, avoids); + + // Determine duty luck + const dutyName = duty.getName(); + const luckyDuties = ['除', '执', '危', '成', '开']; + const unluckyDuties = ['建', '满', '平', '破', '收', '闭']; + let dutyLuck: 'good' | 'bad' | 'neutral' = 'neutral'; + if (luckyDuties.includes(dutyName)) dutyLuck = 'good'; + else if (unluckyDuties.includes(dutyName)) dutyLuck = 'bad'; + // Note: duty luck also depends on day branch; simplified here + + return { + duty: dutyName, + dutyLuck, + twelveStar: { + name: twelveStar.getName(), + ecliptic: ecliptic ? ecliptic.getName() : '', + luck: ecliptic && ecliptic.getName() === '黄道' ? 'good' : 'bad', + }, + twentyEightStar: { + name: twentyEightStar.getName(), + luck: twentyEightStar.getLuck()?.getName() === '吉' ? 'good' : 'bad', + animal: twentyEightStar.getAnimal()?.getName() || '', + }, + nineStar: { + name: nineStar.getName(), + color: nineStar.getColor ? nineStar.getColor() : '', + element: nineStar.getElement ? nineStar.getElement().getName() : '', + }, + sixStar, + minorRen: minorRen ? { + name: minorRen.getName(), + luck: minorRen.getLuck()?.getName() === '吉' ? 'good' : 'bad', + element: minorRen.getElement()?.getName() || '', + } : { name: '', luck: 'bad' as const, element: '' }, + phase, + fetus: { + direction: fetusDay.getDirection()?.getName() || '', + side: fetusDay.getSide() !== undefined ? (fetusDay.getSide() as unknown as number === 0 ? '房内' : '房外') : '', + position: fetusDay.getName(), + }, + recommends, + avoids, + goodGods, + badGods, + dayStem: stem.getName(), + dayBranch: branch.getName(), + dayGanzhi: sixtyCycle.getName(), + pengZu: pengZu.getName(), + pengZuStem: pengZu.getPengZuHeavenStem()?.getName() || '', + pengZuBranch: pengZu.getPengZuEarthBranch()?.getName() || '', + clash: `${opposite.getZodiac().getName()}(${opposite.getName()})`, + harm: harm ? `${harm.getZodiac().getName()}(${harm.getName()})` : '', + combine: combine ? `${combine.getZodiac().getName()}(${combine.getName()})` : '', + evilDirection: ominous ? ominous.getName() : '', + nayin, + hourDetails, + }; +} + +function buildHourlyAlmanac(hours: LunarHour[], _dayRecommends: string[], _dayAvoids: string[]): HourAlmanac[] { + return hours.map(hour => { + const hourSixtyCycle = hour.getSixtyCycle(); + const twelveStar = hour.getTwelveStar ? hour.getTwelveStar() : null; + const nineStar = hour.getNineStar ? hour.getNineStar() : null; + + let hourRecommends: string[] = []; + let hourAvoids: string[] = []; + try { + hourRecommends = hour.getRecommends().map(r => r.getName()); + hourAvoids = hour.getAvoids().map(a => a.getName()); + } catch { /* may throw */ } + + const branchName = hourSixtyCycle.getEarthBranch().getName(); + const hourNames: Record = { + '子': '子时', '丑': '丑时', '寅': '寅时', '卯': '卯时', + '辰': '辰时', '巳': '巳时', '午': '午时', '未': '未时', + '申': '申时', '酉': '酉时', '戌': '戌时', '亥': '亥时', + }; + const hourRanges: Record = { + '子': '23:00-01:00', '丑': '01:00-03:00', '寅': '03:00-05:00', + '卯': '05:00-07:00', '辰': '07:00-09:00', '巳': '09:00-11:00', + '午': '11:00-13:00', '未': '13:00-15:00', '申': '15:00-17:00', + '酉': '17:00-19:00', '戌': '19:00-21:00', '亥': '21:00-23:00', + }; + + return { + branch: branchName, + name: hourNames[branchName] || branchName, + range: hourRanges[branchName] || '', + ganzhi: hourSixtyCycle.getName(), + recommends: hourRecommends, + avoids: hourAvoids, + twelveStar: twelveStar?.getName() || '', + nineStar: nineStar?.getName() || '', + }; + }); +} + +/** Get AlmanacInfo for a specific date */ +export function getAlmanacInfo(year: number, month: number, day: number): AlmanacInfo { + const solarDay = SolarDay.fromYmd(year, month, day); + return solarDayToAlmanacInfo(solarDay); +} diff --git a/web/packages/core/src/transformers/bazi.ts b/web/packages/core/src/transformers/bazi.ts new file mode 100644 index 0000000..de9cda3 --- /dev/null +++ b/web/packages/core/src/transformers/bazi.ts @@ -0,0 +1,212 @@ +import { + SolarTime, + Gender, + ChildLimit, + HideHeavenStemType, + YinYang, + type SixtyCycle, + type HeavenStem, +} from 'tyme4ts'; +import type { + PillarInfo, + HideStemInfo, + DecadeFortuneInfo, + FortuneInfo, + BaziFullResult, +} from '../types/bazi'; + +export interface BirthParams { + year: number; + month: number; + day: number; + hour: number; + minute: number; + gender: 'male' | 'female'; + /** 八字流派:lateZiNextDay=晚子时算次日(23点换日,默认);earlyZiSameDay=晚子时算当日(0点换日) */ + ziSect?: 'lateZiNextDay' | 'earlyZiSameDay'; +} + +/** Transform birth parameters into full Bazi result */ +export function birthInfoToBazi(params: BirthParams): BaziFullResult { + const { year, month, day, hour, minute, gender, ziSect = 'lateZiNextDay' } = params; + + const solarTime = SolarTime.fromYmdHms(year, month, day, hour, minute, 0); + // 早子时流派:23:00 后出生按当日早子时排四柱(日柱不换日),起运仍按真实出生时间 + const ziHour = + ziSect === 'earlyZiSameDay' && hour >= 23 + ? SolarTime.fromYmdHms(year, month, day, 0, minute, 0) + : solarTime; + const lunarHour = ziHour.getLunarHour(); + const eightChar = lunarHour.getEightChar(); + + const yearPillar = eightChar.getYear(); + const monthPillar = eightChar.getMonth(); + const dayPillar = eightChar.getDay(); + const hourPillar = eightChar.getHour(); + + // Day master + const dayStem = dayPillar.getHeavenStem(); + const dayMasterStem = dayStem.getName(); + const dayMasterElement = dayStem.getElement().getName(); + + // Extract pillars with Ten Star relative to day master + const yearPillarInfo = extractPillarInfo(yearPillar, dayStem); + const monthPillarInfo = extractPillarInfo(monthPillar, dayStem); + const dayPillarInfo = extractPillarInfo(dayPillar, dayStem); + const hourPillarInfo = extractPillarInfo(hourPillar, dayStem); + + // Fetal origin, fetal breath, own sign, body sign + const fetalOrigin = eightChar.getFetalOrigin(); + const fetalBreath = eightChar.getFetalBreath(); + const ownSign = eightChar.getOwnSign(); + const bodySign = eightChar.getBodySign(); + + // Empty branches + const emptyTen = dayPillar.getTen(); + const emptyBranches = emptyTen ? dayPillar.getExtraEarthBranches().map(b => b.getName()) : []; + + // Child limit + const genderEnum = gender === 'male' ? Gender.MAN : Gender.WOMAN; + const childLimit = ChildLimit.fromSolarTime(solarTime, genderEnum); + const startDecade = childLimit.getStartDecadeFortune(); + const startFortune = childLimit.getStartFortune(); + + // Decade fortunes (大运) — 10 decades + const decadeFortunes: DecadeFortuneInfo[] = []; + let currentDecade = startDecade; + for (let i = 0; i < 10 && currentDecade; i++) { + const sc = currentDecade.getSixtyCycle(); + decadeFortunes.push({ + index: i, + ganzhi: sc.getName(), + startAge: currentDecade.getStartAge(), + endAge: currentDecade.getEndAge(), + startYear: currentDecade.getStartLunarYear().getYear(), + endYear: currentDecade.getEndLunarYear().getYear(), + heavenStem: sc.getHeavenStem().getName(), + earthBranch: sc.getEarthBranch().getName(), + nayin: sc.getSound().getName(), + }); + currentDecade = currentDecade.next(1) as typeof currentDecade; + } + + // Annual fortunes (流年) — 10 years + const annualFortunes: FortuneInfo[] = []; + let currentFortune = startFortune; + for (let i = 0; i < 10 && currentFortune; i++) { + const sc = currentFortune.getSixtyCycle(); + annualFortunes.push({ + age: currentFortune.getAge(), + year: currentFortune.getLunarYear().getYear(), + ganzhi: sc.getName(), + nayin: sc.getSound().getName(), + }); + currentFortune = currentFortune.next(1) as typeof currentFortune; + } + + return { + eightChar: { + birthDate: `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`, + birthTime: `${String(hour).padStart(2, '0')}:${String(minute).padStart(2, '0')}`, + gender, + yearPillar: yearPillarInfo, + monthPillar: monthPillarInfo, + dayPillar: dayPillarInfo, + hourPillar: hourPillarInfo, + dayMaster: `${dayMasterStem}${dayMasterElement}`, + dayMasterStem, + dayMasterElement, + fetalOrigin: fetalOrigin.getName(), + fetalBreath: fetalBreath.getName(), + ownSign: ownSign.getName(), + bodySign: bodySign.getName(), + emptyBranches, + emptyTen: emptyTen ? emptyTen.getName() : '', + }, + childLimit: { + startTime: childLimit.getStartTime().toString(), + endTime: childLimit.getEndTime().toString(), + yearCount: childLimit.getYearCount(), + monthCount: childLimit.getMonthCount(), + dayCount: childLimit.getDayCount(), + hourCount: childLimit.getHourCount(), + minuteCount: childLimit.getMinuteCount(), + forward: childLimit.isForward(), + startAge: childLimit.getStartAge(), + endAge: childLimit.getEndAge(), + }, + decadeFortunes, + annualFortunes, + }; +} + +/** Extract pillar info from a SixtyCycle, with Ten Star relative to day master */ +function extractPillarInfo(sixtyCycle: SixtyCycle, dayMasterStem: HeavenStem): PillarInfo { + const stem = sixtyCycle.getHeavenStem(); + const branch = sixtyCycle.getEarthBranch(); + const sound = sixtyCycle.getSound(); + + // Hidden stems + const hideStems: HideStemInfo[] = []; + try { + const allHideStems = branch.getHideHeavenStems(); + if (allHideStems) { + for (const hs of allHideStems) { + let typeName = ''; + const type = hs.getType(); + if (type === HideHeavenStemType.MAIN) typeName = '本气'; + else if (type === HideHeavenStemType.MIDDLE) typeName = '中气'; + else if (type === HideHeavenStemType.RESIDUAL) typeName = '余气'; + + let tenStarName: string | null = null; + try { + tenStarName = hs.getHeavenStem().getTenStar(dayMasterStem).getName(); + } catch { /* ten star may not be available */ } + + hideStems.push({ + stem: hs.getHeavenStem().getName(), + type: typeName, + tenStar: tenStarName, + }); + } + } + } catch { /* hide stems may not be available */ } + + // Terrain (十二长生) + let terrainName = ''; + let terrainFortune: 'good' | 'bad' | 'neutral' = 'neutral'; + try { + const terrain = stem.getTerrain(branch); + terrainName = terrain.getName(); + const goodTerrain = ['长生', '冠带', '临官', '帝旺', '胎', '养']; + const badTerrain = ['死', '墓', '绝']; + if (goodTerrain.includes(terrainName)) terrainFortune = 'good'; + else if (badTerrain.includes(terrainName)) terrainFortune = 'bad'; + } catch { /* terrain might fail */ } + + // Ten Star + let tenStarName: string | null = null; + try { + tenStarName = stem.getTenStar(dayMasterStem).getName(); + } catch { /* ten star may not be available */ } + + const yinYang = stem.getYinYang(); + const branchYinYang = branch.getYinYang(); + + return { + ganzhi: sixtyCycle.getName(), + heavenStem: stem.getName(), + earthBranch: branch.getName(), + elementStem: stem.getElement().getName(), + elementBranch: branch.getElement().getName(), + yinYangStem: yinYang === YinYang.YANG ? 'yang' : 'yin', + yinYangBranch: branchYinYang === YinYang.YANG ? 'yang' : 'yin', + hideStems, + nayin: sound.getName(), + terrain: { + name: terrainName, + fortune: terrainFortune, + }, + tenStar: tenStarName, + }; +} diff --git a/web/packages/core/src/transformers/day.ts b/web/packages/core/src/transformers/day.ts new file mode 100644 index 0000000..90992d4 --- /dev/null +++ b/web/packages/core/src/transformers/day.ts @@ -0,0 +1,201 @@ +import { + SolarDay, + SolarMonth, +} from 'tyme4ts'; +import type { DayInfo } from '../types/calendar'; +import { getBuddhistFestival } from '../calculators/buddhistDates'; + +const SEASON_BY_MONTH: Record = { + 3: '春季', 4: '春季', 5: '春季', + 6: '夏季', 7: '夏季', 8: '夏季', + 9: '秋季', 10: '秋季', 11: '秋季', + 12: '冬季', 1: '冬季', 2: '冬季', +}; + +/** Transform a SolarDay into a plain DayInfo object */ +export function solarDayToDayInfo(solarDay: SolarDay): DayInfo { + const lunarDay = solarDay.getLunarDay(); + const lunarMonth = lunarDay.getLunarMonth(); + const week = solarDay.getWeek(); + const constellation = solarDay.getConstellation(); + const term = solarDay.getTerm(); + const termDay = solarDay.getTermDay(); + const month = solarDay.getSolarMonth().getMonth(); + const solarYear = solarDay.getSolarMonth().getSolarYear().getYear(); + + // Solar term + const isTermDay = termDay !== null; + const solarTerm = isTermDay ? term.getName() : null; + let solarTermTime: string | null = null; + if (isTermDay) { + try { + const jd = term.getJulianDay(); + if (jd) { + const st = jd.getSolarTime(); + solarTermTime = `${String(st.getHour()).padStart(2,'0')}:${String(st.getMinute()).padStart(2,'0')}`; + } + } catch { /* time not available */ } + } + + // Season + term progress (every day belongs to a solar term) + const season = SEASON_BY_MONTH[month] || ''; + let currentSolarTerm: string | null = null; + let termDayIndex: number | null = null; + let nextSolarTerm: string | null = null; + let daysToNextTerm: number | null = null; + try { + const currentTerm = solarDay.getTerm(); + const nextTerm = currentTerm.next(1); + currentSolarTerm = currentTerm.getName(); + termDayIndex = solarDay.subtract(currentTerm.getSolarDay()) + 1; + nextSolarTerm = nextTerm.getName(); + daysToNextTerm = nextTerm.getSolarDay().subtract(solarDay); + } catch { /* term may fail */ } + + // Julian day, Buddhist era, Islamic/Hijri date + let julianDay: number | null = null; + try { julianDay = solarDay.getJulianDay().getDay(); } catch { /* */ } + const buddhistYear = solarYear + 543; + let hijriDate: string | null = null; + try { + const hd = solarDay.getHijriDay(); + const hm = hd.getHijriMonth(); + hijriDate = `${hm.getHijriYear().getYear()}年${String(hm.getIndexInYear()).padStart(2, '0')}月${String(hd.getDay()).padStart(2, '0')}日`; + } catch { /* */ } + const buddhistFestival = getBuddhistFestival(lunarMonth.getMonth(), lunarDay.getDay()); + + // Phenology + let phenology: string | null = null; + try { + const pd = solarDay.getPhenologyDay(); + if (pd) phenology = pd.getPhenology().getName(); + } catch { /* not available for all dates */ } + + // Dog days + let dogDay: string | null = null; + try { + const dd = solarDay.getDogDay(); + if (dd) dogDay = dd.getDog().getName(); + } catch { /* not in dog days */ } + + // Nine-day cold + let nineDay: string | null = null; + try { + const nd = solarDay.getNineDay(); + if (nd) nineDay = nd.getNine().getName(); + } catch { /* not in nine days */ } + + // Moon phase + let moonPhase: string | null = null; + try { + const phase = solarDay.getPhase(); + if (phase) moonPhase = phase.getName(); + } catch { /* not available */ } + + // Festivals + let lunarFestival: string | null = null; + try { + const lf = lunarDay.getFestival(); + if (lf) lunarFestival = lf.getName(); + } catch { /* no festival */ } + + let solarFestival: string | null = null; + try { + const sf = solarDay.getFestival(); + if (sf) solarFestival = sf.getName(); + } catch { /* no festival */ } + + let legalHoliday: { name: string; isWork: boolean } | null = null; + try { + const lh = solarDay.getLegalHoliday(); + if (lh) legalHoliday = { name: lh.getName(), isWork: lh.isWork() }; + } catch { /* not a legal holiday */ } + + // Gan-Zhi + const daySixtyCycle = lunarDay.getSixtyCycle(); + const yearSixtyCycle = lunarDay.getYearSixtyCycle(); + const monthSixtyCycle = lunarDay.getMonthSixtyCycle(); + + // Today check + const now = new Date(); + const isToday = + solarDay.getSolarMonth().getSolarYear().getYear() === now.getFullYear() && + solarDay.getSolarMonth().getMonth() === now.getMonth() + 1 && + solarDay.getDay() === now.getDate(); + + const weekDayIndex = week.getIndex(); + const isWeekend = weekDayIndex === 0 || weekDayIndex === 6; + + return { + solarDate: `${solarDay.getSolarMonth().getSolarYear().getYear()}-${String(solarDay.getSolarMonth().getMonth()).padStart(2, '0')}-${String(solarDay.getDay()).padStart(2, '0')}`, + solarDay: solarDay.getDay(), + solarMonth: solarDay.getSolarMonth().getMonth(), + solarYear: solarDay.getSolarMonth().getSolarYear().getYear(), + weekDay: week.getName(), + weekDayIndex, + constellation: constellation.getName(), + solarTerm, + solarTermTime, + isTermDay, + currentSolarTerm, + season, + termDayIndex, + nextSolarTerm, + daysToNextTerm, + julianDay, + buddhistYear, + hijriDate, + buddhistFestival, + phenology, + dogDay, + nineDay, + lunarYear: lunarMonth.getLunarYear().getYear(), + lunarMonth: lunarMonth.getMonth(), + lunarMonthName: lunarMonth.getName(), + lunarDay: lunarDay.getDay(), + lunarDayName: lunarDay.getName(), + isLeapMonth: lunarMonth.isLeap(), + lunarYearGanzhi: yearSixtyCycle.getName(), + lunarMonthGanzhi: monthSixtyCycle.getName(), + lunarDayGanzhi: daySixtyCycle.getName(), + zodiac: daySixtyCycle.getEarthBranch().getZodiac().getName(), + lunarFestival, + solarFestival, + legalHoliday, + moonPhase, + isToday, + isWeekend, + dayOfWeek: weekDayIndex, + }; +} + +/** Get calendar days for a month as a 2D array (weeks × days) */ +export function getMonthCalendar(year: number, month: number, weekStart: 0 | 1 = 0): DayInfo[][] { + const solarMonth = SolarMonth.fromYm(year, month); + const weekCount = solarMonth.getWeekCount(weekStart); + const weeks = solarMonth.getWeeks(weekStart); + + const result: DayInfo[][] = []; + for (let w = 0; w < weekCount; w++) { + const week = weeks[w]; + const days = week.getDays(); + const row: DayInfo[] = []; + for (const day of days) { + row.push(solarDayToDayInfo(day)); + } + result.push(row); + } + return result; +} + +/** Get DayInfo for a specific date */ +export function getDayInfo(year: number, month: number, day: number): DayInfo { + const solarDay = SolarDay.fromYmd(year, month, day); + return solarDayToDayInfo(solarDay); +} + +/** Get today's DayInfo */ +export function getTodayInfo(): DayInfo { + const now = new Date(); + return getDayInfo(now.getFullYear(), now.getMonth() + 1, now.getDate()); +} diff --git a/web/packages/core/src/transformers/yearMonths.ts b/web/packages/core/src/transformers/yearMonths.ts new file mode 100644 index 0000000..6458828 --- /dev/null +++ b/web/packages/core/src/transformers/yearMonths.ts @@ -0,0 +1,59 @@ +/** + * 流月计算:按节气月(立春起 12 节)划分某公历年的 12 个流月 + */ +import { SolarTerm } from 'tyme4ts'; +import { getDayInfo } from './day'; + +export interface YearMonthInfo { + /** 0-11,从寅月(正月/立春)起 */ + index: number; + /** 正月..腊月 */ + name: string; + /** 起始公历月 */ + solarMonth: number; + /** 节日期 YYYY-MM-DD */ + startDate: string; + /** 月末 YYYY-MM-DD(下一节前一天) */ + endDate: string; + /** 月柱干支 */ + ganzhi: string; +} + +// SolarTerm 索引:冬至0 小寒1 大寒2 立春3 雨水4 惊蛰5 春分6 清明7 谷雨8 立夏9 小满10 芒种11 夏至12 小暑13 大暑14 立秋15 处暑16 白露17 秋分18 寒露19 霜降20 立冬21 小雪22 大雪23 +const JIE_INDICES = [3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 1]; // 立春..小寒(次年) + +const MONTH_NAME_BY_BRANCH: Record = { + '寅': '正月', '卯': '二月', '辰': '三月', '巳': '四月', '午': '五月', '未': '六月', + '申': '七月', '酉': '八月', '戌': '九月', '亥': '十月', '子': '冬月', '丑': '腊月', +}; + +function pad(n: number): string { + return String(n).padStart(2, '0'); +} + +function toDateStr(day: { getYear(): number; getMonth(): number; getDay(): number }): string { + return `${day.getYear()}-${pad(day.getMonth())}-${pad(day.getDay())}`; +} + +/** Get the 12 节气月(流月)of a solar year, starting from 立春 */ +export function getYearMonths(year: number): YearMonthInfo[] { + const months: YearMonthInfo[] = []; + for (let k = 0; k < 12; k++) { + const termYear = k === 11 ? year + 1 : year; + const start = SolarTerm.fromIndex(termYear, JIE_INDICES[k]).getSolarDay(); + const end = k === 11 + ? start + : SolarTerm.fromIndex(year, JIE_INDICES[k + 1]).getSolarDay().next(-1); + const di = getDayInfo(start.getYear(), start.getMonth(), start.getDay()); + const branch = di.lunarMonthGanzhi[1]; + months.push({ + index: k, + name: MONTH_NAME_BY_BRANCH[branch] || di.lunarMonthName || `${k + 1}月`, + solarMonth: start.getMonth(), + startDate: toDateStr(start), + endDate: toDateStr(end), + ganzhi: di.lunarMonthGanzhi, + }); + } + return months; +} diff --git a/web/packages/core/src/types/almanac.ts b/web/packages/core/src/types/almanac.ts new file mode 100644 index 0000000..4841859 --- /dev/null +++ b/web/packages/core/src/types/almanac.ts @@ -0,0 +1,89 @@ +/** Full almanac (黄历) information for a single day */ +export interface AlmanacInfo { + // Duty officer (建除十二值神) + duty: string; + dutyLuck: 'good' | 'bad' | 'neutral'; + + // Twelve star (黄道黑道十二神) + twelveStar: { + name: string; + ecliptic: string; // 黄道 or 黑道 + luck: 'good' | 'bad'; + }; + + // Twenty-eight lunar mansion (二十八星宿) + twentyEightStar: { + name: string; + luck: 'good' | 'bad'; + animal: string; + }; + + // Nine star (九星) + nineStar: { + name: string; + color: string; + element: string; + }; + + // Six star (六曜) + sixStar: string; + + // Minor Ren (小六壬) + minorRen: { + name: string; + luck: 'good' | 'bad'; + element: string; + }; + + // Moon phase + phase: string; + + // Fetus god (胎神) + fetus: { + direction: string; + side: string; // 房内/房外 + position: string; // Full position description + }; + + // Recommendations and avoidances (宜忌) + recommends: string[]; + avoids: string[]; + + // Gods (神煞) + goodGods: string[]; + badGods: string[]; + + // Day stem-branch info + dayStem: string; // 天干 e.g. "甲" + dayBranch: string; // 地支 e.g. "子" + dayGanzhi: string; // 干支 e.g. "甲子" + + // Peng Zu taboo (彭祖百忌) + pengZu: string; + pengZuStem: string; // 天干禁忌 + pengZuBranch: string; // 地支禁忌 + + // Chong/Sha/Harm/Combine (冲煞害合) + clash: string; // 冲 e.g. "马(午)" + harm: string; // 害 e.g. "羊(未)" + combine: string; // 合 e.g. "牛(丑)" + evilDirection: string;// 煞 e.g. "北" + + // Na Yin sound (纳音) + nayin: string; + + // Hourly almanac + hourDetails: HourAlmanac[]; +} + +/** Hourly almanac for each of the 12 two-hour periods (时辰) */ +export interface HourAlmanac { + branch: string; // 地支 e.g. "子" + name: string; // 时辰名 e.g. "子时" + range: string; // Time range e.g. "23:00-01:00" + ganzhi: string; // Hour pillar e.g. "甲子" + recommends: string[]; + avoids: string[]; + twelveStar: string; + nineStar: string; +} diff --git a/web/packages/core/src/types/bazi.ts b/web/packages/core/src/types/bazi.ts new file mode 100644 index 0000000..aa54454 --- /dev/null +++ b/web/packages/core/src/types/bazi.ts @@ -0,0 +1,93 @@ +/** A single pillar (柱) in the Bazi — year, month, day, or hour */ +export interface PillarInfo { + ganzhi: string; // "甲子" + heavenStem: string; // "甲" + earthBranch: string; // "子" + elementStem: string; // Stem's five element "木" + elementBranch: string; // Branch's five element "水" + yinYangStem: 'yin' | 'yang'; + yinYangBranch: 'yin' | 'yang'; + hideStems: HideStemInfo[]; + nayin: string; // Na Yin sound "海中金" + terrain: { + name: string; // 十二长生 stage + fortune: 'good' | 'bad' | 'neutral'; + }; + tenStar: string | null; // Ten Star relative to day master +} + +/** Hidden stem within an earth branch (藏干) */ +export interface HideStemInfo { + stem: string; // "甲" + type: string; // "本气" | "中气" | "余气" + tenStar: string | null; // Ten Star relative to day master +} + +/** Complete Bazi (八字) result for a birth date/time */ +export interface EightCharInfo { + birthDate: string; // ISO date + birthTime: string; // HH:mm + gender: 'male' | 'female'; + + yearPillar: PillarInfo; + monthPillar: PillarInfo; + dayPillar: PillarInfo; // Day master pillar + hourPillar: PillarInfo; + + // Derived + dayMaster: string; // "甲木" — day stem + element + dayMasterStem: string; // "甲" + dayMasterElement: string; // "木" + + fetalOrigin: string; // 胎元 + fetalBreath: string; // 胎息 + ownSign: string; // 命宫 + bodySign: string; // 身宫 + emptyBranches: string[]; // 空亡 branches + emptyTen: string; // 旬 +} + +/** Decade fortune (大运) */ +export interface DecadeFortuneInfo { + index: number; + ganzhi: string; + startAge: number; + endAge: number; + startYear: number; + endYear: number; + heavenStem: string; + earthBranch: string; + nayin: string; +} + +/** Annual fortune (流年/小运) */ +export interface FortuneInfo { + age: number; + year: number; + ganzhi: string; + nayin: string; +} + +/** Child limit (起运) information */ +export interface ChildLimitInfo { + startTime: string; // ISO datetime + endTime: string; + yearCount: number; + monthCount: number; + dayCount: number; + hourCount: number; + minuteCount: number; + forward: boolean; // 顺排/逆排 + startAge: number; + endAge: number; +} + +/** Full Bazi analysis result */ +export interface BaziFullResult { + eightChar: EightCharInfo; + childLimit: ChildLimitInfo; + decadeFortunes: DecadeFortuneInfo[]; + annualFortunes: FortuneInfo[]; + /** True when the caller applied solar-time longitude correction to the birth time */ + solarAdjusted?: boolean; +} diff --git a/web/packages/core/src/types/calendar.ts b/web/packages/core/src/types/calendar.ts new file mode 100644 index 0000000..7f28255 --- /dev/null +++ b/web/packages/core/src/types/calendar.ts @@ -0,0 +1,76 @@ +/** Core calendar day information — framework-agnostic plain object */ +export interface DayInfo { + /** ISO date string YYYY-MM-DD */ + solarDate: string; + solarDay: number; + solarMonth: number; + solarYear: number; + /** Chinese weekday name: 日,一,二,三,四,五,六 */ + weekDay: string; + /** 0=Sunday ... 6=Saturday */ + weekDayIndex: number; + /** Western zodiac constellation name */ + constellation: string; + /** Solar term name if this day is a term day */ + solarTerm: string | null; + /** Exact solar term time (HH:mm) */ + solarTermTime: string | null; + /** Whether this day is the exact solar term transition day */ + isTermDay: boolean; + /** The solar term this day belongs to (e.g. 大暑) */ + currentSolarTerm: string | null; + /** Season name: 春季/夏季/秋季/冬季 */ + season: string; + /** 1-based day index within the current solar term (节气第几天) */ + termDayIndex: number | null; + /** Next solar term name */ + nextSolarTerm: string | null; + /** Days until the next solar term */ + daysToNextTerm: number | null; + /** Julian Day number (儒略日) */ + julianDay: number | null; + /** Buddhist Era year (佛历年 = 公历年 + 543) */ + buddhistYear: number | null; + /** Islamic/Hijri date as "1448年02月18日" */ + hijriDate: string | null; + /** Buddhist festival name (农历) if applicable */ + buddhistFestival: string | null; + /** 72 phenology name if applicable */ + phenology: string | null; + /** Three periods (三伏) if applicable */ + dogDay: string | null; + /** Nine-day cold period (数九) if applicable */ + nineDay: string | null; + + // Lunar calendar + lunarYear: number; + lunarMonth: number; + /** Chinese lunar month name e.g. "五月" */ + lunarMonthName: string; + lunarDay: number; + /** Chinese lunar day name e.g. "初十", "廿一" */ + lunarDayName: string; + isLeapMonth: boolean; + /** Gan-Zhi of the lunar year e.g. "丙午" */ + lunarYearGanzhi: string; + /** Gan-Zhi of the lunar month */ + lunarMonthGanzhi: string; + /** Gan-Zhi of the lunar day */ + lunarDayGanzhi: string; + /** Chinese zodiac animal */ + zodiac: string; + + // Festivals & holidays + lunarFestival: string | null; + solarFestival: string | null; + legalHoliday: { name: string; isWork: boolean } | null; + + // Moon phase + moonPhase: string | null; + + // Metadata + isToday: boolean; + isWeekend: boolean; + /** Day-of-week position within the month grid (0-based column) */ + dayOfWeek: number; +} diff --git a/web/packages/core/src/types/fortune.ts b/web/packages/core/src/types/fortune.ts new file mode 100644 index 0000000..e68af00 --- /dev/null +++ b/web/packages/core/src/types/fortune.ts @@ -0,0 +1,66 @@ +/** Relationship between a single pillar in user's Bazi and day's Bazi */ +export interface PillarRelationship { + pillar: 'year' | 'month' | 'day' | 'hour'; + pillarLabel: string; // "年柱", "月柱", "日柱", "时柱" + + userGanzhi: string; + dayGanzhi: string; + + // Heaven stem relationships + stemTenStar: string; // Ten Star: day stem vs user stem + stemCombine: boolean; // 天干合 + stemOpposite: boolean; // 天干冲 + + // Earth branch relationships + branchCombine: boolean; // 六合 + branchThreeCombine: boolean; // 三合 + branchOpposite: boolean; // 六冲 + branchHarm: boolean; // 六害 + branchPunish: boolean; // 相刑 + branchFormation: string | null;// 三合局名 + + // Score contribution + score: number; // -10 to +10 +} + +/** Complete daily fortune result for a user on a specific day */ +export interface DailyFortuneResult { + date: string; // ISO date + lunarDate: string; // Lunar date description + dayGanzhi: string; // Day's Gan-Zhi + + /** Overall score from -100 to +100 */ + overallScore: number; + /** Score level classification */ + scoreLevel: 'great' | 'good' | 'fair' | 'poor' | 'bad'; + + /** Pillar-by-pillar relationship analysis */ + pillarRelationships: PillarRelationship[]; + + /** Positive aspects of the day */ + luckyAspects: string[]; + /** Negative aspects / warnings */ + unluckyAspects: string[]; + /** Actionable suggestions */ + suggestions: string[]; + + /** Primary affected life area */ + affectedAreas: string[]; + + /** Lucky meta info for the day (personalized) */ + luckyMeta: { + colors: string[]; + numbers: number[]; + direction: string; + element: string; + activity: string; + }; + + /** Category scores (-100 to +100) */ + categoryScores: { + love: number; + career: number; + wealth: number; + health: number; + }; +} diff --git a/web/packages/core/src/types/index.ts b/web/packages/core/src/types/index.ts new file mode 100644 index 0000000..fb6e05e --- /dev/null +++ b/web/packages/core/src/types/index.ts @@ -0,0 +1,15 @@ +export type { DayInfo } from './calendar'; +export type { AlmanacInfo, HourAlmanac } from './almanac'; +export type { + PillarInfo, + HideStemInfo, + EightCharInfo, + DecadeFortuneInfo, + FortuneInfo, + ChildLimitInfo, + BaziFullResult, +} from './bazi'; +export type { + PillarRelationship, + DailyFortuneResult, +} from './fortune'; diff --git a/web/packages/core/tsconfig.json b/web/packages/core/tsconfig.json new file mode 100644 index 0000000..465778d --- /dev/null +++ b/web/packages/core/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + "lib": ["ES2022"] + }, + "include": ["src"] +} diff --git a/web/packages/core/tsup.config.ts b/web/packages/core/tsup.config.ts new file mode 100644 index 0000000..d5e2f15 --- /dev/null +++ b/web/packages/core/tsup.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from 'tsup'; + +export default defineConfig({ + entry: ['src/index.ts'], + format: ['esm', 'cjs'], + dts: true, + clean: true, + sourcemap: true, + splitting: false, + treeshake: true, +}); diff --git a/web/packages/web/index.html b/web/packages/web/index.html new file mode 100644 index 0000000..b50db64 --- /dev/null +++ b/web/packages/web/index.html @@ -0,0 +1,24 @@ + + + + + + + + + 万年历 + + + +
+ + + diff --git a/web/packages/web/package.json b/web/packages/web/package.json new file mode 100644 index 0000000..bb3a387 --- /dev/null +++ b/web/packages/web/package.json @@ -0,0 +1,34 @@ +{ + "name": "@lunar/web", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "preview": "vite preview", + "lint": "eslint .", + "clean": "rm -rf dist" + }, + "dependencies": { + "@lunar/core": "workspace:*", + "framer-motion": "^12.0.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-router-dom": "^7.0.0", + "zustand": "^5.0.0", + "lucide-react": "^0.468.0", + "clsx": "^2.1.0", + "tailwind-variants": "^0.3.0" + }, + "devDependencies": { + "@tailwindcss/vite": "^4.0.0", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "tailwindcss": "^4.0.0", + "typescript": "^5.7.0", + "vite": "^6.0.0", + "@vitejs/plugin-react": "^4.4.0", + "vite-plugin-pwa": "^0.21.0" + } +} diff --git a/web/packages/web/public/favicon.svg b/web/packages/web/public/favicon.svg new file mode 100644 index 0000000..17f8f8d --- /dev/null +++ b/web/packages/web/public/favicon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/web/packages/web/src/App.tsx b/web/packages/web/src/App.tsx new file mode 100644 index 0000000..37ed43d --- /dev/null +++ b/web/packages/web/src/App.tsx @@ -0,0 +1,68 @@ +import { Routes, Route, Navigate, useLocation } from 'react-router-dom'; +import { lazy, Suspense } from 'react'; +import { AnimatePresence, motion } from 'framer-motion'; +import { AppShell } from './components/layout/AppShell'; +import { ErrorBoundary } from './components/ui/ErrorBoundary'; +import { PageSkeleton } from './components/ui/Skeleton'; + +// Lazy load pages +const HomePage = lazy(() => import('./pages/HomePage')); +const CalendarPage = lazy(() => import('./pages/CalendarPage')); +const DayDetailPage = lazy(() => import('./pages/DayDetailPage')); +const BaziPage = lazy(() => import('./pages/BaziPage')); +const DailyFortunePage = lazy(() => import('./pages/DailyFortunePage')); +const SettingsPage = lazy(() => import('./pages/SettingsPage')); +const DivinationPage = lazy(() => import('./pages/DivinationPage')); +const SolarTermsPage = lazy(() => import('./pages/SolarTermsPage')); + +const pageVariants = { + initial: { opacity: 0, y: 12 }, + animate: { opacity: 1, y: 0 }, + exit: { opacity: 0, y: -12 }, +}; + +function PageTransition({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ); +} + +function SafePage({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ); +} + +export default function App() { + const location = useLocation(); + + return ( + + + }> + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + + + ); +} diff --git a/web/packages/web/src/components/calendar/CalendarCell.tsx b/web/packages/web/src/components/calendar/CalendarCell.tsx new file mode 100644 index 0000000..526c676 --- /dev/null +++ b/web/packages/web/src/components/calendar/CalendarCell.tsx @@ -0,0 +1,122 @@ +import { cn } from '../../lib/utils'; +import { motion } from 'framer-motion'; +import { Star } from 'lucide-react'; +import type { DayInfo } from '@lunar/core'; + +interface CalendarCellProps { + dayInfo: DayInfo; + isSelected: boolean; + isCurrentMonth: boolean; + fortuneScore?: number; + hasBookmark?: boolean; + expanded?: boolean; + onClick: () => void; +} + +// Short festival abbreviations +function shortFestival(di: DayInfo): string | null { + if (di.lunarFestival) { + if (di.lunarFestival.includes('春节')) return '春节'; + if (di.lunarFestival.includes('元宵')) return '元宵'; + if (di.lunarFestival.includes('清明')) return '清明'; + if (di.lunarFestival.includes('端午')) return '端午'; + if (di.lunarFestival.includes('七夕')) return '七夕'; + if (di.lunarFestival.includes('中秋')) return '中秋'; + if (di.lunarFestival.includes('重阳')) return '重阳'; + if (di.lunarFestival.includes('除夕')) return '除夕'; + if (di.lunarFestival.includes('腊八')) return '腊八'; + if (di.lunarFestival.includes('冬至')) return '冬至'; + if (di.lunarFestival.includes('小年') || di.lunarFestival.includes('灶')) return '小年'; + return '节'; + } + if (di.solarFestival) { + if (di.solarFestival.includes('元旦')) return '元旦'; + if (di.solarFestival.includes('国庆')) return '国庆'; + if (di.solarFestival.includes('劳动')) return '劳动'; + if (di.solarFestival.includes('妇女')) return '妇女'; + if (di.solarFestival.includes('儿童')) return '儿童'; + if (di.solarFestival.includes('情人')) return '情人'; + return '节'; + } + if (di.legalHoliday && !di.legalHoliday.isWork) return '休'; + return null; +} + +export function CalendarCell({ dayInfo, isSelected, isCurrentMonth, fortuneScore, hasBookmark, expanded, onClick }: CalendarCellProps) { + const fest = shortFestival(dayInfo); + const hasTerm = !!dayInfo.solarTerm; + + return ( + + {/* Festival / Solar term label at top */} + {(fest || hasTerm) && ( + + {fest || (hasTerm ? dayInfo.solarTerm!.slice(0, 2) : '')} + + )} + + {/* Fortune dot (if user has bazi) */} + {fortuneScore !== undefined && ( +
= 20 ? 'bg-red-500' : fortuneScore <= -20 ? 'bg-slate-500' : 'bg-amber-400' + }`} /> + )} + + {/* Bookmark star */} + {hasBookmark && ( + + )} + + {/* Solar day number */} + + {dayInfo.solarDay} + + + {/* Lunar day name or festival */} + + {dayInfo.lunarDayName} + + + {/* Extra info in week view */} + {expanded && ( +
+ {dayInfo.lunarFestival && ( + {dayInfo.lunarFestival} + )} + {dayInfo.solarTerm && ( + {dayInfo.solarTerm} + )} + {fortuneScore !== undefined && ( + =20?'text-red-500':fortuneScore<=-20?'text-slate-500':'text-amber-500'}`}> + {fortuneScore>=20?'吉':fortuneScore<=-20?'凶':'平'} {fortuneScore} + + )} +
+ )} + + ); +} diff --git a/web/packages/web/src/components/calendar/CalendarGrid.tsx b/web/packages/web/src/components/calendar/CalendarGrid.tsx new file mode 100644 index 0000000..dcdea0c --- /dev/null +++ b/web/packages/web/src/components/calendar/CalendarGrid.tsx @@ -0,0 +1,113 @@ +import { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { motion } from 'framer-motion'; +import type { DayInfo } from '@lunar/core'; +import { CalendarCell } from './CalendarCell'; +import { WeekDayBar } from './WeekDayBar'; +import { MonthYearPicker } from './MonthYearPicker'; + +interface CalendarGridProps { + days: DayInfo[][]; + viewDate: Date; + selectedDate: Date | null; + weekStart?: 0 | 1; + fortuneScores?: Record | null; + bookmarkedDates?: Set; + onPrevMonth: () => void; + onNextMonth: () => void; + onPrevYear: () => void; + onNextYear: () => void; + onSelectDate: (d: Date) => void; +} + +export function CalendarGrid({ + days, viewDate, selectedDate, weekStart = 0, fortuneScores, bookmarkedDates, + onPrevMonth, onNextMonth, onPrevYear, onNextYear, onSelectDate, +}: CalendarGridProps) { + const navigate = useNavigate(); + const [viewMode, setViewMode] = useState<'month'|'week'>('month'); + const monthKey = `${viewDate.getFullYear()}-${viewDate.getMonth()}`; + + const handleDayClick = (dayInfo: DayInfo) => { + const [y, m, d] = dayInfo.solarDate.split('-').map(Number); + onSelectDate(new Date(y, m - 1, d)); + navigate(`/calendar/${dayInfo.solarDate}`); + }; + + const isSelected = (di: DayInfo): boolean => { + if (!selectedDate) return false; + const [y, m, d] = di.solarDate.split('-').map(Number); + return selectedDate.getFullYear()===y && selectedDate.getMonth()===m-1 && selectedDate.getDate()===d; + }; + + // Week view: show the week containing the selected date, or current week + const selectedWeek = selectedDate + ? days.findIndex(w => w.some(d => { + const [y,m,day] = d.solarDate.split('-').map(Number); + return selectedDate.getFullYear()===y && selectedDate.getMonth()===m-1 && selectedDate.getDate()===day; + })) + : -1; + const displayWeek = selectedWeek >= 0 ? selectedWeek : 0; + const displayDays = viewMode === 'week' ? (days[displayWeek] || days[0] || []) : days.flat(); + + return ( +
+ + + {/* View mode toggle */} +
+ + +
+ +
+ + + {displayDays.map((dayInfo, idx) => ( + handleDayClick(dayInfo)} /> + ))} + +
+ + {/* Month summary (fortune) */} + {fortuneScores && ( + + )} +
+ ); +} + +function MonthSummary({ scores, days }: { scores: Record; days: DayInfo[][] }) { + const allDays = days.flat().filter(d => scores[d.solarDate] !== undefined); + if (allDays.length === 0) return null; + const good = allDays.filter(d => scores[d.solarDate] >= 20).length; + const bad = allDays.filter(d => scores[d.solarDate] <= -20).length; + const fair = allDays.length - good - bad; + const best = allDays.reduce((a, b) => (scores[a.solarDate] > scores[b.solarDate]) ? a : b); + const worst = allDays.reduce((a, b) => (scores[a.solarDate] < scores[b.solarDate]) ? a : b); + return ( +
+

本月运势概览

+
+ 吉 {good}天 + 平 {fair}天 + 凶 {bad}天 +
+
+ 最佳: {best.lunarMonthName}{best.lunarDayName} ({scores[best.solarDate]}) + 谨慎: {worst.lunarMonthName}{worst.lunarDayName} ({scores[worst.solarDate]}) +
+
+ ); +} diff --git a/web/packages/web/src/components/calendar/MonthYearPicker.tsx b/web/packages/web/src/components/calendar/MonthYearPicker.tsx new file mode 100644 index 0000000..18afa4e --- /dev/null +++ b/web/packages/web/src/components/calendar/MonthYearPicker.tsx @@ -0,0 +1,45 @@ +import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from 'lucide-react'; + +interface MonthYearPickerProps { + viewDate: Date; + onPrevMonth: () => void; + onNextMonth: () => void; + onPrevYear: () => void; + onNextYear: () => void; +} + +export function MonthYearPicker({ + viewDate, + onPrevMonth, + onNextMonth, + onPrevYear, + onNextYear, +}: MonthYearPickerProps) { + const monthNames = ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月']; + + return ( +
+
+ + +
+ +

+ {viewDate.getFullYear()}年{monthNames[viewDate.getMonth()]} +

+ +
+ + +
+
+ ); +} diff --git a/web/packages/web/src/components/calendar/WeekDayBar.tsx b/web/packages/web/src/components/calendar/WeekDayBar.tsx new file mode 100644 index 0000000..1632eed --- /dev/null +++ b/web/packages/web/src/components/calendar/WeekDayBar.tsx @@ -0,0 +1,24 @@ +const DAY_LABELS = ['日', '一', '二', '三', '四', '五', '六']; + +export function WeekDayBar({ weekStart = 0 }: { weekStart?: 0 | 1 }) { + const labels = weekStart === 1 ? [...DAY_LABELS.slice(1), DAY_LABELS[0]] : DAY_LABELS; + return ( +
+ {labels.map((label, idx) => { + const weekIdx = (idx + weekStart) % 7; + const isWeekend = weekIdx === 0 || weekIdx === 6; + return ( +
+ {label} +
+ ); + })} +
+ ); +} diff --git a/web/packages/web/src/components/layout/AppShell.tsx b/web/packages/web/src/components/layout/AppShell.tsx new file mode 100644 index 0000000..fda4b6e --- /dev/null +++ b/web/packages/web/src/components/layout/AppShell.tsx @@ -0,0 +1,21 @@ +import type { ReactNode } from 'react'; +import { Header } from './Header'; +import { BottomNav } from './BottomNav'; + +interface AppShellProps { + children: ReactNode; +} + +export function AppShell({ children }: AppShellProps) { + return ( +
+
+
+
+ {children} +
+
+ +
+ ); +} diff --git a/web/packages/web/src/components/layout/BottomNav.tsx b/web/packages/web/src/components/layout/BottomNav.tsx new file mode 100644 index 0000000..2357b67 --- /dev/null +++ b/web/packages/web/src/components/layout/BottomNav.tsx @@ -0,0 +1,43 @@ +import { useNavigate, useLocation } from 'react-router-dom'; +import { Home, CalendarDays, Stars, TrendingUp, Settings } from 'lucide-react'; +import { cn } from '../../lib/utils'; + +const NAV_ITEMS = [ + { path: '/', label: '首页', icon: Home, exact: true }, + { path: '/calendar', label: '日历', icon: CalendarDays }, + { path: '/bazi', label: '八字', icon: Stars }, + { path: '/daily-fortune', label: '运势', icon: TrendingUp }, + { path: '/settings', label: '设置', icon: Settings }, +]; + +export function BottomNav() { + const navigate = useNavigate(); + const location = useLocation(); + + return ( + + ); +} diff --git a/web/packages/web/src/components/layout/Header.tsx b/web/packages/web/src/components/layout/Header.tsx new file mode 100644 index 0000000..8e35502 --- /dev/null +++ b/web/packages/web/src/components/layout/Header.tsx @@ -0,0 +1,43 @@ +import { useNavigate, useLocation } from 'react-router-dom'; +import { Sun, Moon, Calendar, Home } from 'lucide-react'; +import { useSettingsStore } from '../../stores/settings'; + +interface HeaderProps { + title?: string; +} + +export function Header({ title = '万年历' }: HeaderProps) { + const navigate = useNavigate(); + const location = useLocation(); + const { theme, setTheme } = useSettingsStore(); + + const toggleTheme = () => setTheme(theme === 'dark' ? 'light' : 'dark'); + const isHome = location.pathname === '/'; + + return ( +
+
+ + +
+ {!isHome && ( + + )} + + +
+
+
+ ); +} diff --git a/web/packages/web/src/components/ui/AnimatedPanel.tsx b/web/packages/web/src/components/ui/AnimatedPanel.tsx new file mode 100644 index 0000000..97698b8 --- /dev/null +++ b/web/packages/web/src/components/ui/AnimatedPanel.tsx @@ -0,0 +1,54 @@ +import { motion, AnimatePresence } from 'framer-motion'; +import type { ReactNode } from 'react'; + +interface AnimatedPanelProps { + children: ReactNode; + isOpen: boolean; + onClose: () => void; + position?: 'bottom' | 'right'; +} + +export function AnimatedPanel({ + children, + isOpen, + onClose, + position = 'bottom', +}: AnimatedPanelProps) { + return ( + + {isOpen && ( + <> + {/* Backdrop */} + + + {/* Panel */} + + {/* Drag handle for bottom sheet */} + {position === 'bottom' && ( +
+
+
+ )} + {children} + + + )} + + ); +} diff --git a/web/packages/web/src/components/ui/Badge.tsx b/web/packages/web/src/components/ui/Badge.tsx new file mode 100644 index 0000000..96226d9 --- /dev/null +++ b/web/packages/web/src/components/ui/Badge.tsx @@ -0,0 +1,41 @@ +import { cn } from '../../lib/utils'; +import type { ReactNode } from 'react'; + +interface BadgeProps { + children: ReactNode; + variant?: 'default' | 'lucky' | 'unlucky' | 'element' | 'outline' | 'festival'; + size?: 'sm' | 'md'; + className?: string; + element?: string; // 'wood' | 'fire' | 'earth' | 'metal' | 'water' +} + +const elementStyles: Record = { + wood: 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400', + fire: 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400', + earth: 'bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-400', + metal: 'bg-slate-100 text-slate-600 dark:bg-slate-800 dark:text-slate-400', + water: 'bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400', +}; + +export function Badge({ children, variant = 'default', size = 'sm', className, element }: BadgeProps) { + const baseStyles = 'inline-flex items-center font-medium rounded-full'; + const sizeStyles = { + sm: 'px-2 py-0.5 text-xs', + md: 'px-3 py-1 text-sm', + }; + + const variantStyles = { + default: 'bg-primary/10 text-primary', + lucky: 'bg-red-50 text-red-600 dark:bg-red-900/20 dark:text-red-400', + unlucky: 'bg-slate-100 text-slate-600 dark:bg-slate-800 dark:text-slate-400', + element: element ? elementStyles[element] || '' : '', + outline: 'border border-border text-secondary bg-transparent', + festival: 'bg-red-500 text-white', + }; + + return ( + + {children} + + ); +} diff --git a/web/packages/web/src/components/ui/Button.tsx b/web/packages/web/src/components/ui/Button.tsx new file mode 100644 index 0000000..7e45769 --- /dev/null +++ b/web/packages/web/src/components/ui/Button.tsx @@ -0,0 +1,43 @@ +import { cn } from '../../lib/utils'; +import type { ButtonHTMLAttributes, ReactNode } from 'react'; + +interface ButtonProps extends ButtonHTMLAttributes { + children: ReactNode; + variant?: 'primary' | 'secondary' | 'ghost' | 'outline'; + size?: 'sm' | 'md' | 'lg'; + className?: string; +} + +export function Button({ + children, + variant = 'primary', + size = 'md', + className, + disabled, + ...props +}: ButtonProps) { + const baseStyles = 'inline-flex items-center justify-center font-medium rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-primary/30 disabled:opacity-50 disabled:pointer-events-none'; + + const variantStyles = { + primary: 'bg-primary text-white hover:bg-primary-dark active:bg-primary-dark', + secondary: 'bg-accent text-white hover:bg-amber-600 active:bg-amber-700', + ghost: 'bg-transparent text-foreground hover:bg-foreground/5 active:bg-foreground/10', + outline: 'border-2 border-border bg-transparent text-foreground hover:bg-foreground/5', + }; + + const sizeStyles = { + sm: 'h-8 px-3 text-xs gap-1.5', + md: 'h-10 px-4 text-sm gap-2', + lg: 'h-12 px-6 text-base gap-2.5', + }; + + return ( + + ); +} diff --git a/web/packages/web/src/components/ui/Card.tsx b/web/packages/web/src/components/ui/Card.tsx new file mode 100644 index 0000000..cc9ac06 --- /dev/null +++ b/web/packages/web/src/components/ui/Card.tsx @@ -0,0 +1,58 @@ +import { cn } from '../../lib/utils'; +import type { HTMLAttributes, ReactNode } from 'react'; + +interface CardProps extends HTMLAttributes { + children: ReactNode; + variant?: 'default' | 'elevated' | 'outlined'; + padding?: 'none' | 'sm' | 'md' | 'lg'; + className?: string; +} + +export function Card({ + children, + variant = 'default', + padding = 'md', + className, + ...props +}: CardProps) { + const baseStyles = 'rounded-xl overflow-hidden'; + const variantStyles = { + default: 'bg-card shadow-sm border border-border/60', + elevated: 'bg-card shadow-md border border-border/60 chinese-pattern', + outlined: 'bg-transparent border border-border', + }; + const paddingStyles = { + none: '', + sm: 'p-2.5', + md: 'p-3', + lg: 'p-5', + }; + + return ( +
+ {children} +
+ ); +} + +interface CardHeaderProps { + title: ReactNode; + subtitle?: string; + action?: ReactNode; + className?: string; +} + +export function CardHeader({ title, subtitle, action, className }: CardHeaderProps) { + return ( +
+
+

{title}

+ {subtitle &&

{subtitle}

} +
+ {action &&
{action}
} +
+ ); +} diff --git a/web/packages/web/src/components/ui/ErrorBoundary.tsx b/web/packages/web/src/components/ui/ErrorBoundary.tsx new file mode 100644 index 0000000..485ba93 --- /dev/null +++ b/web/packages/web/src/components/ui/ErrorBoundary.tsx @@ -0,0 +1,56 @@ +import { Component, type ReactNode } from 'react'; +import { AlertTriangle, RefreshCw } from 'lucide-react'; +import { Button } from './Button'; + +interface Props { + children: ReactNode; + fallback?: ReactNode; +} + +interface State { + hasError: boolean; + error: Error | null; +} + +export class ErrorBoundary extends Component { + constructor(props: Props) { + super(props); + this.state = { hasError: false, error: null }; + } + + static getDerivedStateFromError(error: Error): State { + return { hasError: true, error }; + } + + componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { + console.error('ErrorBoundary caught:', error, errorInfo); + } + + handleReset = () => { + this.setState({ hasError: false, error: null }); + }; + + render() { + if (this.state.hasError) { + if (this.props.fallback) return this.props.fallback; + + return ( +
+
+ +
+

出了点问题

+

+ {this.state.error?.message || '页面加载失败,请刷新重试'} +

+ +
+ ); + } + + return this.props.children; + } +} diff --git a/web/packages/web/src/components/ui/Skeleton.tsx b/web/packages/web/src/components/ui/Skeleton.tsx new file mode 100644 index 0000000..ea286b0 --- /dev/null +++ b/web/packages/web/src/components/ui/Skeleton.tsx @@ -0,0 +1,132 @@ +import { cn } from '../../lib/utils'; + +interface SkeletonProps { + className?: string; +} + +export function Skeleton({ className }: SkeletonProps) { + return ( +
+ ); +} + +export function PageSkeleton() { + return ( +
+ {/* Hero skeleton */} +
+ + + +
+ + {/* Grid skeleton */} +
+ {[...Array(4)].map((_, i) => ( +
+ + +
+ ))} +
+ + {/* Card skeleton */} +
+ +
+
+ +
+ {[...Array(5)].map((_, i) => ( + + ))} +
+
+
+ +
+ {[...Array(5)].map((_, i) => ( + + ))} +
+
+
+
+ + {/* Button skeleton */} + +
+ + +
+
+ ); +} + +export function CalendarSkeleton() { + return ( +
+ {/* Month picker skeleton */} +
+ + + +
+ {/* Grid skeleton */} +
+
+ {[...Array(7)].map((_, i) => ( +
+ +
+ ))} +
+
+ {[...Array(42)].map((_, i) => ( +
+ + +
+ ))} +
+
+
+ ); +} + +export function BaziSkeleton() { + return ( +
+ + {/* Form skeleton */} +
+ +
+ {[...Array(6)].map((_, i) => ( + + ))} +
+ +
+ {/* Result skeleton */} +
+ +
+ {[...Array(4)].map((_, i) => ( +
+ + + + +
+ ))} +
+
+
+ ); +} diff --git a/web/packages/web/src/hooks/useBazi.ts b/web/packages/web/src/hooks/useBazi.ts new file mode 100644 index 0000000..05b0a83 --- /dev/null +++ b/web/packages/web/src/hooks/useBazi.ts @@ -0,0 +1,37 @@ +import { useCallback } from 'react'; +import { birthInfoToBazi } from '@lunar/core'; +import { useUserStore, type UserProfile } from '../stores/user'; + +export function useBazi() { + const { + profiles, activeIndex, activeProfile, activeBazi, isLoading, error, + addProfile, updateProfile, removeProfile, setActiveIndex, setBaziResult, setLoading, setError, clearAll, + } = useUserStore(); + + const calculate = useCallback((p?: UserProfile, idx?: number) => { + const target = p || activeProfile; + const index = idx ?? activeIndex; + if (!target) { setError('请先输入出生信息'); return null; } + setLoading(true); setError(null); + try { + const result = birthInfoToBazi({ + year: target.birthYear, month: target.birthMonth, day: target.birthDay, + hour: target.birthHour, minute: target.birthMinute, gender: target.gender, + }); + setBaziResult(index, result); + return result; + } catch (e) { + setError(e instanceof Error ? e.message : '八字计算失败'); + return null; + } + }, [activeProfile, activeIndex, setBaziResult, setLoading, setError]); + + return { + profiles, activeIndex, profile: activeProfile, baziResult: activeBazi, isLoading, error, + setProfile: (p: UserProfile) => updateProfile(activeIndex, p), + addProfile, updateProfile, removeProfile, setActiveIndex, + calculate, clearAll, + hasProfile: profiles.length > 0, + profileCount: profiles.length, + }; +} diff --git a/web/packages/web/src/hooks/useCalendar.ts b/web/packages/web/src/hooks/useCalendar.ts new file mode 100644 index 0000000..d457b37 --- /dev/null +++ b/web/packages/web/src/hooks/useCalendar.ts @@ -0,0 +1,40 @@ +import { useMemo } from 'react'; +import { getMonthCalendar } from '@lunar/core'; +import { useCalendarStore } from '../stores/calendar'; +import { useSettingsStore } from '../stores/settings'; + +export function useCalendar() { + const { + viewDate, + selectedDate, + goToToday, + goToNextMonth, + goToPrevMonth, + goToNextYear, + goToPrevYear, + selectDate, + } = useCalendarStore(); + const weekStart = useSettingsStore(s => s.weekStartDay); + + const days = useMemo(() => { + const year = viewDate.getFullYear(); + const month = viewDate.getMonth() + 1; + return getMonthCalendar(year, month, weekStart); + }, [viewDate, weekStart]); + + const today = useMemo(() => new Date(), []); // stable today ref + + return { + viewDate, + selectedDate, + weekStart, + days, + today, + goToToday, + goToNextMonth, + goToPrevMonth, + goToNextYear, + goToPrevYear, + selectDate, + }; +} diff --git a/web/packages/web/src/hooks/useDailyFortune.ts b/web/packages/web/src/hooks/useDailyFortune.ts new file mode 100644 index 0000000..06b1402 --- /dev/null +++ b/web/packages/web/src/hooks/useDailyFortune.ts @@ -0,0 +1,23 @@ +import { useMemo, useState, useEffect } from 'react'; +import { calculateDailyFortune, type DailyFortuneResult } from '@lunar/core'; +import { useUserStore } from '../stores/user'; + +export function useDailyFortune(date: Date) { + const { eightChar, activeProfile } = useUserStore(); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + + const fortune: DailyFortuneResult | null = useMemo(() => { + if (!eightChar) return null; + try { + return calculateDailyFortune(eightChar, date); + } catch (e) { + setError(e instanceof Error ? e.message : '运势计算失败'); + return null; + } + }, [eightChar, date.getFullYear(), date.getMonth(), date.getDate()]); + + useEffect(() => { setIsLoading(false); }, [fortune]); + + return { fortune, isLoading, error, hasProfile: !!activeProfile, needsProfile: !activeProfile }; +} diff --git a/web/packages/web/src/hooks/useDayDetail.ts b/web/packages/web/src/hooks/useDayDetail.ts new file mode 100644 index 0000000..8d87363 --- /dev/null +++ b/web/packages/web/src/hooks/useDayDetail.ts @@ -0,0 +1,26 @@ +import { useMemo, useState, useEffect } from 'react'; +import { getDayInfo, getAlmanacInfo, type DayInfo, type AlmanacInfo } from '@lunar/core'; + +export function useDayDetail(date: Date) { + const [isLoading, setIsLoading] = useState(true); + + const dayInfo: DayInfo = useMemo(() => { + const y = date.getFullYear(); + const m = date.getMonth() + 1; + const d = date.getDate(); + return getDayInfo(y, m, d); + }, [date.getFullYear(), date.getMonth(), date.getDate()]); + + const almanac: AlmanacInfo = useMemo(() => { + const y = date.getFullYear(); + const m = date.getMonth() + 1; + const d = date.getDate(); + return getAlmanacInfo(y, m, d); + }, [date.getFullYear(), date.getMonth(), date.getDate()]); + + useEffect(() => { + setIsLoading(false); + }, [dayInfo]); + + return { dayInfo, almanac, isLoading }; +} diff --git a/web/packages/web/src/lib/utils.ts b/web/packages/web/src/lib/utils.ts new file mode 100644 index 0000000..25658c1 --- /dev/null +++ b/web/packages/web/src/lib/utils.ts @@ -0,0 +1,47 @@ +import { clsx, type ClassValue } from 'clsx'; + +/** Merge class names with clsx */ +export function cn(...inputs: ClassValue[]): string { + return clsx(inputs); +} + +/** Format a Date object to YYYY-MM-DD string */ +export function formatDate(date: Date): string { + const y = date.getFullYear(); + const m = String(date.getMonth() + 1).padStart(2, '0'); + const d = String(date.getDate()).padStart(2, '0'); + return `${y}-${m}-${d}`; +} + +/** Parse a YYYY-MM-DD string to Date */ +export function parseDate(dateStr: string): Date { + const [y, m, d] = dateStr.split('-').map(Number); + return new Date(y, m - 1, d); +} + +/** Get today as YYYY-MM-DD string */ +export function getTodayString(): string { + return formatDate(new Date()); +} + +/** Chinese number mapping for days 1-30 */ +export function getChineseDayName(day: number): string { + const tens = ['', '十', '廿', '三十']; + const ones = ['', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十']; + + if (day === 10) return '初十'; + if (day === 20) return '二十'; + if (day === 30) return '三十'; + + const ten = Math.floor(day / 10); + const one = day % 10; + const prefix = tens[ten]; + const suffix = ones[one]; + return `初${prefix}${suffix}`; +} + +/** Chinese month names */ +export function getChineseMonthName(month: number, isLeap: boolean): string { + const names = ['', '正', '二', '三', '四', '五', '六', '七', '八', '九', '十', '冬', '腊']; + return `${isLeap ? '闰' : ''}${names[month]}月`; +} diff --git a/web/packages/web/src/main.tsx b/web/packages/web/src/main.tsx new file mode 100644 index 0000000..b8a88ac --- /dev/null +++ b/web/packages/web/src/main.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import { BrowserRouter } from 'react-router-dom'; +import App from './App'; +import './styles/globals.css'; + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + + + , +); diff --git a/web/packages/web/src/pages/BaziPage.tsx b/web/packages/web/src/pages/BaziPage.tsx new file mode 100644 index 0000000..0054ddb --- /dev/null +++ b/web/packages/web/src/pages/BaziPage.tsx @@ -0,0 +1,529 @@ +import { useState, useCallback, useMemo, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { motion } from 'framer-motion'; +import { Sparkles, Clock, Scale, MapPin, Plus, X, User } from 'lucide-react'; +import { useUserStore, type UserProfile, type SavedProfile } from '../stores/user'; +import { useSettingsStore } from '../stores/settings'; +import { Card } from '../components/ui/Card'; +import { Badge } from '../components/ui/Badge'; +import { Button } from '../components/ui/Button'; +import { + birthInfoToBazi, analyzeElementBalance, analyzeShensha, analyzeFortuneGanzhi, calculateBoneWeight, getDayInfo, getTodayInfo, getYearMonths, + checkStemCombine, checkStemOpposite, getBranchRelationship, +} from '@lunar/core'; +import type { BaziFullResult, EightCharInfo, PillarInfo, FortuneLuck } from '@lunar/core'; + +const TP = [ + ['子','23-01',0],['丑','01-03',2],['寅','03-05',4],['卯','05-07',6], + ['辰','07-09',8],['巳','09-11',10],['午','11-13',12],['未','13-15',14], + ['申','15-17',16],['酉','17-19',18],['戌','19-21',20],['亥','21-23',22], +] as const; + +const PROVINCES: { name: string; lng: number }[] = [ + { name:'北京', lng:116.4 }, { name:'上海', lng:121.5 }, { name:'天津', lng:117.2 }, { name:'重庆', lng:106.5 }, + { name:'石家庄', lng:114.5 }, { name:'太原', lng:112.5 }, { name:'呼和浩特', lng:111.7 }, { name:'沈阳', lng:123.4 }, + { name:'长春', lng:125.3 }, { name:'哈尔滨', lng:126.6 }, { name:'南京', lng:118.8 }, { name:'杭州', lng:120.2 }, + { name:'合肥', lng:117.3 }, { name:'福州', lng:119.3 }, { name:'南昌', lng:115.9 }, { name:'济南', lng:117.0 }, + { name:'郑州', lng:113.7 }, { name:'武汉', lng:114.3 }, { name:'长沙', lng:113.0 }, { name:'广州', lng:113.3 }, + { name:'南宁', lng:108.3 }, { name:'海口', lng:110.3 }, { name:'成都', lng:104.1 }, { name:'贵阳', lng:106.7 }, + { name:'昆明', lng:102.7 }, { name:'拉萨', lng:91.1 }, { name:'西安', lng:108.9 }, { name:'兰州', lng:103.8 }, + { name:'西宁', lng:101.8 }, { name:'银川', lng:106.2 }, { name:'乌鲁木齐', lng:87.6 }, { name:'台北', lng:121.5 }, + { name:'香港', lng:114.2 }, { name:'澳门', lng:113.5 }, +]; + +const TIMEZONES: number[] = [-12,-11,-10,-9,-8,-7,-6,-5,-4,-3,-2,-1,0,1,2,3,3.5,4,4.5,5,5.5,6,6.5,7,8,9,9.5,10,11,12,13,14]; + +const TZ_EXAMPLES: Record = { + '-5': '纽约/多伦多', '-8': '洛杉矶/温哥华', '0': '伦敦', '1': '巴黎/柏林', '9': '东京/首尔', + '7': '曼谷/雅加达', '10': '悉尼/墨尔本', '13': '奥克兰', '4': '迪拜', '5.5': '新德里', '6.5': '仰光', +}; + +function fmtTz(tz: number): string { + const sign = tz >= 0 ? '+' : '-'; + const abs = Math.abs(tz); + const h = Math.floor(abs); + const m = Math.round((abs - h) * 60); + return `UTC${sign}${h}${m ? ':' + String(m).padStart(2, '0') : ''}`; +} + +const PK = ['year','month','day','hour'] as const; +const PL: Record = { year:'年柱', month:'月柱', day:'日柱', hour:'时柱' }; +const EC: Record = { '木':'#4CAF50','火':'#E53935','土':'#F9A825','金':'#90A4AE','水':'#1E88E5' }; +const ET: Record = { '木':'text-green-700 dark:text-green-400','火':'text-red-700 dark:text-red-400','土':'text-amber-700 dark:text-amber-400','金':'text-slate-600 dark:text-slate-400','水':'text-blue-700 dark:text-blue-400' }; + +export default function BaziPage() { + const { profiles, activeIndex, activeProfile, activeBazi: baziResult, setActiveIndex, + updateProfile, addProfile, removeProfile, setBaziResult, setLoading, setError, isLoading, error } = useUserStore(); + const ziHourSect = useSettingsStore(s => s.ziHourSect); + const solarTime = useSettingsStore(s => s.solarTime); + + const defaultForm: UserProfile = activeProfile || { name:'', birthYear:1990, birthMonth:1, birthDay:1, birthHour:12, birthMinute:0, gender:'male', longitude:116.4 }; + const [fd, setFd] = useState(defaultForm); + + useEffect(() => { if (activeProfile) setFd(activeProfile); }, [activeIndex]); + + const update = (k: keyof UserProfile, v: string|number) => setFd(p => ({...p, [k]: v})); + + const doCalc = useCallback(() => { + updateProfile(activeIndex, fd); + setLoading(true); setError(null); + try { + let h = fd.birthHour, min = fd.birthMinute; + let yy = fd.birthYear, mm = fd.birthMonth, dd = fd.birthDay; + let adjusted = false; + if (fd.tzOffset !== undefined && fd.tzOffset !== 8) { + // 海外出生:本地时间 → 北京时间(UTC+8),可能跨日 + const d = new Date(Date.UTC(yy, mm - 1, dd, h, min) - (fd.tzOffset - 8) * 3600000); + yy = d.getUTCFullYear(); mm = d.getUTCMonth() + 1; dd = d.getUTCDate(); h = d.getUTCHours(); min = d.getUTCMinutes(); + adjusted = true; + } else if (solarTime && fd.longitude && fd.longitude !== 120) { + // 中国城市:北京时间 → 真太阳时(经度校正),可能跨日 + const offsetMin = Math.round((fd.longitude - 120) * 4); + const d = new Date(Date.UTC(yy, mm - 1, dd, h, min) + offsetMin * 60000); + yy = d.getUTCFullYear(); mm = d.getUTCMonth() + 1; dd = d.getUTCDate(); h = d.getUTCHours(); min = d.getUTCMinutes(); + adjusted = true; + } + const r = birthInfoToBazi({ year: yy, month: mm, day: dd, hour: h, minute: min, gender: fd.gender, ziSect: ziHourSect }); + r.solarAdjusted = adjusted; + setBaziResult(activeIndex, r); + } catch(e) { setError(e instanceof Error ? e.message : '计算失败'); } + }, [fd, activeIndex, ziHourSect, solarTime, updateProfile, setBaziResult, setLoading, setError]); + + const solarAdjusted = baziResult?.solarAdjusted; + + const profileLabel = (p: SavedProfile) => + `${p.profile.gender==='male'?'♂':'♀'} ${p.profile.birthYear}/${p.profile.birthMonth}/${p.profile.birthDay}`; + + return ( +
+ {/* Header + profile tabs */} +
+

八字排盘

+ {profiles.length < 3 && ( + + )} +
+ {profiles.length > 0 && ( +
+ {profiles.map((p, i) => ( +
setActiveIndex(i)} + className={`flex items-center gap-1 px-2.5 py-1.5 rounded-lg text-xs whitespace-nowrap shrink-0 cursor-pointer transition-colors ${ + i === activeIndex ? 'bg-primary text-white' : 'bg-card border border-border text-secondary hover:bg-foreground/5' + }`}> + + {profileLabel(p)} + {profiles.length > 1 && ( + + )} +
+ ))} +
+ )} + + {/* Birth form */} + +
+ {/* Year with stepper */} +
+ +
+ + {fd.birthYear} + + + + +
+
+ + {/* Time */} +
+ +
+ + : + + 时:分 +
+
+ {TP.map(([l,range,h]) => { + const currentHourIdx = Math.floor(((fd.birthHour + 1) % 24) / 2); + const isActive = currentHourIdx === (h/2); + return ( + + ); + })} +
+
+ + {/* Birthplace */} +
+ +
+ + +
+ {fd.tzOffset === undefined ? ( + + ) : ( + + )} + {solarAdjusted && ( +

+ {fd.tzOffset !== undefined && fd.tzOffset !== 8 + ? `✓ 已按${fmtTz(fd.tzOffset)}换算为北京时间(UTC+8)排盘` + : fd.longitude && fd.longitude !== 120 + ? `✓ 已按${fd.longitude}°E换算真太阳时(比北京时间${fd.longitude > 120 ? '晚' : '早'}${Math.abs(Math.round((fd.longitude - 120) * 4))}分钟)` + : ''} +

+ )} +
+ +
+ + +
+ + {error &&

{error}

} + +
+
+ + {baziResult && } +
+ ); +} + +/* ========= RESULT ========= */ + +function BaziResult({ result, birth }: { result: BaziFullResult; birth: UserProfile }) { + const ec = result.eightChar; + const ep = useMemo(() => analyzeElementBalance(ec), [ec]); + const shensha = useMemo(() => analyzeShensha(ec), [ec]); + const today = useMemo(() => getTodayInfo(), []); + const decadeLuck = useMemo( + () => result.decadeFortunes.map(df => ({ ...df, luck: analyzeFortuneGanzhi(df.ganzhi, ec.dayMasterStem, ec.dayPillar.earthBranch) })), + [result, ec], + ); + + // —— 大运·流年·流月·流日 交互下钻 —— + const navigate = useNavigate(); + const [selDecade, setSelDecade] = useState(null); + const [selYear, setSelYear] = useState(null); + const [selMonth, setSelMonth] = useState(null); + + const defaultDecadeIdx = Math.max(0, decadeLuck.findIndex(d => today.solarYear >= d.startYear && today.solarYear <= d.endYear)); + const effDecadeIdx = selDecade ?? defaultDecadeIdx; + const effDecade = decadeLuck[effDecadeIdx] ?? decadeLuck[0]; + const decadeYears = useMemo(() => { + if (!effDecade) return []; + const ys: { year: number; ganzhi: string; luck: FortuneLuck }[] = []; + for (let y = effDecade.startYear; y <= effDecade.endYear; y++) { + const gz = getDayInfo(y, 6, 15).lunarYearGanzhi; + ys.push({ year: y, ganzhi: gz, luck: analyzeFortuneGanzhi(gz, ec.dayMasterStem, ec.dayPillar.earthBranch) }); + } + return ys; + }, [effDecade, ec]); + const effYear = selYear + ?? Math.min(Math.max(today.solarYear, decadeYears[0]?.year ?? today.solarYear), decadeYears[decadeYears.length - 1]?.year ?? today.solarYear); + const selYearGanzhi = getDayInfo(effYear, 6, 15).lunarYearGanzhi; + const selYearLuck = useMemo( + () => analyzeFortuneGanzhi(selYearGanzhi, ec.dayMasterStem, ec.dayPillar.earthBranch), + [selYearGanzhi, ec], + ); + const yearMonths = useMemo( + () => getYearMonths(effYear).map(m => ({ ...m, luck: analyzeFortuneGanzhi(m.ganzhi, ec.dayMasterStem, ec.dayPillar.earthBranch) })), + [effYear, ec], + ); + const effMonthIdx = selMonth ?? Math.max(0, yearMonths.findIndex(m => today.solarDate >= m.startDate && today.solarDate <= m.endDate)); + const effMonth = yearMonths[effMonthIdx]; + const selMonthDays = useMemo(() => { + if (!effMonth) return []; + const [y1, m1, d1] = effMonth.startDate.split('-').map(Number); + const [y2, m2, d2] = effMonth.endDate.split('-').map(Number); + const days: { date: string; day: number; ganzhi: string; luck: FortuneLuck }[] = []; + const cur = new Date(y1, m1 - 1, d1); + const end = new Date(y2, m2 - 1, d2); + while (cur <= end) { + const di = getDayInfo(cur.getFullYear(), cur.getMonth() + 1, cur.getDate()); + days.push({ + date: di.solarDate, + day: di.solarDay, + ganzhi: di.lunarDayGanzhi, + luck: analyzeFortuneGanzhi(di.lunarDayGanzhi, ec.dayMasterStem, ec.dayPillar.earthBranch), + }); + cur.setDate(cur.getDate() + 1); + } + return days; + }, [effMonth, ec]); + const bone = useMemo(() => { + try { + const yearGz = ec.yearPillar.ganzhi; + const sixtyCycle = ['甲子','乙丑','丙寅','丁卯','戊辰','己巳','庚午','辛未','壬申','癸酉','甲戌','乙亥','丙子','丁丑','戊寅','己卯','庚辰','辛巳','壬午','癸未','甲申','乙酉','丙戌','丁亥','戊子','己丑','庚寅','辛卯','壬辰','癸巳','甲午','乙未','丙申','丁酉','戊戌','己亥','庚子','辛丑','壬寅','癸卯','甲辰','乙巳','丙午','丁未','戊申','己酉','庚戌','辛亥','壬子','癸丑','甲寅','乙卯','丙辰','丁巳','戊午','己未','庚申','辛酉','壬戌','癸亥']; + const yearIdx = sixtyCycle.indexOf(yearGz); + const hi = Math.floor(((birth.birthHour + 1) % 24) / 2); + const di = getDayInfo(birth.birthYear, birth.birthMonth, birth.birthDay); + return calculateBoneWeight(yearIdx >= 0 ? yearIdx : 0, di.lunarMonth, di.lunarDay, hi); + } catch { return null; } + }, [ec, birth]); + + const mingGe = useMemo(() => { + const dm = ec.dayMasterElement; + const counts: Record = { '木':0,'火':0,'土':0,'金':0,'水':0 }; + const pillars = PK.map(k => ec[`${k}Pillar` as keyof EightCharInfo] as PillarInfo); + for (const p of pillars) { counts[p.elementStem] = (counts[p.elementStem]||0)+1; counts[p.elementBranch] = (counts[p.elementBranch]||0)+1; } + const dmCount = counts[dm]||0; + let pattern = dmCount >= 4 ? `${dm}旺(身强)` : dmCount <= 2 ? `${dm}弱(身弱)` : `${dm}中和`; + const hasWater=counts['水']>=3, hasFire=counts['火']>=3, hasWood=counts['木']>=3, hasMetal=counts['金']>=3; + if (hasWater&&hasFire) pattern += ' · 水火既济'; + else if (hasWood&&hasFire) pattern += ' · 木火通明'; + else if (hasMetal&&hasWater) pattern += ' · 金水相生'; + return pattern; + }, [ec]); + + const siLing = useMemo(() => { + const mb = ec.monthPillar.earthBranch; + const be: Record = { '寅':'木','卯':'木','辰':'土','巳':'火','午':'火','未':'土','申':'金','酉':'金','戌':'土','亥':'水','子':'水','丑':'土' }; + const bs: Record = { '寅':'甲','卯':'乙','辰':'戊','巳':'丙','午':'丁','未':'己','申':'庚','酉':'辛','戌':'戊','亥':'壬','子':'癸','丑':'己' }; + return `月令${mb}(${be[mb]||''})司令${bs[mb]||''}`; + }, [ec]); + + return ( + + +
+ 日主 + {ec.dayMaster} + {mingGe} +
+
+ {PK.map(k => { + const p = ec[`${k}Pillar` as keyof EightCharInfo] as PillarInfo; + return ( +
+

{PL[k]}

+

{p.heavenStem}{p.earthBranch}

+

{p.nayin}

+

{p.elementStem}/{p.elementBranch}

+
+ ); + })} +
+
+

胎元

{ec.fetalOrigin}

+

命宫

{ec.ownSign}

+

身宫

{ec.bodySign}

+

空亡

{ec.emptyBranches[0]||'-'}

+
+

{siLing}

+
+ + {/* Interactions */} + +

柱间生克冲合

+
+ {computeInteractions(ec).map((x) => ( +
+ {x.pair} + {x.detail} + {x.significant && {x.good?'吉':'冲'}} +
+ ))} +
+
+ +
+ +

十神

+ {PK.map(k => { + const p = ec[`${k}Pillar` as keyof EightCharInfo] as PillarInfo; + return
{PL[k]}{k==='day'?'日主':p.tenStar||'-'}
; + })} +
+ +

藏干

+ {PK.map(k => { + const p = ec[`${k}Pillar` as keyof EightCharInfo] as PillarInfo; + return
{PL[k]}{p.earthBranch}{p.hideStems.map(h=>`${h.stem}(${h.type})`).join(' ')}
; + })} +
+ +

五行力量 ({mingGe})

+ {(['木','火','土','金','水'] as const).map(el => { + const c = ep[el.toLowerCase() as keyof typeof ep] as number; + const pct = ep.total>0?Math.round(c/ep.total*100):0; + return ( +
+ {el} +
+ +
+ {c} ({pct}%) +
+ ); + })} +
+
+ + {/* Shen Sha */} + {shensha.length > 0 && ( + +

神煞 共{shensha.length}个

+
+ {shensha.map(s => ( +
+ {s.name} + [{s.foundIn.join('、')}] + {s.description} +
+ ))} +
+

神煞为传统命理标记,仅供娱乐参考,不同流派查法略有差异

+
+ )} + + {/* 大运·流年·流月·流日(交互下钻) */} + +

大运·流年·流月·流日

+

{result.childLimit.yearCount}岁{result.childLimit.monthCount}月起运({result.childLimit.forward?'顺':'逆'}排)· 点选大运→流年→流月,查看每日流日

+ {/* 大运 */} +
+ {decadeLuck.map((df, i) => ( + + ))} +
+ {/* 流年 */} +
+ {decadeYears.map(y => ( + + ))} +
+ {/* 选中流年 vs 日主 */} +
+ {selYearLuck && } +
+ {/* 流月 */} +
+ {yearMonths.map((m, i) => ( + + ))} +
+ {/* 选中流月:每日流日 */} + {selMonthDays.length > 0 && effMonth && ( +
+

{effMonth.name}({effMonth.startDate} ~ {effMonth.endDate})· 点击日期查看当日详情

+
+ {selMonthDays.map(d => ( + + ))} +
+
+ )} +
+ + {bone && ( + +

称骨算命

+
{bone.totalLiang}两{bone.totalQian}钱{bone.fortune} +

{bone.interpretation}

+
+ )} + + +

十二长生

+
+ {PK.map(k => { + const p = ec[`${k}Pillar` as keyof EightCharInfo] as PillarInfo; + return

{PL[k]}

{p.terrain.name||'-'}

; + })} +
+
+
+ ); +} + +/* ====== HELPERS ====== */ + +interface PillarInteraction { key:string; pair:string; detail:string; significant:boolean; good:boolean; } + +function computeInteractions(ec: EightCharInfo): PillarInteraction[] { + const r: PillarInteraction[] = []; + const pillars = PK.map(k => ec[`${k}Pillar` as keyof EightCharInfo] as PillarInfo); + const pairs = [[0,1,'年·月'],[0,2,'年·日'],[0,3,'年·时'],[1,2,'月·日'],[1,3,'月·时'],[2,3,'日·时']] as const; + for (const [a,b,name] of pairs) { + const pa=pillars[a], pb=pillars[b]; + if (checkStemCombine(pa.heavenStem,pb.heavenStem)) r.push({key:`${name}-sc`,pair:name,detail:`天干五合:${pa.heavenStem}合${pb.heavenStem}`,significant:true,good:true}); + if (checkStemOpposite(pa.heavenStem,pb.heavenStem)) r.push({key:`${name}-so`,pair:name,detail:`天干相冲:${pa.heavenStem}冲${pb.heavenStem}`,significant:true,good:false}); + const br = getBranchRelationship(pa.earthBranch,pb.earthBranch); + if (br.combine) r.push({key:`${name}-bc`,pair:name,detail:`地支六合:${pa.earthBranch}合${pb.earthBranch}`,significant:true,good:true}); + if (br.threeCombine&&br.formation) r.push({key:`${name}-b3`,pair:name,detail:`三合局${br.formation}:${pa.earthBranch}${pb.earthBranch}`,significant:true,good:true}); + if (br.opposite) r.push({key:`${name}-bo`,pair:name,detail:`地支六冲:${pa.earthBranch}冲${pb.earthBranch}`,significant:true,good:false}); + if (br.harm) r.push({key:`${name}-bh`,pair:name,detail:`地支六害:${pa.earthBranch}害${pb.earthBranch}`,significant:true,good:false}); + if (br.punish) r.push({key:`${name}-bp`,pair:name,detail:`地支相刑:${pa.earthBranch}刑${pb.earthBranch}`,significant:true,good:false}); + } + return r; +} + +function LuckRow({ ganzhi, meta, luck }: { ganzhi: string; meta: string; luck: FortuneLuck }) { + const markers = [ + luck.stemCombine || luck.branchCombine || luck.branchThreeCombine ? '合' : null, + luck.stemOpposite || luck.branchOpposite ? '冲' : null, + luck.branchHarm ? '害' : null, + luck.branchPunish ? '刑' : null, + ].filter(Boolean); + return ( +
+ + {ganzhi} + {meta} + {luck.tenStar ? `${luck.tenStar}·${luck.elementRelation}` : luck.elementRelation} + {markers.length > 0 && {markers.join('')}} +
+ ); +} diff --git a/web/packages/web/src/pages/CalendarPage.tsx b/web/packages/web/src/pages/CalendarPage.tsx new file mode 100644 index 0000000..e564f66 --- /dev/null +++ b/web/packages/web/src/pages/CalendarPage.tsx @@ -0,0 +1,110 @@ +import { useRef, useCallback, useMemo } from 'react'; +import { motion } from 'framer-motion'; +import { CalendarGrid } from '../components/calendar/CalendarGrid'; +import { useCalendar } from '../hooks/useCalendar'; +import { getTodayInfo, getAlmanacInfo, calculateDailyFortune } from '@lunar/core'; +import { Badge } from '../components/ui/Badge'; +import { useUserStore } from '../stores/user'; +import { useBookmarkStore } from '../stores/bookmarks'; +import { useNavigate } from 'react-router-dom'; + +export default function CalendarPage() { + const navigate = useNavigate(); + const { viewDate, selectedDate, weekStart, days, goToToday, goToPrevMonth, goToNextMonth, goToPrevYear, goToNextYear, selectDate } = useCalendar(); + const eightChar = useUserStore(s => s.eightChar); + const ec = eightChar; + const bookmarks = useBookmarkStore(s => s.items); + + // Bookmarked dates as Set for quick lookup + const bookmarkedDates = useMemo(() => { + const set = new Set(); + for (const bm of bookmarks) { + if (!bm.isLunar) set.add(bm.solarDate); + else { + // For lunar recurring, find matching solar dates in current view + for (const week of days) { + for (const d of week) { + if (d.lunarMonth === bm.lunarMonth && d.lunarDay === bm.lunarDay && !d.isLeapMonth) { + set.add(d.solarDate); + } + } + } + } + } + return set; + }, [bookmarks, days]); + const touchRef = useRef({ startX: 0, startY: 0 }); + const onTouchStart = useCallback((e: React.TouchEvent) => { touchRef.current = { startX: e.touches[0].clientX, startY: e.touches[0].clientY }; }, []); + const onTouchEnd = useCallback((e: React.TouchEvent) => { + const { startX, startY } = touchRef.current; + const dx = e.changedTouches[0].clientX - startX; + const dy = e.changedTouches[0].clientY - startY; + if (Math.abs(dx) > Math.abs(dy) && Math.abs(dx) > 50) { + if (dx > 0) goToPrevMonth(); else goToNextMonth(); + } + }, [goToPrevMonth, goToNextMonth]); + + // Today's quick summary + const todaySummary = useMemo(() => { + const now = new Date(); + return { di: getTodayInfo(), al: getAlmanacInfo(now.getFullYear(), now.getMonth() + 1, now.getDate()) }; + }, []); + + // "回到今天" shows only when the viewed date is not today + const isViewingToday = useMemo(() => { + const now = new Date(); + return ( + viewDate.getFullYear() === now.getFullYear() && + viewDate.getMonth() === now.getMonth() && + viewDate.getDate() === now.getDate() + ); + }, [viewDate]); + + // Fortune scores for visible days (if user has bazi) + const fortuneScores = useMemo(() => { + if (!ec) return null; + const scores: Record = {}; + for (const week of days) { + for (const d of week) { + try { + const [y, m, day] = d.solarDate.split('-').map(Number); + scores[d.solarDate] = calculateDailyFortune(ec, new Date(y, m-1, day)).overallScore; + } catch { scores[d.solarDate] = 0; } + } + } + return scores; + }, [days, ec]); + + return ( + + {/* Today quick info — compact */} + + + + + {/* Floating Today button */} + {!isViewingToday && ( + + )} + + ); +} diff --git a/web/packages/web/src/pages/DailyFortunePage.tsx b/web/packages/web/src/pages/DailyFortunePage.tsx new file mode 100644 index 0000000..9fa2dc0 --- /dev/null +++ b/web/packages/web/src/pages/DailyFortunePage.tsx @@ -0,0 +1,197 @@ +import { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { Stars, AlertTriangle, ChevronLeft, ChevronRight, Sparkles } from 'lucide-react'; +import { motion } from 'framer-motion'; +import { useDailyFortune } from '../hooks/useDailyFortune'; +import { useUserStore } from '../stores/user'; +import { Badge } from '../components/ui/Badge'; +import { Button } from '../components/ui/Button'; +import { formatDate } from '../lib/utils'; +import type { DailyFortuneResult } from '@lunar/core'; + +const LC: Record = { + great:{label:'大吉',color:'#E53935',emoji:'🎉'}, good:{label:'吉',color:'#FB8C00',emoji:'👍'}, + fair:{label:'平',color:'#FDD835',emoji:'😐'}, poor:{label:'凶',color:'#78909C',emoji:'😟'}, bad:{label:'大凶',color:'#424242',emoji:'⚠️'}, +}; +const RE: Record = { '正印':'📚','偏印':'🧠','食神':'🎨','伤官':'💬','正财':'💰','偏财':'💎','正官':'👔','七杀':'⚔️','比肩':'🤝','劫财':'🏃' }; + +export default function DailyFortunePage() { + const navigate = useNavigate(); + const [targetDate, setTargetDate] = useState(new Date()); + const { fortune, needsProfile } = useDailyFortune(targetDate); + const profile = useUserStore(s => s.activeProfile); + const goToday = () => setTargetDate(new Date()); + const goNext = () => { const d = new Date(targetDate); d.setDate(d.getDate()+1); setTargetDate(d); }; + const goPrev = () => { const d = new Date(targetDate); d.setDate(d.getDate()-1); setTargetDate(d); }; + const isToday = targetDate.toDateString() === new Date().toDateString(); + + if (needsProfile) { + return ( + + + + +

需要您的八字

+

输入出生日期即可查看每日个性化运势

+ +
+ ); + } + + return ( +
+
+

每日运势

+ {profile && {profile.gender==='male'?'♂':'♀'} {profile.birthYear}年} +
+ + {/* Date selector */} +
+ +
+ +

{isToday?'今天':''}

+
+ +
+ + {fortune && } +
+ ); +} + +function FortuneView({ fortune }: { fortune: DailyFortuneResult }) { + const cfg = LC[fortune.scoreLevel]; + const pct = (fortune.overallScore+100)/2; + return ( + + {/* Score card */} + +
+
+

{fortune.lunarDate}

+
+ + + + +
+ {fortune.overallScore} + {cfg.emoji} {cfg.label} +
+
+
+ + + {/* Category Scores */} + +

运势细分

+
+ {([ + {e:'💕',l:'爱情',s: fortune.categoryScores.love, hi:'感情运佳,适合约会表白', mid:'感情平稳,适合日常相处', lo:'感情需注意沟通,避免误会'}, + {e:'💼',l:'事业',s: fortune.categoryScores.career, hi:'事业运势旺盛,适合推进重要项目', mid:'工作平稳,按部就班即可', lo:'职场多注意,避免冲突和失误'}, + {e:'💰',l:'财运',s: fortune.categoryScores.wealth, hi:'财运亨通,适合投资理财', mid:'财运平稳,量入为出', lo:'财运低迷,避免大额支出'}, + {e:'💪',l:'健康',s: fortune.categoryScores.health, hi:'精力充沛,适合运动锻炼', mid:'身体状态尚可,注意休息', lo:'容易疲劳,注意劳逸结合'}, + ] as const).map(({e,l,s,hi,mid,lo}) => ( +
+

{e}

+

{l}

+

=20?'text-red-500':s<=-20?'text-slate-500':'text-amber-500'}`}>{s}

+
+
=20?'bg-red-500':s<=-20?'bg-slate-400':'bg-amber-400'}`} style={{width:`${(s+100)/2}%`}}/> +
+

{s>=20?hi:s<=-20?lo:mid}

+
+ ))} +
+ + + {/* Lucky Meta */} + +

今日幸运指南

+
+
+

幸运色

+
+ {fortune.luckyMeta.colors.map(c => {c})} +
+
+
+

幸运数字

+

{fortune.luckyMeta.numbers.join(' · ')}

+
+
+

吉利方位

+

{fortune.luckyMeta.direction}

+
+
+
+ + {/* Pillar comparison */} + +

四柱精析

+ {fortune.pillarRelationships.map((rel,idx) => ( + + {rel.pillarLabel} +
+
+ {rel.userGanzhi} + vs + {rel.dayGanzhi} +
+
+ {rel.stemTenStar && {RE[rel.stemTenStar]||''}{rel.stemTenStar}} + {rel.stemCombine && } + {rel.stemOpposite && 天冲} + {rel.branchCombine && 六合} + {rel.branchThreeCombine && {rel.branchFormation}} + {rel.branchOpposite && 六冲} + {rel.branchHarm && } + {rel.branchPunish && } +
+
+ 0?'text-red-500':rel.score<0?'text-slate-500':'text-muted'}`}> + {rel.score>0?'+':''}{rel.score} + +
+ ))} +
+ + {/* Lucky & Unlucky */} +
+ +

吉利

+ {fortune.luckyAspects.map((a,i)=>

{a}

)} +
+ {fortune.unluckyAspects.length>0 && ( + +

注意

+ {fortune.unluckyAspects.map((a,i)=>

{a}

)} +
+ )} +
+ + {/* Suggestions */} + +

今日建议

+ {fortune.suggestions.map((s,i)=>( + + {i+1} + {s} + + ))} +
+ + ); +} diff --git a/web/packages/web/src/pages/DayDetailPage.tsx b/web/packages/web/src/pages/DayDetailPage.tsx new file mode 100644 index 0000000..7ecbb62 --- /dev/null +++ b/web/packages/web/src/pages/DayDetailPage.tsx @@ -0,0 +1,225 @@ +import { useParams, useNavigate } from 'react-router-dom'; +import { useMemo } from 'react'; +import { ArrowLeft, ChevronLeft, ChevronRight, Moon, AlertTriangle, TrendingUp, Star } from 'lucide-react'; +import { motion } from 'framer-motion'; +import { getDayInfo, getAlmanacInfo, birthInfoToBazi, calculateDailyFortune } from '@lunar/core'; +import { Badge } from '../components/ui/Badge'; +import { Button } from '../components/ui/Button'; +import { useUserStore } from '../stores/user'; +import { useBookmarkStore } from '../stores/bookmarks'; +import type { DailyFortuneResult } from '@lunar/core'; + +function dateAddDays(dateStr: string, days: number): string { + const [y, m, d] = dateStr.split('-').map(Number); + const dt = new Date(y, m - 1, d); + dt.setDate(dt.getDate() + days); + return `${dt.getFullYear()}-${String(dt.getMonth()+1).padStart(2,'0')}-${String(dt.getDate()).padStart(2,'0')}`; +} + +export default function DayDetailPage() { + const { date } = useParams<{ date: string }>(); const navigate = useNavigate(); + const eightChar = useUserStore(s => s.eightChar); + const ec = eightChar; + const { items: bookmarks, add: addBookmark, remove: removeBookmark } = useBookmarkStore(); + + const { dayInfo, almanac, dayBazi, prevDate, nextDate, fortune } = useMemo(() => { + if (!date) return { dayInfo: null, almanac: null, dayBazi: null, prevDate: null, nextDate: null, fortune: null }; + const [y, m, d] = date.split('-').map(Number); + const bz = birthInfoToBazi({ year: y, month: m, day: d, hour: 12, minute: 0, gender: 'male' }); + let ft: DailyFortuneResult | null = null; + if (ec) { try { ft = calculateDailyFortune(ec, new Date(y, m-1, d)); } catch { /* */ } } + return { + dayInfo: getDayInfo(y, m, d), almanac: getAlmanacInfo(y, m, d), dayBazi: bz.eightChar, + prevDate: dateAddDays(date, -1), nextDate: dateAddDays(date, 1), fortune: ft, + }; + }, [date, ec]); + + if (!dayInfo || !almanac) { + return

日期无效

; + } + + return ( + + {/* Navigation row */} +
+ +
+ {prevDate && } + {dayInfo.solarDate} + {nextDate && } +
+
+
+ + {/* Header */} +
+

周{dayInfo.weekDay}

+

{dayInfo.lunarMonthName}{dayInfo.lunarDayName}

+

{dayInfo.lunarYearGanzhi}年 · 生肖{dayInfo.zodiac} · {dayInfo.constellation}

+ +
+ {dayInfo.solarTerm && {dayInfo.solarTerm}{dayInfo.solarTermTime ? ` ${dayInfo.solarTermTime}` : ''}} + {dayInfo.lunarFestival && {dayInfo.lunarFestival}} + {dayInfo.buddhistFestival && 🪷 {dayInfo.buddhistFestival}} + {dayInfo.moonPhase && {dayInfo.moonPhase}} +
+
+ + {/* Personalized fortune (if user has bazi) */} + {fortune && ( + + )} + + {/* Day's Four Pillars */} + {dayBazi && ( +
+

当日四柱

+
+ {[ + ['年', dayBazi.yearPillar.ganzhi],['月', dayBazi.monthPillar.ganzhi], + ['日', dayBazi.dayPillar.ganzhi],['时', dayBazi.hourPillar.ganzhi], + ].map(([l, v]) => ( +
+

{l}柱

+

{v}

+
+ ))} +
+
+ )} + + {/* NaYin */} +
+

纳音

+

{almanac.nayin}

+

{almanac.dayGanzhi} · {almanac.dayStem}{almanac.dayBranch}

+
+ + {/* Calendar extras */} +
+

历法信息

+
+ 季节{dayInfo.season || '—'} + 节气 + + {dayInfo.currentSolarTerm + ? `${dayInfo.currentSolarTerm} 第${dayInfo.termDayIndex ?? '?'}天${dayInfo.daysToNextTerm != null ? `(距${dayInfo.nextSolarTerm}还有${dayInfo.daysToNextTerm}天)` : ''}` + : '—'} + + 儒略日{dayInfo.julianDay ?? '—'} + 佛历{dayInfo.buddhistYear ? `${dayInfo.buddhistYear}年` : '—'} + 伊斯兰历{dayInfo.hijriDate || '—'} +
+
+ + {/* Stars grid */} +
+ {[ + ['建除', almanac.duty], ['黄道', almanac.twelveStar.name], ['星宿', almanac.twentyEightStar.name], + ['九星', almanac.nineStar.name], ['六曜', almanac.sixStar], ['小六壬', almanac.minorRen.name], + ].map(([l, v]) => ( +
+

{l}

{v}

+
+ ))} +
+ + {/* Chong Sha */} +
+

冲·合·害·煞

+
+

{almanac.clash}

+

{almanac.evilDirection}

+

{almanac.combine}

+

{almanac.harm}

+
+
+ + {/* Yi Ji */} +
+
+

+
+ {almanac.recommends.map((r,i)=>{r})} + {almanac.recommends.length===0 && } +
+
+
+

+
+ {almanac.avoids.map((a,i)=>{a})} + {almanac.avoids.length===0 && } +
+
+
+ + {/* Gods */} +
+
+

吉神

+
{almanac.goodGods.slice(0,6).map((g,i)=>{g})}
+
+

凶神

+
{almanac.badGods.slice(0,6).map((b,i)=>{b})}
+
+
+
+ + {/* PengZu + Fetus */} +
+
+ +
+

彭祖百忌

+

{almanac.pengZu}

+

天干:{almanac.pengZuStem} · 地支:{almanac.pengZuBranch} · 胎神:{almanac.fetus.position}

+
+
+
+ + {/* Hourly */} + {almanac.hourDetails.length > 0 && ( +
+

时辰吉凶

+
+ {almanac.hourDetails.map(h => ( +
+

{h.name}

+

{h.ganzhi}

+
+ ))} +
+
+ )} + + ); +} + +function BookmarkButton({ date, bookmarks, add, remove }: { + date: string; + bookmarks: import('../stores/bookmarks').Bookmark[]; + add: (b: Omit) => void; + remove: (id: string) => void; +}) { + const existing = bookmarks.find(b => b.solarDate === date); + const toggle = () => { + if (existing) { remove(existing.id); } + else { add({ solarDate: date, label: '', isLunar: false, color: '#E53935' }); } + }; + return ( + + ); +} diff --git a/web/packages/web/src/pages/DivinationPage.tsx b/web/packages/web/src/pages/DivinationPage.tsx new file mode 100644 index 0000000..9983fcd --- /dev/null +++ b/web/packages/web/src/pages/DivinationPage.tsx @@ -0,0 +1,144 @@ +import { useMemo } from 'react'; +import { motion } from 'framer-motion'; +import { Compass, Scale } from 'lucide-react'; +import { calculatePlumBlossom, calculateBoneWeight, getTodayInfo } from '@lunar/core'; +import { Badge } from '../components/ui/Badge'; +import { useUserStore } from '../stores/user'; + +// Hexagram line rendering +const TRIGRAM_BITS: Record = { + 1:[1,1,1],2:[1,1,0],3:[1,0,1],4:[1,0,0],5:[0,1,1],6:[0,1,0],7:[0,0,1],8:[0,0,0], +}; + +function HexagramLines({ upperIdx, lowerIdx, changingLine }: { upperIdx: number; lowerIdx: number; changingLine: number }) { + const upper = TRIGRAM_BITS[upperIdx] || [0,0,0]; + const lower = TRIGRAM_BITS[lowerIdx] || [0,0,0]; + // Lines from top (6) to bottom (1): upper[2],upper[1],upper[0], lower[2],lower[1],lower[0] + const lines = [upper[2], upper[1], upper[0], lower[2], lower[1], lower[0]]; + + return ( +
+ {lines.map((isYang, i) => { + const lineNum = 6 - i; // line numbers: 6,5,4,3,2,1 + const isChanging = lineNum === changingLine; + return ( +
+ {lineNum} + {isYang ? ( +
+ ) : ( +
+
+
+
+ )} + {isChanging && } +
+ ); + })} +
+ ); +} + +export default function DivinationPage() { + const ec = useUserStore(s => s.eightChar); + + const { plum, bone } = useMemo(() => { + const now = new Date(); const y=now.getFullYear(), m=now.getMonth()+1, d=now.getDate(), h=now.getHours(); + const plumResult = calculatePlumBlossom(y,m,d,h); + let boneResult = null; + if (ec) { + try { + const di = getTodayInfo(); + const hi = Math.floor(((h+1)%24)/2); + const yi = getLunarYearIdx(ec.yearPillar.ganzhi); + boneResult = calculateBoneWeight(yi, di.lunarMonth, di.lunarDay, hi); + } catch { /* */ } + } + return { plum: plumResult, bone: boneResult }; + }, [ec]); + + return ( +
+

占卜

+ + {!ec && ( + + +

请先在「八字」页面输入出生信息

+

即可查看梅花易数与称骨算命

+
+ )} + + {/* 梅花易数 */} + {plum && ( + +

梅花易数 以今日时间起卦

+ +
+

下卦

{plum.lowerTrigram.symbol}

{plum.lowerTrigram.name}

+ + +

上卦

{plum.upperTrigram.symbol}

{plum.upperTrigram.name}

+ = +

本卦

+
{plum.originalHexagram.upperTrigram.symbol}{plum.originalHexagram.lowerTrigram.symbol}
+

{plum.originalHexagram.name}

+
+
+ + {/* Hexagram line visualization */} + + +
+

本卦

{plum.originalHexagram.name}

第{plum.originalHexagram.number}卦

+

互卦

{plum.mutualHexagram?.name||'—'}

+

变卦

{plum.transformedHexagram?.name||'—'}

+
+ +
+ 体卦:{plum.lowerTrigram.name}({plum.constitution}) + 用卦:{plum.upperTrigram.name}({plum.function}) + 动爻:第{plum.changingLine}爻 +
+

{plum.relationship}

+ +
+

卦辞

+

{plum.originalHexagram.judgment}

+

象辞

+

{plum.originalHexagram.image}

+
+ +
+

爻辞(动爻高亮)

+ {plum.originalHexagram.lines.map((line,i)=>line?( +

第{i+1}爻:{line}{i+1===plum.changingLine?' ←':''}

+ ):null)} +
+
+ )} + + {/* 称骨 */} + {bone && ( + +

称骨算命

+
+ {[{l:'年',w:bone.yearWeight},{l:'月',w:bone.monthWeight},{l:'日',w:bone.dayWeight},{l:'时',w:bone.hourWeight}].map(x=>( +

{x.l}

{x.w}

+ ))} +
+
+

{bone.totalLiang}两{bone.totalQian}钱

+ {bone.fortune} +
+

{bone.interpretation}

+
+ )} +
+ ); +} + +function getLunarYearIdx(gz: string): number { + const t = ['甲子','乙丑','丙寅','丁卯','戊辰','己巳','庚午','辛未','壬申','癸酉','甲戌','乙亥','丙子','丁丑','戊寅','己卯','庚辰','辛巳','壬午','癸未','甲申','乙酉','丙戌','丁亥','戊子','己丑','庚寅','辛卯','壬辰','癸巳','甲午','乙未','丙申','丁酉','戊戌','己亥','庚子','辛丑','壬寅','癸卯','甲辰','乙巳','丙午','丁未','戊申','己酉','庚戌','辛亥','壬子','癸丑','甲寅','乙卯','丙辰','丁巳','戊午','己未','庚申','辛酉','壬戌','癸亥']; + return t.indexOf(gz); +} diff --git a/web/packages/web/src/pages/HomePage.tsx b/web/packages/web/src/pages/HomePage.tsx new file mode 100644 index 0000000..5e6326f --- /dev/null +++ b/web/packages/web/src/pages/HomePage.tsx @@ -0,0 +1,164 @@ +import { useNavigate } from 'react-router-dom'; +import { Stars, TrendingUp, CalendarDays, Sparkles, AlertTriangle, Compass } from 'lucide-react'; +import { motion } from 'framer-motion'; +import { getTodayInfo, getAlmanacInfo, calculateDailyFortune } from '@lunar/core'; +import { useMemo } from 'react'; +import { Badge } from '../components/ui/Badge'; +import { useUserStore } from '../stores/user'; + +export default function HomePage() { + const navigate = useNavigate(); + const eightChar = useUserStore(s => s.eightChar); + + const { dayInfo, almanac, fortune } = useMemo(() => { + const now = new Date(); const y=now.getFullYear(), m=now.getMonth()+1, d=now.getDate(); + const di = getTodayInfo(), al = getAlmanacInfo(y,m,d); + let ft = null; if (eightChar) { try { ft = calculateDailyFortune(eightChar, now); } catch { /* */ } } + return { dayInfo: di, almanac: al, fortune: ft }; + }, [eightChar]); + + const solarDate = `${dayInfo.solarYear}年${dayInfo.solarMonth}月${dayInfo.solarDay}日 星期${dayInfo.weekDay}`; + + return ( +
+ {/* === HERO: Today's lunar date === */} + +

{solarDate}

+

{dayInfo.lunarMonthName}{dayInfo.lunarDayName}

+

{dayInfo.lunarYearGanzhi}年 · 生肖{dayInfo.zodiac} · 日柱{almanac.dayGanzhi}

+
+ {dayInfo.solarTerm && 🌿 {dayInfo.solarTerm}} + {dayInfo.lunarFestival && {dayInfo.lunarFestival}} + {dayInfo.solarFestival && {dayInfo.solarFestival}} + {dayInfo.moonPhase && {dayInfo.moonPhase}} +
+
+ + {/* === FORTUNE (if bazi set) === */} + {fortune ? ( + + {/* Score + quick actions */} +
+ +
+

今日运势

+
+ {fortune.luckyAspects.slice(0,1).map((a,i)=>

{a}

)} + {fortune.unluckyAspects.slice(0,1).map((a,i)=>

{a}

)} +
+
+ 🎨 {fortune.luckyMeta.colors[0]} + 🔢 {fortune.luckyMeta.numbers.join('/')} + 🧭 {fortune.luckyMeta.direction} +
+
+
+
+ ) : ( + navigate('/bazi')} + className="w-full bg-card rounded-xl border border-dashed border-border p-4 text-center hover:bg-foreground/[0.02] transition-colors"> + +

设置您的八字

+

获取个性化每日运势

+
+ )} + + {/* === ALMANAC KEY INFO === */} + + + + + + + + {/* === YI JI === */} + +
+

宜 {almanac.recommends.length > 0 && `· ${almanac.recommends.length}项`}

+
+ {almanac.recommends.slice(0,10).map((r,i)=>{r})} + {almanac.recommends.length > 10 && …共{almanac.recommends.length}项} + {almanac.recommends.length===0 && } +
+
+
+

忌 {almanac.avoids.length > 0 && `· ${almanac.avoids.length}项`}

+
+ {almanac.avoids.slice(0,8).map((a,i)=>{a})} + {almanac.avoids.length > 8 && …共{almanac.avoids.length}项} + {almanac.avoids.length===0 && } +
+
+
+ + {/* === QUICK NAV === */} + + } label="日历" onClick={()=>navigate('/calendar')}/> + } label="八字" onClick={()=>navigate('/bazi')}/> + } label="运势" onClick={()=>navigate('/daily-fortune')}/> + } label="占卜" onClick={()=>navigate('/divination')}/> + + + + + + {/* === GODS === */} + +
+ 吉神 + {almanac.goodGods.slice(0,5).join('、')||'无'} +
+
+ 凶神 + {almanac.badGods.slice(0,5).join('、')||'无'} +
+
+ 彭祖 + {almanac.pengZu} +
+
+
+ ); +} + +function ScoreRing({ score }: { score: number }) { + const pct = (score+100)/2; + const color = score>=20?'#E53935':score<=-20?'#78909C':'#FB8C00'; + const r=20; const c=2*Math.PI*r; + return ( +
+ + + + + {score} +
+ ); +} + +function InfoCard({ label, value, sub, highlight }: { label: string; value: string; sub?: string; highlight?: boolean }) { + return ( +
+

{label}

+

{value}

+ {sub &&

{sub}

} +
+ ); +} + +function QuickBtn({ icon, label, onClick }: { icon: React.ReactNode; label: string; onClick: () => void }) { + return ( + + ); +} diff --git a/web/packages/web/src/pages/SettingsPage.tsx b/web/packages/web/src/pages/SettingsPage.tsx new file mode 100644 index 0000000..58726c4 --- /dev/null +++ b/web/packages/web/src/pages/SettingsPage.tsx @@ -0,0 +1,145 @@ +import { useState, useMemo } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { useSettingsStore } from '../stores/settings'; +import { useUserStore } from '../stores/user'; +import { Button } from '../components/ui/Button'; +import { getTodayInfo, getMonthCalendar } from '@lunar/core'; +import { Sun, Moon, Monitor, Stars, Search, Trash2 } from 'lucide-react'; + +export default function SettingsPage() { + const navigate = useNavigate(); + const { theme, weekStartDay, ziHourSect, solarTime, setTheme, setWeekStartDay, setZiHourSect, setSolarTime, resetDefaults } = useSettingsStore(); + const { activeProfile: profile, activeBazi: baziResult, clearAll } = useUserStore(); + const [lunarSearch, setLunarSearch] = useState({ month: 1, day: 1 }); + + // Find next occurrence of a lunar date + const lunarResult = useMemo(() => { + try { + const now = new Date(); + const curYear = now.getFullYear(); + // Search from current year forward + for (let y = curYear; y <= curYear + 3; y++) { + for (let m = 1; m <= 12; m++) { + const cal = getMonthCalendar(y, m); + for (const week of cal) { + for (const d of week) { + if (d.lunarMonth === lunarSearch.month && d.lunarDay === lunarSearch.day && !d.isLeapMonth && d.solarYear >= curYear) { + return { solarDate: d.solarDate, lunarName: `${d.lunarMonthName}${d.lunarDayName}`, ganzhi: d.lunarDayGanzhi }; + } + } + } + } + } + return null; + } catch { return null; } + }, [lunarSearch]); + + // Today's moon info + const moonInfo = useMemo(() => { + const di = getTodayInfo(); + return { phase: di.moonPhase, solarTerm: di.solarTerm, phenology: di.phenology }; + }, []); + + const monthNames = ['正月','二月','三月','四月','五月','六月','七月','八月','九月','十月','十一月','十二月']; + const dayNames = ['初一','初二','初三','初四','初五','初六','初七','初八','初九','初十','十一','十二','十三','十四','十五','十六','十七','十八','十九','二十','廿一','廿二','廿三','廿四','廿五','廿六','廿七','廿八','廿九','三十']; + + return ( +
+

设置

+ + {/* Profile */} +
+

个人八字

+ {profile ? ( +
+
+
+

{profile.gender==='male'?'♂':'♀'} {profile.birthYear}/{profile.birthMonth}/{profile.birthDay}

+ {baziResult?.eightChar &&

日主 {baziResult.eightChar.dayMaster}

} +
+
+ + +
+
+
+ + + +
+
+ ) : ( +
+

尚未设置

+ +
+ )} +
+ + {/* Lunar date finder */} +
+

查找农历日期

+
+ + +
+ {lunarResult ? ( + + ) : ( +

未找到对应日期

+ )} +
+ + {/* Moon & Phenology */} +
+

今日天象

+
+ 🌙 {moonInfo.phase || '-'} + {moonInfo.solarTerm && 🌿 {moonInfo.solarTerm}} +
+ {moonInfo.phenology &&

{moonInfo.phenology}

} +
+ + {/* Display */} +
+

显示

+
+ + + +
+
+ + +
+

八字流派

+
+ + +
+

23:00 后出生的日柱归属流派,仅影响八字排盘

+

真太阳时校正

+
+ + +
+

关闭后中国出生按北京时间直接排盘(不做经度校正),海外时区换算不受影响

+
+ + {/* About */} +
+

万年历 v0.1.0 · 基于 tyme4ts · 64卦梅花易数 · 称骨算命

+ +
+
+ ); +} diff --git a/web/packages/web/src/pages/SolarTermsPage.tsx b/web/packages/web/src/pages/SolarTermsPage.tsx new file mode 100644 index 0000000..7e0fbed --- /dev/null +++ b/web/packages/web/src/pages/SolarTermsPage.tsx @@ -0,0 +1,71 @@ +import { useMemo, useState } from 'react'; +import { motion } from 'framer-motion'; +import { ChevronLeft, ChevronRight } from 'lucide-react'; +import { getDayInfo } from '@lunar/core'; + +export default function SolarTermsPage() { + const [year, setYear] = useState(new Date().getFullYear()); + + const terms = useMemo(() => { + const result: { name: string; date: string; time: string; isJie: boolean; desc: string }[] = []; + const seen = new Set(); + // Scan through the year to find term days + for (let month = 1; month <= 12; month++) { + for (let day = 1; day <= 31; day++) { + try { + const di = getDayInfo(year, month, day); + if (di.isTermDay && di.solarTerm && !seen.has(di.solarTerm)) { + seen.add(di.solarTerm); + const isJie = ['立春','惊蛰','清明','立夏','芒种','小暑','立秋','白露','寒露','立冬','大雪','小寒'].includes(di.solarTerm); + result.push({ + name: di.solarTerm, + date: di.solarDate, + time: di.solarTermTime || '', + isJie, + desc: di.phenology || '', + }); + } + } catch { /* date invalid */ } + } + } + return result; + }, [year]); + + return ( +
+ {/* Year picker */} +
+ +

{year}年 二十四节气

+ +
+ + {/* Terms list */} + + {terms.map((term, idx) => ( + +
+ + {term.isJie ? '节' : '气'} + + {term.name} +
+
+

{term.date}

+

{term.time}

+ {term.desc &&

{term.desc}

} +
+
+ ))} +
+
+ ); +} diff --git a/web/packages/web/src/stores/bookmarks.ts b/web/packages/web/src/stores/bookmarks.ts new file mode 100644 index 0000000..cbef48c --- /dev/null +++ b/web/packages/web/src/stores/bookmarks.ts @@ -0,0 +1,33 @@ +import { create } from 'zustand'; +import { persist } from 'zustand/middleware'; + +export interface Bookmark { + id: string; + solarDate: string; // YYYY-MM-DD + label: string; // 名称 + lunarMonth?: number; + lunarDay?: number; + isLunar: boolean; // true = lunar recurring, false = solar fixed + color: string; // #C41E3A, #4CAF50, etc. +} + +interface BookmarksState { + items: Bookmark[]; + add: (b: Omit) => void; + remove: (id: string) => void; + getByDate: (solarDate: string) => Bookmark[]; + getByLunarDate: (month: number, day: number) => Bookmark[]; +} + +export const useBookmarkStore = create()( + persist( + (set, get) => ({ + items: [], + add: (b) => set(s => ({ items: [...s.items, { ...b, id: Date.now().toString(36)}] })), + remove: (id) => set(s => ({ items: s.items.filter(i => i.id !== id) })), + getByDate: (solarDate) => get().items.filter(i => !i.isLunar && i.solarDate === solarDate), + getByLunarDate: (month, day) => get().items.filter(i => i.isLunar && i.lunarMonth === month && i.lunarDay === day), + }), + { name: 'lunar-bookmarks' }, + ), +); diff --git a/web/packages/web/src/stores/calendar.ts b/web/packages/web/src/stores/calendar.ts new file mode 100644 index 0000000..29bb93e --- /dev/null +++ b/web/packages/web/src/stores/calendar.ts @@ -0,0 +1,58 @@ +import { create } from 'zustand'; + +interface CalendarState { + viewDate: Date; + selectedDate: Date | null; + weekStart: 0 | 1; + + setViewDate: (d: Date) => void; + goToToday: () => void; + goToNextMonth: () => void; + goToPrevMonth: () => void; + goToNextYear: () => void; + goToPrevYear: () => void; + selectDate: (d: Date) => void; + clearSelection: () => void; + setWeekStart: (ws: 0 | 1) => void; +} + +function getToday(): Date { + const now = new Date(); + return new Date(now.getFullYear(), now.getMonth(), now.getDate()); +} + +export const useCalendarStore = create((set, get) => ({ + viewDate: getToday(), + selectedDate: getToday(), + weekStart: 0, + + setViewDate: (d) => set({ viewDate: d }), + goToToday: () => set({ viewDate: getToday(), selectedDate: getToday() }), + goToNextMonth: () => { + const { viewDate } = get(); + const d = new Date(viewDate); + d.setMonth(d.getMonth() + 1); + set({ viewDate: d }); + }, + goToPrevMonth: () => { + const { viewDate } = get(); + const d = new Date(viewDate); + d.setMonth(d.getMonth() - 1); + set({ viewDate: d }); + }, + goToNextYear: () => { + const { viewDate } = get(); + const d = new Date(viewDate); + d.setFullYear(d.getFullYear() + 1); + set({ viewDate: d }); + }, + goToPrevYear: () => { + const { viewDate } = get(); + const d = new Date(viewDate); + d.setFullYear(d.getFullYear() - 1); + set({ viewDate: d }); + }, + selectDate: (d) => set({ selectedDate: d }), + clearSelection: () => set({ selectedDate: null }), + setWeekStart: (ws) => set({ weekStart: ws }), +})); diff --git a/web/packages/web/src/stores/settings.ts b/web/packages/web/src/stores/settings.ts new file mode 100644 index 0000000..1240fa8 --- /dev/null +++ b/web/packages/web/src/stores/settings.ts @@ -0,0 +1,92 @@ +import { create } from 'zustand'; +import { persist } from 'zustand/middleware'; + +type Theme = 'light' | 'dark' | 'system'; + +type ZiHourSect = 'earlyZiSameDay' | 'lateZiNextDay'; + +interface SettingsState { + theme: Theme; + weekStartDay: 0 | 1; + showLunar: boolean; + showSolarTerm: boolean; + showHoliday: boolean; + ziHourSect: ZiHourSect; + solarTime: boolean; + + setTheme: (t: Theme) => void; + setWeekStartDay: (ws: 0 | 1) => void; + toggleShowLunar: () => void; + toggleShowSolarTerm: () => void; + toggleShowHoliday: () => void; + setZiHourSect: (s: ZiHourSect) => void; + setSolarTime: (v: boolean) => void; + resetDefaults: () => void; +} + +function applyTheme(theme: Theme) { + const root = document.documentElement; + if (theme === 'dark') { + root.classList.add('dark'); + } else if (theme === 'light') { + root.classList.remove('dark'); + } else { + // system + if (window.matchMedia('(prefers-color-scheme: dark)').matches) { + root.classList.add('dark'); + } else { + root.classList.remove('dark'); + } + } +} + +export const useSettingsStore = create()( + persist( + (set, get) => ({ + theme: 'system', + weekStartDay: 0, + showLunar: true, + showSolarTerm: true, + showHoliday: true, + ziHourSect: 'lateZiNextDay', + solarTime: true, + + setTheme: (theme) => { + applyTheme(theme); + set({ theme }); + }, + setWeekStartDay: (ws) => set({ weekStartDay: ws }), + toggleShowLunar: () => set({ showLunar: !get().showLunar }), + toggleShowSolarTerm: () => set({ showSolarTerm: !get().showSolarTerm }), + toggleShowHoliday: () => set({ showHoliday: !get().showHoliday }), + setZiHourSect: (ziHourSect) => set({ ziHourSect }), + setSolarTime: (solarTime) => set({ solarTime }), + resetDefaults: () => { + applyTheme('system'); + set({ + theme: 'system', + weekStartDay: 0, + showLunar: true, + showSolarTerm: true, + showHoliday: true, + ziHourSect: 'lateZiNextDay', + solarTime: true, + }); + }, + }), + { + name: 'lunar-settings', + onRehydrateStorage: () => (state) => { + if (state) applyTheme(state.theme); + }, + }, + ), +); + +// Listen for system theme changes +if (typeof window !== 'undefined') { + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => { + const { theme } = useSettingsStore.getState(); + if (theme === 'system') applyTheme('system'); + }); +} diff --git a/web/packages/web/src/stores/ui.ts b/web/packages/web/src/stores/ui.ts new file mode 100644 index 0000000..d52156a --- /dev/null +++ b/web/packages/web/src/stores/ui.ts @@ -0,0 +1,37 @@ +import { create } from 'zustand'; + +interface UIState { + isSideNavOpen: boolean; + isMobile: boolean; + activeTab: string; + showDayDetail: boolean; + + toggleSideNav: () => void; + closeSideNav: () => void; + setMobile: (v: boolean) => void; + setActiveTab: (t: string) => void; + openDayDetail: () => void; + closeDayDetail: () => void; +} + +export const useUIStore = create((set) => ({ + isSideNavOpen: false, + isMobile: typeof window !== 'undefined' ? window.innerWidth < 768 : false, + activeTab: 'calendar', + showDayDetail: false, + + toggleSideNav: () => set((s) => ({ isSideNavOpen: !s.isSideNavOpen })), + closeSideNav: () => set({ isSideNavOpen: false }), + setMobile: (v) => set({ isMobile: v }), + setActiveTab: (t) => set({ activeTab: t }), + openDayDetail: () => set({ showDayDetail: true }), + closeDayDetail: () => set({ showDayDetail: false }), +})); + +// Listen for resize to update mobile state +if (typeof window !== 'undefined') { + window.addEventListener('resize', () => { + const isMobile = window.innerWidth < 768; + useUIStore.getState().setMobile(isMobile); + }); +} diff --git a/web/packages/web/src/stores/user.ts b/web/packages/web/src/stores/user.ts new file mode 100644 index 0000000..df294fe --- /dev/null +++ b/web/packages/web/src/stores/user.ts @@ -0,0 +1,120 @@ +import { create } from 'zustand'; +import { persist } from 'zustand/middleware'; +import type { EightCharInfo, BaziFullResult } from '@lunar/core'; + +export interface UserProfile { + name: string; + birthYear: number; + birthMonth: number; + birthDay: number; + birthHour: number; + birthMinute: number; + gender: 'male' | 'female'; + longitude?: number; + /** UTC 时区偏移(小时),海外出生时填写;缺省按北京时间 UTC+8 排盘 */ + tzOffset?: number; +} + +export interface SavedProfile { + profile: UserProfile; + baziResult: BaziFullResult | null; +} + +interface UserState { + profiles: SavedProfile[]; + activeIndex: number; + isLoading: boolean; + error: string | null; + + // Current active profile convenience + activeProfile: UserProfile | null; + activeBazi: BaziFullResult | null; + eightChar: EightCharInfo | null; + + // Actions + addProfile: (profile: UserProfile) => void; + updateProfile: (index: number, profile: UserProfile) => void; + removeProfile: (index: number) => void; + setActiveIndex: (index: number) => void; + setBaziResult: (index: number, result: BaziFullResult) => void; + setLoading: (loading: boolean) => void; + setError: (error: string | null) => void; + clearAll: () => void; +} + +function deriveActive(state: Pick) { + const p = state.profiles[state.activeIndex]; + return { + activeProfile: p?.profile || null, + activeBazi: p?.baziResult || null, + eightChar: p?.baziResult?.eightChar || null, + }; +} + +export const useUserStore = create()( + persist( + (set, get) => ({ + profiles: [], + activeIndex: 0, + isLoading: false, + error: null, + activeProfile: null, + activeBazi: null, + eightChar: null, + + addProfile: (profile) => { + const { profiles } = get(); + if (profiles.length >= 3) return; // max 3 + const newProfiles = [...profiles, { profile, baziResult: null }]; + const activeIndex = newProfiles.length - 1; + set({ profiles: newProfiles, activeIndex, ...deriveActive({ profiles: newProfiles, activeIndex }) }); + }, + + updateProfile: (index, profile) => { + const { profiles } = get(); + const newProfiles = [...profiles]; + newProfiles[index] = { ...newProfiles[index], profile }; + const activeIndex = get().activeIndex; + set({ profiles: newProfiles, ...deriveActive({ profiles: newProfiles, activeIndex }) }); + }, + + removeProfile: (index) => { + const { profiles, activeIndex } = get(); + const newProfiles = profiles.filter((_, i) => i !== index); + const newActive = activeIndex >= newProfiles.length ? Math.max(0, newProfiles.length - 1) : activeIndex; + set({ profiles: newProfiles, activeIndex: newActive, ...deriveActive({ profiles: newProfiles, activeIndex: newActive }) }); + }, + + setActiveIndex: (index) => { + const { profiles } = get(); + set({ activeIndex: index, ...deriveActive({ profiles, activeIndex: index }) }); + }, + + setBaziResult: (index, result) => { + const { profiles, activeIndex } = get(); + const newProfiles = [...profiles]; + newProfiles[index] = { ...newProfiles[index], baziResult: result }; + set({ profiles: newProfiles, isLoading: false, ...deriveActive({ profiles: newProfiles, activeIndex }) }); + }, + + setLoading: (loading) => set({ isLoading: loading }), + setError: (error) => set({ error, isLoading: false }), + clearAll: () => set({ profiles: [], activeIndex: 0, isLoading: false, error: null, activeProfile: null, activeBazi: null, eightChar: null }), + }), + { + name: 'lunar-user-profiles', + partialize: (state) => ({ + profiles: state.profiles, + activeIndex: state.activeIndex, + }), + onRehydrateStorage: () => (state) => { + if (state) { + const derived = deriveActive({ profiles: state.profiles, activeIndex: state.activeIndex }); + state.activeProfile = derived.activeProfile; + state.activeBazi = derived.activeBazi; + state.eightChar = derived.eightChar; + } + }, + }, + ), +); diff --git a/web/packages/web/src/styles/globals.css b/web/packages/web/src/styles/globals.css new file mode 100644 index 0000000..87f33b9 --- /dev/null +++ b/web/packages/web/src/styles/globals.css @@ -0,0 +1,144 @@ +@import "tailwindcss"; + +/* ===== Design Tokens ===== */ +:root { + /* Primary palette: cinnabar red / gold */ + --color-primary: #C41E3A; + --color-primary-light: #E85D75; + --color-primary-dark: #8B1A2B; + --color-accent: #D4A853; + --color-accent-light: #F0D68A; + + /* Five Element colors */ + --color-wood: #4CAF50; + --color-fire: #E53935; + --color-earth: #8D6E3F; + --color-metal: #B0BEC5; + --color-water: #1E88E5; + + /* Semantic */ + --color-lucky: #C41E3A; + --color-unlucky: #546E7A; + + /* Surfaces */ + --color-bg: #FFFBF5; + --color-bg-card: #FFFFFF; + --color-bg-elevated: #FFF8F0; + --color-border: #E8D5C4; + --color-text: #1A1A1A; + --color-text-secondary: #6B5E53; + --color-text-muted: #9E8E7E; + + /* Fonts */ + --font-sans: 'PingFang SC', 'Noto Sans SC', 'Hiragino Sans GB', system-ui, -apple-system, sans-serif; + --font-serif: 'Noto Serif SC', 'STSong', 'SimSun', 'Songti SC', serif; + --font-mono: 'SF Mono', 'JetBrains Mono', 'Fira Code', monospace; + + /* Radii */ + --radius-sm: 0.375rem; + --radius-md: 0.5rem; + --radius-lg: 0.75rem; + --radius-xl: 1rem; +} + +.dark { + --color-primary: #E85D75; + --color-primary-light: #F48F9F; + --color-primary-dark: #B71C32; + --color-accent: #F0D68A; + --color-accent-light: #F5E6B8; + + --color-bg: #1A1410; + --color-bg-card: #2A2218; + --color-bg-elevated: #3A2E20; + --color-border: #4A3A2A; + --color-text: #F5F0E8; + --color-text-secondary: #A09888; + --color-text-muted: #6B6058; + + --color-lucky: #E85D75; + --color-unlucky: #78909C; +} + +/* ===== Base Styles ===== */ +@theme inline { + --color-background: var(--color-bg); + --color-foreground: var(--color-text); + --color-primary: var(--color-primary); + --color-primary-light: var(--color-primary-light); + --color-accent: var(--color-accent); + --color-card: var(--color-bg-card); + --color-border: var(--color-border); + --color-muted: var(--color-text-muted); + --color-secondary: var(--color-text-secondary); + --font-sans: var(--font-sans); + --font-serif: var(--font-serif); + --font-mono: var(--font-mono); +} + +body { + font-family: var(--font-sans); + background-color: var(--color-bg); + color: var(--color-text); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + overflow-x: hidden; +} + +/* Chinese pattern background for cards */ +.chinese-pattern { + background-image: + radial-gradient(circle at 20% 20%, rgba(212, 168, 83, 0.04) 1px, transparent 1px), + radial-gradient(circle at 80% 80%, rgba(212, 168, 83, 0.04) 1px, transparent 1px); + background-size: 24px 24px, 24px 24px; +} + +/* Serif font for Chinese titles */ +.font-chinese { + font-family: var(--font-serif); +} + +/* Calendar grid cell transition */ +.calendar-cell { + transition: background-color 0.15s ease, transform 0.1s ease; +} +.calendar-cell:active { + transform: scale(0.95); +} + +/* Scrollbar styling */ +::-webkit-scrollbar { + width: 4px; +} +::-webkit-scrollbar-track { + background: transparent; +} +::-webkit-scrollbar-thumb { + background: var(--color-border); + border-radius: 2px; +} +::-webkit-scrollbar-thumb:hover { + background: var(--color-text-muted); +} + +/* ===== Utility Classes ===== */ +@utility container-page { + max-width: 480px; + margin-left: auto; + margin-right: auto; + padding-left: 1rem; + padding-right: 1rem; +} + +@media (min-width: 768px) { + .container-page { + max-width: 768px; + } +} +@media (min-width: 1024px) { + .container-page { + max-width: 1024px; + padding-left: 2rem; + padding-right: 2rem; + } +} diff --git a/web/packages/web/tsconfig.json b/web/packages/web/tsconfig.json new file mode 100644 index 0000000..4cad9cf --- /dev/null +++ b/web/packages/web/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "jsx": "react-jsx", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "outDir": "./dist", + "rootDir": "./src", + "noEmit": true + }, + "include": ["src"] +} diff --git a/web/packages/web/vite.config.ts b/web/packages/web/vite.config.ts new file mode 100644 index 0000000..55ae803 --- /dev/null +++ b/web/packages/web/vite.config.ts @@ -0,0 +1,62 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import tailwindcss from '@tailwindcss/vite'; +import { VitePWA } from 'vite-plugin-pwa'; +import { resolve } from 'path'; + +export default defineConfig({ + plugins: [ + tailwindcss(), + react(), + VitePWA({ + registerType: 'autoUpdate', + includeAssets: ['favicon.svg'], + manifest: { + name: '万年历 - 农历黄历八字运势', + short_name: '万年历', + description: '农历黄历、八字排盘、每日运势', + lang: 'zh-CN', + theme_color: '#FFFBF5', + background_color: '#FFFBF5', + display: 'standalone', + orientation: 'portrait', + start_url: '/', + icons: [ + { + src: 'favicon.svg', + sizes: '64x64', + type: 'image/svg+xml', + }, + ], + }, + workbox: { + globPatterns: ['**/*.{js,css,html,svg,png,woff2}'], + runtimeCaching: [ + { + urlPattern: /^https:\/\/fonts\.googleapis\.com\/.*/i, + handler: 'CacheFirst', + options: { + cacheName: 'google-fonts-cache', + expiration: { maxEntries: 10, maxAgeSeconds: 60 * 60 * 24 * 365 }, + }, + }, + ], + }, + }), + ], + resolve: { + alias: { + '@': resolve(__dirname, 'src'), + }, + conditions: ['development', 'browser'], + }, + server: { + port: 4258, + strictPort: true, + open: true, + }, + build: { + target: 'es2022', + sourcemap: true, + }, +}); diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml new file mode 100644 index 0000000..02c297e --- /dev/null +++ b/web/pnpm-lock.yaml @@ -0,0 +1,5928 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@eslint/js': + specifier: ^10.0.1 + version: 10.0.1(eslint@10.8.0(jiti@2.7.0)) + eslint: + specifier: ^10.8.0 + version: 10.8.0(jiti@2.7.0) + typescript-eslint: + specifier: ^8.65.0 + version: 8.65.0(eslint@10.8.0(jiti@2.7.0))(typescript@5.9.3) + + packages/core: + dependencies: + tyme4ts: + specifier: ^1.5.1 + version: 1.5.1 + devDependencies: + tsup: + specifier: ^8.4.0 + version: 8.5.1(jiti@2.7.0)(postcss@8.5.15)(typescript@5.9.3) + typescript: + specifier: ^5.7.0 + version: 5.9.3 + vitest: + specifier: ^4.1.10 + version: 4.1.10(vite@6.4.3(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)) + + packages/web: + dependencies: + '@lunar/core': + specifier: workspace:* + version: link:../core + clsx: + specifier: ^2.1.0 + version: 2.1.1 + framer-motion: + specifier: ^12.0.0 + version: 12.40.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + lucide-react: + specifier: ^0.468.0 + version: 0.468.0(react@19.2.7) + react: + specifier: ^19.0.0 + version: 19.2.7 + react-dom: + specifier: ^19.0.0 + version: 19.2.7(react@19.2.7) + react-router-dom: + specifier: ^7.0.0 + version: 7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + tailwind-variants: + specifier: ^0.3.0 + version: 0.3.1(tailwindcss@4.3.0) + zustand: + specifier: ^5.0.0 + version: 5.0.14(@types/react@19.2.16)(react@19.2.7) + devDependencies: + '@tailwindcss/vite': + specifier: ^4.0.0 + version: 4.3.0(vite@6.4.3(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)) + '@types/react': + specifier: ^19.0.0 + version: 19.2.16 + '@types/react-dom': + specifier: ^19.0.0 + version: 19.2.3(@types/react@19.2.16) + '@vitejs/plugin-react': + specifier: ^4.4.0 + version: 4.7.0(vite@6.4.3(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)) + tailwindcss: + specifier: ^4.0.0 + version: 4.3.0 + typescript: + specifier: ^5.7.0 + version: 5.9.3 + vite: + specifier: ^6.0.0 + version: 6.4.3(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0) + vite-plugin-pwa: + specifier: ^0.21.0 + version: 0.21.2(vite@6.4.3(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0))(workbox-build@7.4.1(@types/babel__core@7.20.5))(workbox-window@7.4.1) + +packages: + + '@apideck/better-ajv-errors@0.3.7': + resolution: {integrity: sha512-TajUJwGWbDwkCx/CZi7tRE8PVB7simCvKJfHUsSdvps+aTM/PDPP4gkLmKnc+x3CE//y9i/nj74GqdL/hwk7Iw==} + engines: {node: '>=10'} + peerDependencies: + ajv: '>=8' + + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.7': + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.7': + resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-annotate-as-pure@7.29.7': + resolution: {integrity: sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-create-class-features-plugin@7.29.7': + resolution: {integrity: sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-create-regexp-features-plugin@7.29.7': + resolution: {integrity: sha512-907Uymvqgg1dwUA+7IGwFAOSYzQOuzPXKNJ1yxzwPffzkYFg2q2eHi1fIOs6sXkG9NbIUMunnUlkYsfRFNvomg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-define-polyfill-provider@0.6.8': + resolution: {integrity: sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-member-expression-to-functions@7.29.7': + resolution: {integrity: sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-optimise-call-expression@7.29.7': + resolution: {integrity: sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==} + engines: {node: '>=6.9.0'} + + '@babel/helper-plugin-utils@7.29.7': + resolution: {integrity: sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-remap-async-to-generator@7.29.7': + resolution: {integrity: sha512-16AMiW26DbXWBbr3B8wNozKM0ydMLB892vaOaJW/fPJdnT8vJk5sdkQcU/isqUxyCE0cEoa8wZOcbgDuC4b6Og==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-replace-supers@7.29.7': + resolution: {integrity: sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-skip-transparent-expression-wrappers@7.29.7': + resolution: {integrity: sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-wrap-function@7.29.7': + resolution: {integrity: sha512-iES0Skag9ERIF68aXadpO6dbXa03mNWK3sEqJaMnLNs/eC3l0lkImdfoy6Y09/SfkpawdAB4RjQ7PVA7TcVGdw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.7': + resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.7': + resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.29.7': + resolution: {integrity: sha512-j8SrR0zLZrRsC09DlszEx8FpMiwukKffYXMK0d5LmOglO7vGG6sz/BR/20yHqWH+Lnn31JTt2PE3hIWNgM2J6w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.29.7': + resolution: {integrity: sha512-r8j8escF+U2FUHo0KOhPUdMzUO+jp9fInva6+ACVAF3Y97Ev+5iNZwiqTghmzNeWwDkOPlYuTcfb1vDaoZKmAQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.29.7': + resolution: {integrity: sha512-GE1TFSiuFeGsCxmYXZl8HwoPrVlwe4rHPFE8weieGKZqnDORK+Ar3vgWMgW+AOxQ6/2TgLSKx9p6W7O4rC6qgQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array@7.29.7': + resolution: {integrity: sha512-oBNVCvnO5tND+xSopWvV8WNGfpTfgP4Zr/YXXSj8zfmcPktp5Ku/aZlsIowgSD4fjmgHn6sGmB9APVsU5zOdhA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.29.7': + resolution: {integrity: sha512-QQt9qKHZ2sg/kivaLr7lnQr8HVrQDdBNSfCsTjiDxRuX/K5ORyKq+Bu8Xr0cDE3Dfkv0cw28Ve0EKyKMvulkOw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.13.0 + + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.29.7': + resolution: {integrity: sha512-pn6QacGLgvCcwc+syUhKE/qSjV2D1IHDB84RNxWYSt1mW3K/SCtjinZ2p0cETJxAWBjPy3K/1lHwG5BjjPxNlw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2': + resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-assertions@7.29.7': + resolution: {integrity: sha512-/An1OCBN93thpBAGyfsK2pcf0jvju1SAtKkL2Ny++B5Sy6sqgzXDQH1cZxWbF96Wuk+bn41MDA9bLd4VVAw6rw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-attributes@7.29.7': + resolution: {integrity: sha512-zGYcYfq/WmZ4V+kBIXQon9dSSc8ircGZqw9ZaNhhGj9nZkeBu1jHLBDQqYYi5WA9uawvA2sIMbry2nCFhf5Djg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-unicode-sets-regex@7.18.6': + resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-arrow-functions@7.29.7': + resolution: {integrity: sha512-N7zArUXWzAMzm+/N0uPBeVB3Fam5lMxtUwMmDK5f/IBBS7a7p1qeUoxd/6CckXoxUdgsntq1Dh8xNW06maZbDQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-async-generator-functions@7.29.7': + resolution: {integrity: sha512-d98gXZkgswvkyohMBABkhm3GeXhYj8psWfwQ2C7gtfrKGTykQa/iOIi+JJhwMjPlZ6Vm2XN+DCf3Es1EoG4ZLA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-async-to-generator@7.29.7': + resolution: {integrity: sha512-pcUb2SS+RMo9TWVBwKGI5ShtoG7R+zBsFmCKDa6fe8c+hPr3XJlZgoE5j6i8W7gDjhyvy+85vmYexanvXh3d1w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-block-scoped-functions@7.29.7': + resolution: {integrity: sha512-cUSmjh72N+rN4PrkFlN1dJwNCwjVp5d38/CQrEsFggkD10UiFlBFgdH3tv5dNsLuHY+3S8db2xCHjhZcv5WgvA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-block-scoping@7.29.7': + resolution: {integrity: sha512-ONyr4+AZhKh8yKWInVxU9AXA9EbsyeLcL6V0dJy6M2/62vuvpGm29zzuymbTpdc451GEpDIdAyPLP3r+P61yKQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-class-properties@7.29.7': + resolution: {integrity: sha512-GtcpjFvanPfzNQi3eTitsCqtRRmmqzpy/A+yhTR1HaZo1Ly3EA8ZXxlPyHdR8/IuRMYc3E4wdGBewB2QKQjAaA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-class-static-block@7.29.7': + resolution: {integrity: sha512-kibJgmEdX2iMwsHY2tSZNDgj8PwIlCQz7FK9KuGKO8zsuoUwSEhoNnNVp/emKWrbY4HeO6kkXfdMqRKKKXBm2A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.12.0 + + '@babel/plugin-transform-classes@7.29.7': + resolution: {integrity: sha512-qV0OGGBVacduzQHE649JyCneOFI/maT+YKsO+K4Yi3xv2wTPNjM/W2o2gdzMwEAZz7fXNTHAe0NcSg30bIN69g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-computed-properties@7.29.7': + resolution: {integrity: sha512-RK7/IyU5phpuCdBAuig5VkzG/EnbDaui5SQGdU9BFrHdV+mV4cUjLMQ9lJDjLNtWHsqtiefpGZUXQP2BiTYMsA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-destructuring@7.29.7': + resolution: {integrity: sha512-iPX8aD6H9zV5s7ZsqTdNocPN/MGQ5sSMnElKrktxjJRMnB2jN/1p2+R7GkfD6CAYoVFqy5A4XnSIUeGgJzIWpg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-dotall-regex@7.29.7': + resolution: {integrity: sha512-3qc18hsD2RdZiyJNDNc7HQpv6xbncwh8FYtxNFFzclSyh/trPD9KkVR9BDECUjDLvb7yJVF15GfYUuC+LMkkiQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-duplicate-keys@7.29.7': + resolution: {integrity: sha512-6IvRRriEMqnBwD6chtxdLpMYCHWEzN+oL5cyQtjykya19UgzbmKhxmhZgKC/LHxS2nYr9Q/qYPZ5Lr6jOL9+yQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.7': + resolution: {integrity: sha512-2wiIyo2BjtgU7HufSeDnL9L2O7zr8jmhFKuSr65VpRkUiRKRNpb0mdlk56+XPPKoIrfHqzbMuglDvZun0RISsA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-dynamic-import@7.29.7': + resolution: {integrity: sha512-giOlEm/EFjfjr+te9NsdjkUo2v4f8rS/SXPumRVHAtbNcyNlvtREkU1dZzaIDclNpnaVhlCqRdFKhJBjBikzLg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-explicit-resource-management@7.29.7': + resolution: {integrity: sha512-Rstj7coNz8sE+7Ju7ihpHLI564lsK5pUpNNlvptCIC/16E/S5hbl6n3kESPKdNRmqEWlpn5xpS5Q2dvXBsySLw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-exponentiation-operator@7.29.7': + resolution: {integrity: sha512-zFpMOTLZBdW5LfObqcSbL6kefg4R4eLdmvS0wbN9M6D5Mym/sKm9toOoWyVOa+xDjvCnuWcHls2YonXwHvH3CQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-export-namespace-from@7.29.7': + resolution: {integrity: sha512-24B2nOy2TeJSMheqwPD4DDQOV/elLSIlKxjZt4i05H5AgdPdWR3n18HnNrcJ+j76WJd9gbwb9jPjNYUy6RautA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-for-of@7.29.7': + resolution: {integrity: sha512-zeSIHh0+E1Um1WJRXCFlHQYu2ieJNdivLLjlBEp+dIBu3S51n+SZZmIXjxnItw6pz56Cn+KvK68BIBVsxq2JiQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-function-name@7.29.7': + resolution: {integrity: sha512-otRWaHXE6fbAGkePvaj/kvs3HsqXfPhlnzwSOlnFgbqCPMd975dW+4wZ00WFBt+/YlBGcJwNrARQTOJOb4ZrIg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-json-strings@7.29.7': + resolution: {integrity: sha512-RRnE2+eon1rJAq8MnoF1b5kTpY1vU88twHcvcKMrsqP/jxIRqDVs9iJB5fqPuqyeFAW0wJo4MlUIPpQCq/aRsg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-literals@7.29.7': + resolution: {integrity: sha512-DZ/oLP21ZuWx1vKqnoNv6/tvEK48AQOBRai40CX9dTjGluvT/YZCyY3rryDtyUqCEoyNroy5KKPwX2iQCiRvyw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-logical-assignment-operators@7.29.7': + resolution: {integrity: sha512-A0H91hh6W8MFRkp5TqJmMr39jzGD1A1E1Ysiv2O06Sfbhkapm+XyIzxWCEh5kqwOZ1/8QZ0dY3SeQ7XBqfJd5Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-member-expression-literals@7.29.7': + resolution: {integrity: sha512-hl1kwFZCCiDyfH25Xmco9jTrkPgnS9pmOzSG7W5I4SaGbLeqKv417hcU2RKmaxoPEgsoJh7ZPOrnPGq99bHoUg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-amd@7.29.7': + resolution: {integrity: sha512-fxtQoH3m5ywUSIfaH0FGCzWu4McsYon5bD3K4XnskC7f+OyQMj7rsOMi4NvvmJ83WwBAg4UCe+ov4VZlqEvyew==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-commonjs@7.29.7': + resolution: {integrity: sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-systemjs@7.29.7': + resolution: {integrity: sha512-TM2ZcQLoG2/y4HODiStCo10DibYhWhGWAwVv+EQKmG/7GFl0N+AAmUiXOMKM+aiJ9XBJ9AHVZBvTzMnJ2sM3cQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-umd@7.29.7': + resolution: {integrity: sha512-B4UkaTK3QpgCwJnrxKfMPKdo92CN7OKXAlpAAnM3UPu0Q0lCCk57ylA9AJbRy2v8dDKOPAAWcoR6CMyeoHwRCA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-named-capturing-groups-regex@7.29.7': + resolution: {integrity: sha512-vuFoLwr4qnv2xbZ16SQd6uPcH5FNrLHhk/Jzo++0XJFcaDsr4gjJVg6j398oMHiC+83k/GiBzviwF5KBJkPUtQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-new-target@7.29.7': + resolution: {integrity: sha512-fEo41GmsOUhOBlw8ioo6zvjX5Xc2Lqkzlyfqbpsk3eB6TReV18uhxZ0esfEokVbY2+PVJAQHNKxER6lGrzNd3A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-nullish-coalescing-operator@7.29.7': + resolution: {integrity: sha512-idmp1dFaekP9GbcMvG24Kvw2BfhFZjHnNJCkV4WuIY4PskJzwI3f1N5OdgYke38T7rftO6ERulFRn2cFeZwRkg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-numeric-separator@7.29.7': + resolution: {integrity: sha512-zR7fv/z14OjgHl4AgRtkDBvBMhIzCxqV/qN/2BCRC7LjFwvuzjYe7gDWxC4Wl/SNsLM6SE1IWvRPYMgSJaUvNw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-object-rest-spread@7.29.7': + resolution: {integrity: sha512-Ld98jn4c0smUywL57m7SgsHq3OpThOa6LqZJif3G6jYOovPleoFhVrBJ1WegRApSFB2wu4+RelAj9AC9G08Z4A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-object-super@7.29.7': + resolution: {integrity: sha512-Ea/diGcw0twB5IlZPO5sgET6fJsLJqPABqTuFWIR+iMPGPZJkATEIWx0wa+aEQ5UY1CBQyP/gkAiLEqn1vBiQA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-optional-catch-binding@7.29.7': + resolution: {integrity: sha512-sLsyndxK2VwX6yNUOakMb7Sh553ZTe/vVM1XJ+9Z5aW1ytsc8xOIwmyk05NNjN60vkc5/KqoTH6hB4V41LJhng==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-optional-chaining@7.29.7': + resolution: {integrity: sha512-6GM1dhvK3gNODkXcEcMCOLEDCLSoZ/sBbro2Ax8HURyasQ4NshagQixkRFdh5niI6E4gmA/jYI/4aT7rRos3ZQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-parameters@7.29.7': + resolution: {integrity: sha512-ZDOBqV/qLYJI0YElr8DcENEyARsFQeESqWXH6gZlghYXuPPjvweuDhP4VyEi4BlUBlLRFZVjxoZDMjxhLW766g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-private-methods@7.29.7': + resolution: {integrity: sha512-/6Rz4DK1ETDEM/bWHsPHcaEe7ZaT1EqSXjtSP/L0DijOYuaUhiRiOKcwpZ8P7zR4xXEHc2ITdiCgBm9Tpyv9ug==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-private-property-in-object@7.29.7': + resolution: {integrity: sha512-+BNo06dnrzdNNqCm1X6YUaVv0DKk8Q+JYcoZfOkLhYWNCXzlwTSRq8zGWayT1csjcpNXV9CQTBRRbmTLZac5cA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-property-literals@7.29.7': + resolution: {integrity: sha512-bOMRLQuI0A5ZqHq3OWJ89/rXpJ/NJrbVhXiP4zwPGMs6kpcVsuTUNjwoE30K0Qm3mf48a/TnRYYD6vPNqcg6jA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-self@7.29.7': + resolution: {integrity: sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.29.7': + resolution: {integrity: sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-regenerator@7.29.7': + resolution: {integrity: sha512-rNNFV0DBAJp988xW2DOntfDoYn1eR8GGF5AT5vYc+rjyfaQkM242c9tZUHHPe7KYaiJizXPWhQTzzdbXySyhBw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-regexp-modifiers@7.29.7': + resolution: {integrity: sha512-mB5Fs0VWrJ42ZCmc8114v60qetdaUVNkj9PmSZRmanCZM3S9hm0CFRLjRmYIsuXav14l2jvZ+4T8iiCGnhj3nQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-reserved-words@7.29.7': + resolution: {integrity: sha512-5+YhdpVgmfSmwZyLMftfaiffLRMHjzIRHFHHLdibcSyJm2pasMrKHrO3Ptrt2DRshjvpgjEJJ1zVW14WPq/6QA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-shorthand-properties@7.29.7': + resolution: {integrity: sha512-I+WYbGBAiCn7nA6xBrlgPH+MB7HWb4u8pv5S0Pv7OtwNvIFvCCb24YlttKEeUFVurfBCEaOTnuhlqsb7f0Z5Dg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-spread@7.29.7': + resolution: {integrity: sha512-/u5K1QWada7tbYNqTjMh96718g9NTwh9tfPJMsSmVsQwGT447FskV+KcfeXkXq2GWki4EM/MuTdmBec+hOuVTQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-sticky-regex@7.29.7': + resolution: {integrity: sha512-BCHzNYJGe9l7EpwwDBN/ztlL2NYFFq8hp9ddjtUEM9f2O7S7kKV/lL6Fwo7IF7NSkYhPK2vO+86nIGltA90MsA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-template-literals@7.29.7': + resolution: {integrity: sha512-NCSEJ4sLFU2gqAub45HYh4fus2yQ36rr6ei6vpU7NdoJqCpxvEG8E6eJpscGyXP3VHD2Ny+fSXr04k1hoUrFqA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typeof-symbol@7.29.7': + resolution: {integrity: sha512-223mNGoTkBiTEWFoK+Q6Go3tueMRclO8vxxxxquNCYuNI4jWOofFKJRRDu6SDrB8Sgo1UEGW9T4GAQ8ZyRso1A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-escapes@7.29.7': + resolution: {integrity: sha512-jCfXxSjf94lf4E0hKE0AByxF6F3/pVFqRdUUNkDJhsY0m1ZKjnN6ZYyMeHNpzflxb/0q5b7t3p+BE+SLF1WOtA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-property-regex@7.29.7': + resolution: {integrity: sha512-OgZ+zoAJgZLUCunsTRQ5LAjOywDv5zzZ2/hQ5aMw1pGXyY2rtE8/chXYUmu3AlVHKpm10KEdG9aMwbI/K76ZGw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-regex@7.29.7': + resolution: {integrity: sha512-7D/x/23/d/3VqZ0QA+LGbZMlGwZjztBygSWWWsfTPoQ1oQ6Q1P6Mr3d0kk42XabyUVw+fha3LqdRsFqeKqvCyA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-sets-regex@7.29.7': + resolution: {integrity: sha512-BLOhLht9DOJwIxlmp91wHvkXv1lguuHS3/FwUO8HL1H0u8s4hR1gASVFyilu9iGtcTRYqjTZmlsFFeQletntEg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/preset-env@7.29.7': + resolution: {integrity: sha512-GYzX36n1nsciIb0uyH0GHwxwtNwPQIcpxSeiVLDtG/B7jB5xXgchnmL1f/jCX5o+pwnaDBtO60ONSJhEBJfxYA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/preset-modules@0.1.6-no-external-plugins': + resolution: {integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==} + peerDependencies: + '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 + + '@babel/runtime@7.29.7': + resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.7': + resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.7': + resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + engines: {node: '>=6.9.0'} + + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.27.7': + resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.27.7': + resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.27.7': + resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.27.7': + resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.27.7': + resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.7': + resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.27.7': + resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.7': + resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.27.7': + resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.27.7': + resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.27.7': + resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.27.7': + resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.27.7': + resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.27.7': + resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.7': + resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.27.7': + resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.27.7': + resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-arm64@0.27.7': + resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.7': + resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-arm64@0.27.7': + resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.7': + resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/openharmony-arm64@0.27.7': + resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.27.7': + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.27.7': + resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.27.7': + resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.27.7': + resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.10.1': + resolution: {integrity: sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.23.5': + resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/config-helpers@0.7.0': + resolution: {integrity: sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/core@1.2.1': + resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/js@10.0.1': + resolution: {integrity: sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + peerDependencies: + eslint: ^10.0.0 + peerDependenciesMeta: + eslint: + optional: true + + '@eslint/object-schema@3.0.5': + resolution: {integrity: sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/plugin-kit@0.7.2': + resolution: {integrity: sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} + engines: {node: '>=18.18.0'} + + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@isaacs/cliui@9.0.0': + resolution: {integrity: sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==} + engines: {node: '>=18'} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/source-map@0.3.11': + resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@rolldown/pluginutils@1.0.0-beta.27': + resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} + + '@rollup/plugin-babel@6.1.0': + resolution: {integrity: sha512-dFZNuFD2YRcoomP4oYf+DvQNSUA9ih+A3vUqopQx5EdtPGo3WBnQcI/S8pwpz91UsGfL0HsMSOlaMld8HrbubA==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@types/babel__core': ^7.1.9 + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + '@types/babel__core': + optional: true + rollup: + optional: true + + '@rollup/plugin-node-resolve@16.0.3': + resolution: {integrity: sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^2.78.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/plugin-replace@6.0.3': + resolution: {integrity: sha512-J4RZarRvQAm5IF0/LwUUg+obsm+xZhYnbMXmXROyoSE1ATJe3oXSb9L5MMppdxP2ylNSjv6zFBwKYjcKMucVfA==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/plugin-terser@1.0.0': + resolution: {integrity: sha512-FnCxhTBx6bMOYQrar6C8h3scPt8/JwIzw3+AJ2K++6guogH5fYaIFia+zZuhqv0eo1RN7W1Pz630SyvLbDjhtQ==} + engines: {node: '>=20.0.0'} + peerDependencies: + rollup: ^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/pluginutils@5.4.0': + resolution: {integrity: sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/rollup-android-arm-eabi@4.61.1': + resolution: {integrity: sha512-JnBB8MdXj45cajvTuO5FmPlvFVJRQgvrz1uSEl3NwqFnReAPGwb8EanbGi4z2nRaqLzjJSv5/JmycoTKlRZxHA==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.61.1': + resolution: {integrity: sha512-Jx2g7iSjw4AOT0HDPHM9RV3GNjRXwybWtSFZiZAYUTjUwjVrYIwq3kBf+LnhqJlzXFAqTAh2F7IGI+O568exPw==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.61.1': + resolution: {integrity: sha512-0F1L/Z3Eqv8mT2n3dCpeO8GcTvHvVqkP5/t6DMsn0KzhYVcg+s7Ncl5DS8qjKYEeio6Az0Gt6nyBORay5qIlCA==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.61.1': + resolution: {integrity: sha512-qLttcH871ujY4YcVfUSShhOw+CsoTatYz8gRbHO7Bb92QH059/P0y5do1KMs41fY0BpD2x4AJH/gID0zFiqVKQ==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.61.1': + resolution: {integrity: sha512-fUI4RapGE0Oh3mb8mgfvC1O2nU1RpDZUKnDQm3xB1Ipg7C2wTs5Kstz7G2uWK99a8S2yTMq8/P4uycwNa0nJyw==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.61.1': + resolution: {integrity: sha512-H5YrdvJaDtI/U9/emrD4b++xkvp3y/JvOe4rizHbxvkyMfRS/CiRYdji+Pl8D0brEaNFWUh1drQxgAGIl6Xudw==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.61.1': + resolution: {integrity: sha512-Q8CBCCQtDFrYtXoeUXSrnFXKOnyUhx6bz+SkL6A0E7V8kAiCJ5pamq1WtbfpVGhR5TSpXY6ak3avmDc5fHTyJA==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.61.1': + resolution: {integrity: sha512-nwnhk1581l0FBVellGcVCAT0Oi06onEA3WB53sf01VO3I0UPBkMH9sXONYME2K0ovXcNayJfNtHfm6mpJElatQ==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.61.1': + resolution: {integrity: sha512-x5Xr49hwt3hdW75UOZm3395YwwzPyauktslv29KpWL/T+vVAzoT3azLcTWv0eMciBNrx+DYjH4paehHoLpPvpg==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.61.1': + resolution: {integrity: sha512-unMS3H73DpaoPyyEVPjGKleM/s0mkmsauTENpw4INQY8y4+IuLNjkueQ5QCtC0D3N38Y38yhAU8OoZ20S2Tm6w==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.61.1': + resolution: {integrity: sha512-zNZzGRnAhwjFEYmvphJRV5XaQGjs62cCmeYYHUT//NbvEnHauw+I85nGG+SiVg5ld4GX8D1IbKIX+ozITQnhMQ==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.61.1': + resolution: {integrity: sha512-LdpWGL8X209B2SIvWjqlc8VZgM6PKfontSerGepuldQmHYrAOtnMCXeJkxXGbC+PPZVOuu5czJo7fNV6aeW8rQ==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.61.1': + resolution: {integrity: sha512-EC5kTtNaNGOmbMGqar8dvJy6y/hg99GAwjfBz++pxZhQATXGcRjd6c5en5wcbru0vkRmiMGsQKdMJOOf6sza4g==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.61.1': + resolution: {integrity: sha512-8hiwp6D4acEcNK78I4rP0/XtS1sknWIAMJBPdR4l6zUtyTm5KiTDr5bXmWt4foY7nAN7AThDHgkLIEZOWKbzWw==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.61.1': + resolution: {integrity: sha512-10dh/h/BqA7DuMPWSxkR8uks18FRwnwOEqr5zOTEl+NOwP/OMzKX8OFR/Of9xxDA7D5qef1Nzar5WDD2kCCr1g==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.61.1': + resolution: {integrity: sha512-YKJ5lg35DP17gcAOggnihe+APw9HLyj1Xn7gsmGumBJAUDa6NGXNixJzmkWLhcK9TOuuyQjdamzvJefkO7qHZQ==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.61.1': + resolution: {integrity: sha512-Mlil5G2Jj6a7B3LWGctg+XPL9vdXYuzCtNXfxOQ0nPjc2m6ueUktocPGH9bnAM0bNRKb/bAWTujUU7IJQdQA+g==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.61.1': + resolution: {integrity: sha512-bVWIOIk6pV01p4CdUbPP7CJ/434z+OooYjDuFcR+44N35YvKUC66G8MGnvcWx5mWKW3g61J+t74l3Kj15Kwn2Q==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.61.1': + resolution: {integrity: sha512-qy5pBvZbqNFheBz61R1rzsezjm0J7O2oNGoWtGoY89SZYLUfxAJTBAqDChqAIdB4rCiIbi9nF7yZ83GnNiLwSw==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.61.1': + resolution: {integrity: sha512-E83TXjI4zm0+5f2qO+UOudaCYIhYwpJ5jq6YCZNIZ+6CbfhKrkAGezeiASBL9ElxAxFsRS9ZhESv8mfnj6TKeg==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.61.1': + resolution: {integrity: sha512-fbWnKqVkjrJN38vNe3ahkbk6iejS/3b0Nt7EEtPpE6RBacZcGXNKbzfHN3GUUlXOPghUg0j6XUGrtjX9z1sIvA==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.61.1': + resolution: {integrity: sha512-ArMl38iVAbk0New1ogihQNY6iphLi4ZaRsa037gUzv5yeKPY8TD3Dmy4x2RNC1VztU/uqm+G+/RwFrSka3Oy2g==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.61.1': + resolution: {integrity: sha512-0mYtjHS9ucAbcATycCNK9IGBk/cCe/ma7EmSLGZdsxnOA8cjRIyU04wDpVAD9NiOfLUR9KTxdiO53uOkherqjQ==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.61.1': + resolution: {integrity: sha512-gK1iCEPfpoSG9wfBihXxvBMi8ZfcWffYkEsC/Eih+iFENTaewvNcrEQ69lIOWYO5pePHKLHHO7nq5AILGO/HQQ==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.61.1': + resolution: {integrity: sha512-X+zaP2x+j4RXGfbp/seSoRHWnPxzApilDszisZxbYH5C/jTxFhCtDNdPGZb9lJyYPs24wGxruPF7Y+sIXt9Gzw==} + cpu: [x64] + os: [win32] + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@tailwindcss/node@4.3.0': + resolution: {integrity: sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==} + + '@tailwindcss/oxide-android-arm64@4.3.0': + resolution: {integrity: sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.3.0': + resolution: {integrity: sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.3.0': + resolution: {integrity: sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==} + engines: {node: '>= 20'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.3.0': + resolution: {integrity: sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==} + engines: {node: '>= 20'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.0': + resolution: {integrity: sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==} + engines: {node: '>= 20'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.3.0': + resolution: {integrity: sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-arm64-musl@4.3.0': + resolution: {integrity: sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-linux-x64-gnu@4.3.0': + resolution: {integrity: sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-x64-musl@4.3.0': + resolution: {integrity: sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-wasm32-wasi@4.3.0': + resolution: {integrity: sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.3.0': + resolution: {integrity: sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.3.0': + resolution: {integrity: sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==} + engines: {node: '>= 20'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.3.0': + resolution: {integrity: sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==} + engines: {node: '>= 20'} + + '@tailwindcss/vite@4.3.0': + resolution: {integrity: sha512-t6J3OrB5Fc0ExuhohouH0fWUGMYL6PTLhW+E7zIk/pdbnJARZDCwjBznFnkh5ynRnIRSI4YjtTH0t6USjJISrw==} + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 || ^8 + + '@trickfilm400/rollup-plugin-off-main-thread@3.0.0-pre1': + resolution: {integrity: sha512-/67zpWDBLV+oYAEL682s1ktXL0HgqX76f6gaVGkGnVZlBbm1zd0v4Bz8MFF2GGhoX9rvfq3KSQHubFHwa6w6/Q==} + engines: {node: '>=12'} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/esrecurse@4.3.1': + resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.16': + resolution: {integrity: sha512-esJiCAnl0kfpNdE69f3So4WJUXy95dLZydX0KwK46riIHDzHM7O9Vtf9xCHW0PXIqvgqNrswl522kA/5yx+F4w==} + + '@types/resolve@1.20.2': + resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} + + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + + '@typescript-eslint/eslint-plugin@8.65.0': + resolution: {integrity: sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.65.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/parser@8.65.0': + resolution: {integrity: sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/project-service@8.65.0': + resolution: {integrity: sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/scope-manager@8.65.0': + resolution: {integrity: sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.65.0': + resolution: {integrity: sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/type-utils@8.65.0': + resolution: {integrity: sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/types@8.65.0': + resolution: {integrity: sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.65.0': + resolution: {integrity: sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/utils@8.65.0': + resolution: {integrity: sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/visitor-keys@8.65.0': + resolution: {integrity: sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@vitejs/plugin-react@4.7.0': + resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + + '@vitest/expect@4.1.10': + resolution: {integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==} + + '@vitest/mocker@4.1.10': + resolution: {integrity: sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.1.10': + resolution: {integrity: sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==} + + '@vitest/runner@4.1.10': + resolution: {integrity: sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==} + + '@vitest/snapshot@4.1.10': + resolution: {integrity: sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==} + + '@vitest/spy@4.1.10': + resolution: {integrity: sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==} + + '@vitest/utils@4.1.10': + resolution: {integrity: sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} + + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} + + arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + async-function@1.0.0: + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} + + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + + at-least-node@1.0.0: + resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} + engines: {node: '>= 4.0.0'} + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + babel-plugin-polyfill-corejs2@0.4.17: + resolution: {integrity: sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-corejs3@0.14.2: + resolution: {integrity: sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-regenerator@0.6.8: + resolution: {integrity: sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + baseline-browser-mapping@2.10.33: + resolution: {integrity: sha512-bA6+tcSLpz2tIEdDXZPpPTIuxBcC4+w6SieaYyfigIa4h8GlFxbA17v22Vx3JUtuZQj9SgOsnbK+aTBzyDyEuw==} + engines: {node: '>=6.0.0'} + hasBin: true + + brace-expansion@2.1.1: + resolution: {integrity: sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==} + + brace-expansion@5.0.6: + resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} + engines: {node: 18 || 20 || >=22} + + browserslist@4.28.2: + resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + bundle-require@5.1.0: + resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + peerDependencies: + esbuild: '>=0.18' + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.9: + resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + caniuse-lite@1.0.30001793: + resolution: {integrity: sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==} + + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + common-tags@1.8.2: + resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==} + engines: {node: '>=4.0.0'} + + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} + + core-js-compat@3.49.0: + resolution: {integrity: sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + crypto-random-string@2.0.0: + resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==} + engines: {node: '>=8'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + data-view-buffer@1.0.2: + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.2: + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.1: + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: '>= 0.4'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + ejs@3.1.10: + resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==} + engines: {node: '>=0.10.0'} + hasBin: true + + electron-to-chromium@1.5.368: + resolution: {integrity: sha512-7RckJJK4uESJF9PxvfMWd3TGqIiieUTG4HxnKaKuIpGbcr+r2ZEB3g2gAhCP3Fqm42vJSzLfgab9eva/C4/XVw==} + + enhanced-resolve@5.23.0: + resolution: {integrity: sha512-yJN/BOOLxcOW2aQgeif9mSnaUB8KtvmMMp56oA1kx1CRfBKbhZm2pJ+NBY+3eOboHxix8lfjWpHE0Ei5U8RbSA==} + engines: {node: '>=10.13.0'} + + es-abstract@1.24.2: + resolution: {integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-module-lexer@2.3.1: + resolution: {integrity: sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==} + + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + es-to-primitive@1.3.0: + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} + engines: {node: '>= 0.4'} + + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + + esbuild@0.27.7: + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-scope@9.1.2: + resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@10.8.0: + resolution: {integrity: sha512-nuKKvN+oIBO0koN7Tm7dlkmnkc21mtt0QJLwAKzjLq14y6lRTdVG36MZHJ8eQHwdJMwZbQNMlPOYedMq/oVJvQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@11.2.0: + resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + eta@4.6.0: + resolution: {integrity: sha512-lW6is4T1NFOYnmqGZIfvixqj7A7sSvScF+DN8EK6K58xI5MZ5UvYe0GjopxOXQtZvUn4eDdVuZ8XSoYWTMEKwA==} + engines: {node: '>=20'} + + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} + engines: {node: '>=12.0.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fast-uri@3.1.2: + resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + filelist@1.0.6: + resolution: {integrity: sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + fix-dts-default-cjs-exports@1.0.1: + resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.4.4: + resolution: {integrity: sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==} + + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + framer-motion@12.40.0: + resolution: {integrity: sha512-uaBd3qC1v3KQqBEjwTUd183K6PbS+j0yR9w9VmEOLWA/tnUcSn8Xa3uck7t4dgpDoUss8xQTcj8W2L07lrnLFg==} + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true + react: + optional: true + react-dom: + optional: true + + fs-extra@9.1.0: + resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} + engines: {node: '>=10'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + function.prototype.name@1.1.8: + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + generator-function@2.0.1: + resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} + engines: {node: '>= 0.4'} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-own-enumerable-property-symbols@3.0.2: + resolution: {integrity: sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-symbol-description@1.1.0: + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} + engines: {node: '>= 0.4'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@11.1.0: + resolution: {integrity: sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==} + engines: {node: 20 || >=22} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + hasBin: true + + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + + idb@7.1.1: + resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.6: + resolution: {integrity: sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==} + engines: {node: '>= 4'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} + + is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: '>= 0.4'} + + is-async-function@2.1.1: + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: '>= 0.4'} + + is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} + + is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} + engines: {node: '>= 0.4'} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-core-module@2.16.2: + resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} + engines: {node: '>= 0.4'} + + is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: '>= 0.4'} + + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-finalizationregistry@1.1.1: + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} + + is-generator-function@1.1.2: + resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} + engines: {node: '>= 0.4'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-module@1.0.0: + resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} + + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + + is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} + + is-obj@1.0.1: + resolution: {integrity: sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==} + engines: {node: '>=0.10.0'} + + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-regexp@1.0.0: + resolution: {integrity: sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==} + engines: {node: '>=0.10.0'} + + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} + + is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.1.1: + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} + + is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jackspeak@4.2.3: + resolution: {integrity: sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==} + engines: {node: 20 || >=22} + + jake@10.9.4: + resolution: {integrity: sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==} + engines: {node: '>=10'} + hasBin: true + + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} + hasBin: true + + joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonfile@6.2.1: + resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==} + + jsonpointer@5.0.1: + resolution: {integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==} + engines: {node: '>=0.10.0'} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + leven@3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + load-tsconfig@0.2.5: + resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.debounce@4.0.8: + resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + + lodash.sortby@4.7.0: + resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} + + lru-cache@11.5.1: + resolution: {integrity: sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==} + engines: {node: 20 || >=22} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lucide-react@0.468.0: + resolution: {integrity: sha512-6koYRhnM2N0GGZIdXzSeiNwguv1gt/FAjZOiPl76roBi3xKEXa4WmfpxgQwTTL4KipXjefrnf3oV4IsYhi4JFA==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + + minimatch@5.1.9: + resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} + engines: {node: '>=10'} + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + + mlly@1.8.2: + resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} + + motion-dom@12.40.0: + resolution: {integrity: sha512-HxU3ZaBwNPVQUBQf1xxgq+7JrPNZvjLVxgbpEZL7RrWJnsxOf0/OM+yrHG9ogLQ31Do/r57Oz2gQWPK+6q62mg==} + + motion-utils@12.39.0: + resolution: {integrity: sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + nanoid@3.3.12: + resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + node-releases@2.0.47: + resolution: {integrity: sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==} + engines: {node: '>=18'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} + + obug@2.1.4: + resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==} + engines: {node: '>=12.20.0'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + own-keys@1.0.1: + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + engines: {node: '>= 0.4'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@2.0.2: + resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} + engines: {node: 18 || 20 || >=22} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + + postcss@8.5.15: + resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + pretty-bytes@5.6.0: + resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==} + engines: {node: '>=6'} + + pretty-bytes@6.1.1: + resolution: {integrity: sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==} + engines: {node: ^14.13.1 || >=16.0.0} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + react-dom@19.2.7: + resolution: {integrity: sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==} + peerDependencies: + react: ^19.2.7 + + react-refresh@0.17.0: + resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} + engines: {node: '>=0.10.0'} + + react-router-dom@7.17.0: + resolution: {integrity: sha512-fyU2yjGups/hE6Xz0I5ZYbVL8Gx29eCjgpHaRaTaVU+OOAdfRX05KsvyRm0GO8YQwOkhpU3MurW1jyMUJn+zSw==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + + react-router@7.17.0: + resolution: {integrity: sha512-FDELK7rTMlCHO5+reyXsPlmfr7N1F91lPHsWYfMEGQm/KQ+F4JFM8jGoeQDmDvdTs93Fw9aSilH+uKRb4/jXvQ==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + peerDependenciesMeta: + react-dom: + optional: true + + react@19.2.7: + resolution: {integrity: sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==} + engines: {node: '>=0.10.0'} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + reflect.getprototypeof@1.0.10: + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} + + regenerate-unicode-properties@10.2.2: + resolution: {integrity: sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==} + engines: {node: '>=4'} + + regenerate@1.4.2: + resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} + + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} + + regexpu-core@6.4.0: + resolution: {integrity: sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==} + engines: {node: '>=4'} + + regjsgen@0.8.0: + resolution: {integrity: sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==} + + regjsparser@0.13.1: + resolution: {integrity: sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw==} + hasBin: true + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + resolve@1.22.12: + resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} + engines: {node: '>= 0.4'} + hasBin: true + + rollup@4.61.1: + resolution: {integrity: sha512-I4KW6iuRpuu2uHBLraZ1wNZe0DP7lnRha+VJ9tNaYVaVgKhW0aI3h4RYnoRPeql0flHm/Co55b7snEDcOfOJrA==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + safe-array-concat@1.1.4: + resolution: {integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==} + engines: {node: '>=0.4'} + + safe-push-apply@1.0.0: + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + engines: {node: '>=10'} + hasBin: true + + serialize-javascript@7.0.5: + resolution: {integrity: sha512-F4LcB0UqUl1zErq+1nYEEzSHJnIwb3AF2XWB94b+afhrekOUijwooAYqFyRbjYkm2PAKBabx6oYv/xDxNi8IBw==} + engines: {node: '>=20.0.0'} + + set-cookie-parser@2.7.2: + resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + set-proto@1.0.0: + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + smob@1.6.2: + resolution: {integrity: sha512-RQsvleCbF8cVHEv+xuDGaA4pOizFqJ0GgjtMSRo6oP8pnN7WsigHgVGey6aILRBKv4W2YOMHLqbKdnB6hpB9fw==} + engines: {node: '>=20.0.0'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + + source-map@0.8.0-beta.0: + resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} + engines: {node: '>= 8'} + deprecated: The work that was done in this beta branch won't be included in future versions + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@4.2.0: + resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==} + + stop-iteration-iterator@1.1.0: + resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} + engines: {node: '>= 0.4'} + + string.prototype.matchall@4.0.12: + resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} + engines: {node: '>= 0.4'} + + string.prototype.trim@1.2.10: + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.9: + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + engines: {node: '>= 0.4'} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + + stringify-object@3.3.0: + resolution: {integrity: sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==} + engines: {node: '>=4'} + + strip-comments@2.0.1: + resolution: {integrity: sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==} + engines: {node: '>=10'} + + sucrase@3.35.1: + resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + tailwind-merge@2.5.4: + resolution: {integrity: sha512-0q8cfZHMu9nuYP/b5Shb7Y7Sh1B7Nnl5GqNr1U+n2p6+mybvRtayrQ+0042Z5byvTA8ihjlP8Odo8/VnHbZu4Q==} + + tailwind-variants@0.3.1: + resolution: {integrity: sha512-krn67M3FpPwElg4FsZrOQd0U26o7UDH/QOkK8RNaiCCrr052f6YJPBUfNKnPo/s/xRzNPtv1Mldlxsg8Tb46BQ==} + engines: {node: '>=16.x', pnpm: '>=7.x'} + peerDependencies: + tailwindcss: '*' + + tailwindcss@4.3.0: + resolution: {integrity: sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==} + + tapable@2.3.3: + resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} + engines: {node: '>=6'} + + temp-dir@2.0.0: + resolution: {integrity: sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==} + engines: {node: '>=8'} + + tempy@0.6.0: + resolution: {integrity: sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==} + engines: {node: '>=10'} + + terser@5.48.0: + resolution: {integrity: sha512-J/9An6vs9Us6wKRriSFXBWdRZapREHqFzdNUKk0pmu804EMR6dr6winwo7e5JDxN4xahxQsuysyYFwlwj4XN/Q==} + engines: {node: '>=10'} + hasBin: true + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyexec@1.3.0: + resolution: {integrity: sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==} + engines: {node: '>=18'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tinyrainbow@3.1.1: + resolution: {integrity: sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==} + engines: {node: '>=14.0.0'} + + tr46@1.0.1: + resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} + + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tsup@8.5.1: + resolution: {integrity: sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + '@microsoft/api-extractor': ^7.36.0 + '@swc/core': ^1 + postcss: ^8.4.12 + typescript: '>=4.5.0' + peerDependenciesMeta: + '@microsoft/api-extractor': + optional: true + '@swc/core': + optional: true + postcss: + optional: true + typescript: + optional: true + + tyme4ts@1.5.1: + resolution: {integrity: sha512-GzL9rP4k5vFdj/8iLnlGrC3jPop4Og2oAE9aBTj2JbOKuZ22+iMlpD+4Ekke4FylHn5BO9V6XRJOgJtFVDFDqQ==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-fest@0.16.0: + resolution: {integrity: sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==} + engines: {node: '>=10'} + + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.4: + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.8: + resolution: {integrity: sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==} + engines: {node: '>= 0.4'} + + typescript-eslint@8.65.0: + resolution: {integrity: sha512-/ggrHAwyjENDusvyxbuqxAC2dTnZg/Z8F+fgQtYIz+L6n/9HfSlEZcFGV/NsMNa6CkGk0xUjUAFwC0vHOflvIA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + ufo@1.6.4: + resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} + + unbox-primitive@1.1.0: + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} + + unicode-canonical-property-names-ecmascript@2.0.1: + resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==} + engines: {node: '>=4'} + + unicode-match-property-ecmascript@2.0.0: + resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==} + engines: {node: '>=4'} + + unicode-match-property-value-ecmascript@2.2.1: + resolution: {integrity: sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==} + engines: {node: '>=4'} + + unicode-property-aliases-ecmascript@2.2.0: + resolution: {integrity: sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==} + engines: {node: '>=4'} + + unique-string@2.0.0: + resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==} + engines: {node: '>=8'} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + upath@1.2.0: + resolution: {integrity: sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==} + engines: {node: '>=4'} + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + vite-plugin-pwa@0.21.2: + resolution: {integrity: sha512-vFhH6Waw8itNu37hWUJxL50q+CBbNcMVzsKaYHQVrfxTt3ihk3PeLO22SbiP1UNWzcEPaTQv+YVxe4G0KOjAkg==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@vite-pwa/assets-generator': ^0.2.6 + vite: ^3.1.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 + workbox-build: ^7.3.0 + workbox-window: ^7.3.0 + peerDependenciesMeta: + '@vite-pwa/assets-generator': + optional: true + + vite@6.4.3: + resolution: {integrity: sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@4.1.10: + resolution: {integrity: sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.10 + '@vitest/browser-preview': 4.1.10 + '@vitest/browser-webdriverio': 4.1.10 + '@vitest/coverage-istanbul': 4.1.10 + '@vitest/coverage-v8': 4.1.10 + '@vitest/ui': 4.1.10 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + webidl-conversions@4.0.2: + resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} + + whatwg-url@7.1.0: + resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} + + which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} + + which-builtin-type@1.2.1: + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-typed-array@1.1.21: + resolution: {integrity: sha512-zbRA8cVm6io/d5W8uIe2hblzN76/Wm3v/yiythQvr+dpBWeqhPSWIDNj4zOyHi4zKbMK6DN34Xsr9jPHJERAEw==} + engines: {node: '>= 0.4'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + workbox-background-sync@7.4.1: + resolution: {integrity: sha512-HhT7KE8tOWDm02wRNshXUnUPofMlhenF2DBdUnDPOubhizzPeItkYTmAB6td1Z2cjYPa98vzEiPLEuzn5hN66g==} + + workbox-broadcast-update@7.4.1: + resolution: {integrity: sha512-uAlgslKLvbQY+suirIdnBCSYrcgBhjp81Nj4l1lj/Jmj0MJO2CJERnCJjT0GFVwmReV0N+zs78K6gqd5gr9/+A==} + + workbox-build@7.4.1: + resolution: {integrity: sha512-SDhxIvEAde9Gy/5w4Yo1Jh/M49Z0qE3q0oteyE8zGq0DScxFqVBcCtIXFuLtmtxRQZCMbf0prco4VyEu3KBQuw==} + engines: {node: '>=20.0.0'} + + workbox-cacheable-response@7.4.1: + resolution: {integrity: sha512-8xaFoJdDc2OjrlbbL3gEeBO1WKcMwRqwLRupgqahYXu75yXajPLuwrbXMrIGZuWYXrQwk0xDjOxZ/ujCy/oJYw==} + + workbox-core@7.4.1: + resolution: {integrity: sha512-DT+vu46eh/2vRsSHTY4Xmc32Z1rr9PRlQUXr1Dx30ZuXRWwOsvZgGgcwxcasubQLQmbTNYZjv44LkBAQ4tT5tQ==} + + workbox-expiration@7.4.1: + resolution: {integrity: sha512-lRKUF7b+OGbeXkQk1s6MHXOa3d7Xxf7Of31W6c6hCfipfIyrtdWZ89stq21AHZMaoG7VNFoHply4Ox+rU31TWg==} + + workbox-google-analytics@7.4.1: + resolution: {integrity: sha512-Mks1JwLEt++ZAkF6sS1OpSh9RtAMIsiDgRpK+codiHGIPXeaUOgi4cPc3GFadUl8V5QPeypEk8Oxgl3HlwVzHw==} + + workbox-navigation-preload@7.4.1: + resolution: {integrity: sha512-C4KVsjPcYKJOhr631AxR9XoG2rLF3QiTk5aMv36MXOjtWvm8axwNFAtKUPGsWUwLXXAMgYM1En7fsvndaXeXRQ==} + + workbox-precaching@7.4.1: + resolution: {integrity: sha512-cdr/9qByww7yzEp7zg/qI4ukUrrNjQLgN+ONQRpjy/VqGQXwkgHwr00KksGJK8v0VifwDXBb8a4cWNZH71jn3Q==} + + workbox-range-requests@7.4.1: + resolution: {integrity: sha512-7i2oxAUE82gHdAJBCAQ04JzNOdRPqzuOzGfoUyJpFSmeqBNYGPrAH8GPoPjUQTfp+NycwrD2H68VtuF8qxv0vQ==} + + workbox-recipes@7.4.1: + resolution: {integrity: sha512-gnbVfmV4/TtmQaM4x9AtuXhcdstJsep3XMVeztOrQVPT+R6+6DeBjGTCQ7fFCXm+4GEHUA5VEBTyi5+4gWGeog==} + + workbox-routing@7.4.1: + resolution: {integrity: sha512-yubJGErZOusuidAenaL5ypfhQOa7urxP/f8E0ws7FPb4039RiWXUWBAyUkmUoOL/BcQGen3h0J8872d51IYxtA==} + + workbox-strategies@7.4.1: + resolution: {integrity: sha512-GZxpaw9NbmOelj7667uZ2kpk5BFpOGbO4X0qjwh5ls8XQ8C+Lha5LQchTiUzsTFSS+NlUpftYAyOVXvQUrcqOQ==} + + workbox-streams@7.4.1: + resolution: {integrity: sha512-HWWtraKUbJknd9kgqGcpQ3G114HOPYvqs8HaJMDs2ebLNAimDkVDaWfAXE6Ybl+m8U6KsCE6pWyLYuigWmnAXw==} + + workbox-sw@7.4.1: + resolution: {integrity: sha512-fez5f2DUlDJWTFYkCWQpY10N8gtztd849NswCbVFk0QlcSM4HT5A8x4g4ii650yem4I8tHY0R7JZahwp3ltIPw==} + + workbox-window@7.4.1: + resolution: {integrity: sha512-notZDH2u8VXaqyuD7xaqIfEFi6SRM4SUSd7ewe9PDsVqADuepxX2ZMY3uvuZGxzY5ZOsGC/vD3A/3smFtJt4/A==} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zustand@5.0.14: + resolution: {integrity: sha512-/8tAspM5LMPr28b3fwLYrtdj77ECpfZviaP75CMTnwO8ISyaE4GDIG/9rDDYq/cH9D2Xw2A2RXglLInmVBQB/g==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=18.0.0' + immer: '>=9.0.6' + react: '>=18.0.0' + use-sync-external-store: '>=1.2.0' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + use-sync-external-store: + optional: true + +snapshots: + + '@apideck/better-ajv-errors@0.3.7(ajv@8.20.0)': + dependencies: + ajv: 8.20.0 + jsonpointer: 5.0.1 + leven: 3.1.0 + + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.7': {} + + '@babel/core@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helpers': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.7': + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-annotate-as-pure@7.29.7': + dependencies: + '@babel/types': 7.29.7 + + '@babel/helper-compilation-targets@7.29.7': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.28.2 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-create-class-features-plugin@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-member-expression-to-functions': 7.29.7 + '@babel/helper-optimise-call-expression': 7.29.7 + '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7) + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + '@babel/traverse': 7.29.7 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-create-regexp-features-plugin@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + regexpu-core: 6.4.0 + semver: 6.3.1 + + '@babel/helper-define-polyfill-provider@0.6.8(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + debug: 4.4.3 + lodash.debounce: 4.0.8 + resolve: 1.22.12 + transitivePeerDependencies: + - supports-color + + '@babel/helper-globals@7.29.7': {} + + '@babel/helper-member-expression-to-functions@7.29.7': + dependencies: + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-imports@7.29.7': + dependencies: + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-optimise-call-expression@7.29.7': + dependencies: + '@babel/types': 7.29.7 + + '@babel/helper-plugin-utils@7.29.7': {} + + '@babel/helper-remap-async-to-generator@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-wrap-function': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-replace-supers@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-member-expression-to-functions': 7.29.7 + '@babel/helper-optimise-call-expression': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-skip-transparent-expression-wrappers@7.29.7': + dependencies: + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/helper-validator-option@7.29.7': {} + + '@babel/helper-wrap-function@7.29.7': + dependencies: + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helpers@7.29.7': + dependencies: + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 + + '@babel/parser@7.29.7': + dependencies: + '@babel/types': 7.29.7 + + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + '@babel/plugin-transform-optional-chaining': 7.29.7(@babel/core@7.29.7) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + + '@babel/plugin-syntax-import-assertions@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-import-attributes@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-arrow-functions@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-async-generator-functions@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-remap-async-to-generator': 7.29.7(@babel/core@7.29.7) + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-async-to-generator@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-remap-async-to-generator': 7.29.7(@babel/core@7.29.7) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-block-scoped-functions@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-block-scoping@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-class-properties@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-class-static-block@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-classes@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-globals': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7) + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-computed-properties@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/template': 7.29.7 + + '@babel/plugin-transform-destructuring@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-dotall-regex@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-duplicate-keys@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-dynamic-import@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-explicit-resource-management@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-destructuring': 7.29.7(@babel/core@7.29.7) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-exponentiation-operator@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-export-namespace-from@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-for-of@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-function-name@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-json-strings@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-literals@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-logical-assignment-operators@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-member-expression-literals@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-modules-amd@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-commonjs@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-systemjs@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-umd@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-named-capturing-groups-regex@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-new-target@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-nullish-coalescing-operator@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-numeric-separator@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-object-rest-spread@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-destructuring': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-parameters': 7.29.7(@babel/core@7.29.7) + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-object-super@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-optional-catch-binding@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-optional-chaining@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-parameters@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-private-methods@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-private-property-in-object@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-property-literals@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-react-jsx-self@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-react-jsx-source@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-regenerator@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-regexp-modifiers@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-reserved-words@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-shorthand-properties@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-spread@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-sticky-regex@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-template-literals@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-typeof-symbol@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-unicode-escapes@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-unicode-property-regex@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-unicode-regex@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-unicode-sets-regex@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/preset-env@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/core': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.7) + '@babel/plugin-syntax-import-assertions': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-syntax-import-attributes': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.29.7) + '@babel/plugin-transform-arrow-functions': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-async-generator-functions': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-async-to-generator': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-block-scoped-functions': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-block-scoping': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-class-properties': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-class-static-block': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-classes': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-computed-properties': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-destructuring': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-dotall-regex': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-duplicate-keys': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-dynamic-import': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-explicit-resource-management': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-exponentiation-operator': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-export-namespace-from': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-for-of': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-function-name': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-json-strings': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-literals': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-logical-assignment-operators': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-member-expression-literals': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-modules-amd': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-modules-systemjs': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-modules-umd': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-named-capturing-groups-regex': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-new-target': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-nullish-coalescing-operator': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-numeric-separator': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-object-rest-spread': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-object-super': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-optional-catch-binding': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-optional-chaining': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-parameters': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-private-methods': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-private-property-in-object': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-property-literals': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-regenerator': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-regexp-modifiers': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-reserved-words': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-shorthand-properties': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-spread': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-sticky-regex': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-template-literals': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-typeof-symbol': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-unicode-escapes': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-unicode-property-regex': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-unicode-regex': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-unicode-sets-regex': 7.29.7(@babel/core@7.29.7) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.29.7) + babel-plugin-polyfill-corejs2: 0.4.17(@babel/core@7.29.7) + babel-plugin-polyfill-corejs3: 0.14.2(@babel/core@7.29.7) + babel-plugin-polyfill-regenerator: 0.6.8(@babel/core@7.29.7) + core-js-compat: 3.49.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/types': 7.29.7 + esutils: 2.0.3 + + '@babel/runtime@7.29.7': {} + + '@babel/template@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + + '@babel/traverse@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.7': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/aix-ppc64@0.27.7': + optional: true + + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.27.7': + optional: true + + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-arm@0.27.7': + optional: true + + '@esbuild/android-x64@0.25.12': + optional: true + + '@esbuild/android-x64@0.27.7': + optional: true + + '@esbuild/darwin-arm64@0.25.12': + optional: true + + '@esbuild/darwin-arm64@0.27.7': + optional: true + + '@esbuild/darwin-x64@0.25.12': + optional: true + + '@esbuild/darwin-x64@0.27.7': + optional: true + + '@esbuild/freebsd-arm64@0.25.12': + optional: true + + '@esbuild/freebsd-arm64@0.27.7': + optional: true + + '@esbuild/freebsd-x64@0.25.12': + optional: true + + '@esbuild/freebsd-x64@0.27.7': + optional: true + + '@esbuild/linux-arm64@0.25.12': + optional: true + + '@esbuild/linux-arm64@0.27.7': + optional: true + + '@esbuild/linux-arm@0.25.12': + optional: true + + '@esbuild/linux-arm@0.27.7': + optional: true + + '@esbuild/linux-ia32@0.25.12': + optional: true + + '@esbuild/linux-ia32@0.27.7': + optional: true + + '@esbuild/linux-loong64@0.25.12': + optional: true + + '@esbuild/linux-loong64@0.27.7': + optional: true + + '@esbuild/linux-mips64el@0.25.12': + optional: true + + '@esbuild/linux-mips64el@0.27.7': + optional: true + + '@esbuild/linux-ppc64@0.25.12': + optional: true + + '@esbuild/linux-ppc64@0.27.7': + optional: true + + '@esbuild/linux-riscv64@0.25.12': + optional: true + + '@esbuild/linux-riscv64@0.27.7': + optional: true + + '@esbuild/linux-s390x@0.25.12': + optional: true + + '@esbuild/linux-s390x@0.27.7': + optional: true + + '@esbuild/linux-x64@0.25.12': + optional: true + + '@esbuild/linux-x64@0.27.7': + optional: true + + '@esbuild/netbsd-arm64@0.25.12': + optional: true + + '@esbuild/netbsd-arm64@0.27.7': + optional: true + + '@esbuild/netbsd-x64@0.25.12': + optional: true + + '@esbuild/netbsd-x64@0.27.7': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.27.7': + optional: true + + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-x64@0.27.7': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.27.7': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.27.7': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + + '@esbuild/win32-arm64@0.27.7': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-ia32@0.27.7': + optional: true + + '@esbuild/win32-x64@0.25.12': + optional: true + + '@esbuild/win32-x64@0.27.7': + optional: true + + '@eslint-community/eslint-utils@4.10.1(eslint@10.8.0(jiti@2.7.0))': + dependencies: + eslint: 10.8.0(jiti@2.7.0) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.23.5': + dependencies: + '@eslint/object-schema': 3.0.5 + debug: 4.4.3 + minimatch: 10.2.5 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.7.0': + dependencies: + '@eslint/core': 1.2.1 + + '@eslint/core@1.2.1': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/js@10.0.1(eslint@10.8.0(jiti@2.7.0))': + optionalDependencies: + eslint: 10.8.0(jiti@2.7.0) + + '@eslint/object-schema@3.0.5': {} + + '@eslint/plugin-kit@0.7.2': + dependencies: + '@eslint/core': 1.2.1 + levn: 0.4.1 + + '@humanfs/core@0.19.2': + dependencies: + '@humanfs/types': 0.15.0 + + '@humanfs/node@0.16.8': + dependencies: + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 + '@humanwhocodes/retry': 0.4.3 + + '@humanfs/types@0.15.0': {} + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@isaacs/cliui@9.0.0': {} + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/source-map@0.3.11': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@rolldown/pluginutils@1.0.0-beta.27': {} + + '@rollup/plugin-babel@6.1.0(@babel/core@7.29.7)(@types/babel__core@7.20.5)(rollup@4.61.1)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@rollup/pluginutils': 5.4.0(rollup@4.61.1) + optionalDependencies: + '@types/babel__core': 7.20.5 + rollup: 4.61.1 + transitivePeerDependencies: + - supports-color + + '@rollup/plugin-node-resolve@16.0.3(rollup@4.61.1)': + dependencies: + '@rollup/pluginutils': 5.4.0(rollup@4.61.1) + '@types/resolve': 1.20.2 + deepmerge: 4.3.1 + is-module: 1.0.0 + resolve: 1.22.12 + optionalDependencies: + rollup: 4.61.1 + + '@rollup/plugin-replace@6.0.3(rollup@4.61.1)': + dependencies: + '@rollup/pluginutils': 5.4.0(rollup@4.61.1) + magic-string: 0.30.21 + optionalDependencies: + rollup: 4.61.1 + + '@rollup/plugin-terser@1.0.0(rollup@4.61.1)': + dependencies: + serialize-javascript: 7.0.5 + smob: 1.6.2 + terser: 5.48.0 + optionalDependencies: + rollup: 4.61.1 + + '@rollup/pluginutils@5.4.0(rollup@4.61.1)': + dependencies: + '@types/estree': 1.0.9 + estree-walker: 2.0.2 + picomatch: 4.0.4 + optionalDependencies: + rollup: 4.61.1 + + '@rollup/rollup-android-arm-eabi@4.61.1': + optional: true + + '@rollup/rollup-android-arm64@4.61.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.61.1': + optional: true + + '@rollup/rollup-darwin-x64@4.61.1': + optional: true + + '@rollup/rollup-freebsd-arm64@4.61.1': + optional: true + + '@rollup/rollup-freebsd-x64@4.61.1': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.61.1': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.61.1': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.61.1': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.61.1': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.61.1': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.61.1': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.61.1': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.61.1': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.61.1': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.61.1': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.61.1': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.61.1': + optional: true + + '@rollup/rollup-linux-x64-musl@4.61.1': + optional: true + + '@rollup/rollup-openbsd-x64@4.61.1': + optional: true + + '@rollup/rollup-openharmony-arm64@4.61.1': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.61.1': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.61.1': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.61.1': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.61.1': + optional: true + + '@standard-schema/spec@1.1.0': {} + + '@tailwindcss/node@4.3.0': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.23.0 + jiti: 2.7.0 + lightningcss: 1.32.0 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.3.0 + + '@tailwindcss/oxide-android-arm64@4.3.0': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.3.0': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.3.0': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.3.0': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.0': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.3.0': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.3.0': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.3.0': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.3.0': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.3.0': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.3.0': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.3.0': + optional: true + + '@tailwindcss/oxide@4.3.0': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.3.0 + '@tailwindcss/oxide-darwin-arm64': 4.3.0 + '@tailwindcss/oxide-darwin-x64': 4.3.0 + '@tailwindcss/oxide-freebsd-x64': 4.3.0 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.3.0 + '@tailwindcss/oxide-linux-arm64-gnu': 4.3.0 + '@tailwindcss/oxide-linux-arm64-musl': 4.3.0 + '@tailwindcss/oxide-linux-x64-gnu': 4.3.0 + '@tailwindcss/oxide-linux-x64-musl': 4.3.0 + '@tailwindcss/oxide-wasm32-wasi': 4.3.0 + '@tailwindcss/oxide-win32-arm64-msvc': 4.3.0 + '@tailwindcss/oxide-win32-x64-msvc': 4.3.0 + + '@tailwindcss/vite@4.3.0(vite@6.4.3(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0))': + dependencies: + '@tailwindcss/node': 4.3.0 + '@tailwindcss/oxide': 4.3.0 + tailwindcss: 4.3.0 + vite: 6.4.3(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0) + + '@trickfilm400/rollup-plugin-off-main-thread@3.0.0-pre1': + dependencies: + ejs: 3.1.10 + json5: 2.2.3 + magic-string: 0.30.21 + string.prototype.matchall: 4.0.12 + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.29.7 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.29.7 + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + + '@types/esrecurse@4.3.1': {} + + '@types/estree@1.0.9': {} + + '@types/json-schema@7.0.15': {} + + '@types/react-dom@19.2.3(@types/react@19.2.16)': + dependencies: + '@types/react': 19.2.16 + + '@types/react@19.2.16': + dependencies: + csstype: 3.2.3 + + '@types/resolve@1.20.2': {} + + '@types/trusted-types@2.0.7': {} + + '@typescript-eslint/eslint-plugin@8.65.0(@typescript-eslint/parser@8.65.0(eslint@10.8.0(jiti@2.7.0))(typescript@5.9.3))(eslint@10.8.0(jiti@2.7.0))(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.65.0(eslint@10.8.0(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.65.0 + '@typescript-eslint/type-utils': 8.65.0(eslint@10.8.0(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/utils': 8.65.0(eslint@10.8.0(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.65.0 + eslint: 10.8.0(jiti@2.7.0) + ignore: 7.0.6 + natural-compare: 1.4.0 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.65.0(eslint@10.8.0(jiti@2.7.0))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.65.0 + '@typescript-eslint/types': 8.65.0 + '@typescript-eslint/typescript-estree': 8.65.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.65.0 + debug: 4.4.3 + eslint: 10.8.0(jiti@2.7.0) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.65.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.65.0(typescript@5.9.3) + '@typescript-eslint/types': 8.65.0 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.65.0': + dependencies: + '@typescript-eslint/types': 8.65.0 + '@typescript-eslint/visitor-keys': 8.65.0 + + '@typescript-eslint/tsconfig-utils@8.65.0(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/type-utils@8.65.0(eslint@10.8.0(jiti@2.7.0))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.65.0 + '@typescript-eslint/typescript-estree': 8.65.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.65.0(eslint@10.8.0(jiti@2.7.0))(typescript@5.9.3) + debug: 4.4.3 + eslint: 10.8.0(jiti@2.7.0) + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.65.0': {} + + '@typescript-eslint/typescript-estree@8.65.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.65.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.65.0(typescript@5.9.3) + '@typescript-eslint/types': 8.65.0 + '@typescript-eslint/visitor-keys': 8.65.0 + debug: 4.4.3 + minimatch: 10.2.5 + semver: 7.8.5 + tinyglobby: 0.2.17 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.65.0(eslint@10.8.0(jiti@2.7.0))(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.0(jiti@2.7.0)) + '@typescript-eslint/scope-manager': 8.65.0 + '@typescript-eslint/types': 8.65.0 + '@typescript-eslint/typescript-estree': 8.65.0(typescript@5.9.3) + eslint: 10.8.0(jiti@2.7.0) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.65.0': + dependencies: + '@typescript-eslint/types': 8.65.0 + eslint-visitor-keys: 5.0.1 + + '@vitejs/plugin-react@4.7.0(vite@6.4.3(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0))': + dependencies: + '@babel/core': 7.29.7 + '@babel/plugin-transform-react-jsx-self': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-react-jsx-source': 7.29.7(@babel/core@7.29.7) + '@rolldown/pluginutils': 1.0.0-beta.27 + '@types/babel__core': 7.20.5 + react-refresh: 0.17.0 + vite: 6.4.3(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0) + transitivePeerDependencies: + - supports-color + + '@vitest/expect@4.1.10': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.10 + '@vitest/utils': 4.1.10 + chai: 6.2.2 + tinyrainbow: 3.1.1 + + '@vitest/mocker@4.1.10(vite@6.4.3(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0))': + dependencies: + '@vitest/spy': 4.1.10 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 6.4.3(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0) + + '@vitest/pretty-format@4.1.10': + dependencies: + tinyrainbow: 3.1.1 + + '@vitest/runner@4.1.10': + dependencies: + '@vitest/utils': 4.1.10 + pathe: 2.0.3 + + '@vitest/snapshot@4.1.10': + dependencies: + '@vitest/pretty-format': 4.1.10 + '@vitest/utils': 4.1.10 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.1.10': {} + + '@vitest/utils@4.1.10': + dependencies: + '@vitest/pretty-format': 4.1.10 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.1 + + acorn-jsx@5.3.2(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + + acorn@8.16.0: {} + + ajv@6.15.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ajv@8.20.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.2 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + any-promise@1.3.0: {} + + array-buffer-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + is-array-buffer: 3.0.5 + + arraybuffer.prototype.slice@1.0.4: + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + is-array-buffer: 3.0.5 + + assertion-error@2.0.1: {} + + async-function@1.0.0: {} + + async@3.2.6: {} + + at-least-node@1.0.0: {} + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + + babel-plugin-polyfill-corejs2@0.4.17(@babel/core@7.29.7): + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/core': 7.29.7 + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.7) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-corejs3@0.14.2(@babel/core@7.29.7): + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.7) + core-js-compat: 3.49.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-regenerator@0.6.8(@babel/core@7.29.7): + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.7) + transitivePeerDependencies: + - supports-color + + balanced-match@1.0.2: {} + + balanced-match@4.0.4: {} + + baseline-browser-mapping@2.10.33: {} + + brace-expansion@2.1.1: + dependencies: + balanced-match: 1.0.2 + + brace-expansion@5.0.6: + dependencies: + balanced-match: 4.0.4 + + browserslist@4.28.2: + dependencies: + baseline-browser-mapping: 2.10.33 + caniuse-lite: 1.0.30001793 + electron-to-chromium: 1.5.368 + node-releases: 2.0.47 + update-browserslist-db: 1.2.3(browserslist@4.28.2) + + buffer-from@1.1.2: {} + + bundle-require@5.1.0(esbuild@0.27.7): + dependencies: + esbuild: 0.27.7 + load-tsconfig: 0.2.5 + + cac@6.7.14: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.9: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + caniuse-lite@1.0.30001793: {} + + chai@6.2.2: {} + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + clsx@2.1.1: {} + + commander@2.20.3: {} + + commander@4.1.1: {} + + common-tags@1.8.2: {} + + confbox@0.1.8: {} + + consola@3.4.2: {} + + convert-source-map@2.0.0: {} + + cookie@1.1.1: {} + + core-js-compat@3.49.0: + dependencies: + browserslist: 4.28.2 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + crypto-random-string@2.0.0: {} + + csstype@3.2.3: {} + + data-view-buffer@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-offset@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-is@0.1.4: {} + + deepmerge@4.3.1: {} + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + + detect-libc@2.1.2: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + ejs@3.1.10: + dependencies: + jake: 10.9.4 + + electron-to-chromium@1.5.368: {} + + enhanced-resolve@5.23.0: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.3 + + es-abstract@1.24.2: + dependencies: + array-buffer-byte-length: 1.0.2 + arraybuffer.prototype.slice: 1.0.4 + available-typed-arrays: 1.0.7 + call-bind: 1.0.9 + call-bound: 1.0.4 + data-view-buffer: 1.0.2 + data-view-byte-length: 1.0.2 + data-view-byte-offset: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + es-set-tostringtag: 2.1.0 + es-to-primitive: 1.3.0 + function.prototype.name: 1.1.8 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + get-symbol-description: 1.1.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 + is-callable: 1.2.7 + is-data-view: 1.0.2 + is-negative-zero: 2.0.3 + is-regex: 1.2.1 + is-set: 2.0.3 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.1 + math-intrinsics: 1.1.0 + object-inspect: 1.13.4 + object-keys: 1.1.1 + object.assign: 4.1.7 + own-keys: 1.0.1 + regexp.prototype.flags: 1.5.4 + safe-array-concat: 1.1.4 + safe-push-apply: 1.0.0 + safe-regex-test: 1.1.0 + set-proto: 1.0.0 + stop-iteration-iterator: 1.1.0 + string.prototype.trim: 1.2.10 + string.prototype.trimend: 1.0.9 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.3 + typed-array-byte-length: 1.0.3 + typed-array-byte-offset: 1.0.4 + typed-array-length: 1.0.8 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.21 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-module-lexer@2.3.1: {} + + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.4 + + es-to-primitive@1.3.0: + dependencies: + is-callable: 1.2.7 + is-date-object: 1.1.0 + is-symbol: 1.1.1 + + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + + esbuild@0.27.7: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.7 + '@esbuild/android-arm': 0.27.7 + '@esbuild/android-arm64': 0.27.7 + '@esbuild/android-x64': 0.27.7 + '@esbuild/darwin-arm64': 0.27.7 + '@esbuild/darwin-x64': 0.27.7 + '@esbuild/freebsd-arm64': 0.27.7 + '@esbuild/freebsd-x64': 0.27.7 + '@esbuild/linux-arm': 0.27.7 + '@esbuild/linux-arm64': 0.27.7 + '@esbuild/linux-ia32': 0.27.7 + '@esbuild/linux-loong64': 0.27.7 + '@esbuild/linux-mips64el': 0.27.7 + '@esbuild/linux-ppc64': 0.27.7 + '@esbuild/linux-riscv64': 0.27.7 + '@esbuild/linux-s390x': 0.27.7 + '@esbuild/linux-x64': 0.27.7 + '@esbuild/netbsd-arm64': 0.27.7 + '@esbuild/netbsd-x64': 0.27.7 + '@esbuild/openbsd-arm64': 0.27.7 + '@esbuild/openbsd-x64': 0.27.7 + '@esbuild/openharmony-arm64': 0.27.7 + '@esbuild/sunos-x64': 0.27.7 + '@esbuild/win32-arm64': 0.27.7 + '@esbuild/win32-ia32': 0.27.7 + '@esbuild/win32-x64': 0.27.7 + + escalade@3.2.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-scope@9.1.2: + dependencies: + '@types/esrecurse': 4.3.1 + '@types/estree': 1.0.9 + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@5.0.1: {} + + eslint@10.8.0(jiti@2.7.0): + dependencies: + '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.0(jiti@2.7.0)) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.23.5 + '@eslint/config-helpers': 0.7.0 + '@eslint/core': 1.2.1 + '@eslint/plugin-kit': 0.7.2 + '@humanfs/node': 0.16.8 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.9 + ajv: 6.15.0 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 9.1.2 + eslint-visitor-keys: 5.0.1 + espree: 11.2.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + minimatch: 10.2.5 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.7.0 + transitivePeerDependencies: + - supports-color + + espree@11.2.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 5.0.1 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@2.0.2: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + + esutils@2.0.3: {} + + eta@4.6.0: {} + + expect-type@1.4.0: {} + + fast-deep-equal@3.1.3: {} + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fast-uri@3.1.2: {} + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + filelist@1.0.6: + dependencies: + minimatch: 5.1.9 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + fix-dts-default-cjs-exports@1.0.1: + dependencies: + magic-string: 0.30.21 + mlly: 1.8.2 + rollup: 4.61.1 + + flat-cache@4.0.1: + dependencies: + flatted: 3.4.4 + keyv: 4.5.4 + + flatted@3.4.4: {} + + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + framer-motion@12.40.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + dependencies: + motion-dom: 12.40.0 + motion-utils: 12.39.0 + tslib: 2.8.1 + optionalDependencies: + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + + fs-extra@9.1.0: + dependencies: + at-least-node: 1.0.0 + graceful-fs: 4.2.11 + jsonfile: 6.2.1 + universalify: 2.0.1 + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + function.prototype.name@1.1.8: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + functions-have-names: 1.2.3 + hasown: 2.0.4 + is-callable: 1.2.7 + + functions-have-names@1.2.3: {} + + generator-function@2.0.1: {} + + gensync@1.0.0-beta.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + math-intrinsics: 1.1.0 + + get-own-enumerable-property-symbols@3.0.2: {} + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.2 + + get-symbol-description@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@11.1.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 4.2.3 + minimatch: 10.2.5 + minipass: 7.1.3 + package-json-from-dist: 1.0.1 + path-scurry: 2.0.2 + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + has-bigints@1.1.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-proto@1.2.0: + dependencies: + dunder-proto: 1.0.1 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + + idb@7.1.1: {} + + ignore@5.3.2: {} + + ignore@7.0.6: {} + + imurmurhash@0.1.4: {} + + internal-slot@1.1.0: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.4 + side-channel: 1.1.0 + + is-array-buffer@3.0.5: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + is-async-function@2.1.1: + dependencies: + async-function: 1.0.0 + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-bigint@1.1.0: + dependencies: + has-bigints: 1.1.0 + + is-boolean-object@1.2.2: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-callable@1.2.7: {} + + is-core-module@2.16.2: + dependencies: + hasown: 2.0.4 + + is-data-view@1.0.2: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-typed-array: 1.1.15 + + is-date-object@1.1.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-extglob@2.1.1: {} + + is-finalizationregistry@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-generator-function@1.1.2: + dependencies: + call-bound: 1.0.4 + generator-function: 2.0.1 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-map@2.0.3: {} + + is-module@1.0.0: {} + + is-negative-zero@2.0.3: {} + + is-number-object@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-obj@1.0.1: {} + + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.4 + + is-regexp@1.0.0: {} + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.4: + dependencies: + call-bound: 1.0.4 + + is-stream@2.0.1: {} + + is-string@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-symbol@1.1.1: + dependencies: + call-bound: 1.0.4 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.21 + + is-weakmap@2.0.2: {} + + is-weakref@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-weakset@2.0.4: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + jackspeak@4.2.3: + dependencies: + '@isaacs/cliui': 9.0.0 + + jake@10.9.4: + dependencies: + async: 3.2.6 + filelist: 1.0.6 + picocolors: 1.1.1 + + jiti@2.7.0: {} + + joycon@3.1.1: {} + + js-tokens@4.0.0: {} + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-schema-traverse@1.0.0: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@2.2.3: {} + + jsonfile@6.2.1: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + jsonpointer@5.0.1: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + leven@3.1.0: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + load-tsconfig@0.2.5: {} + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.debounce@4.0.8: {} + + lodash.sortby@4.7.0: {} + + lru-cache@11.5.1: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lucide-react@0.468.0(react@19.2.7): + dependencies: + react: 19.2.7 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + math-intrinsics@1.1.0: {} + + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.6 + + minimatch@5.1.9: + dependencies: + brace-expansion: 2.1.1 + + minipass@7.1.3: {} + + mlly@1.8.2: + dependencies: + acorn: 8.16.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.4 + + motion-dom@12.40.0: + dependencies: + motion-utils: 12.39.0 + + motion-utils@12.39.0: {} + + ms@2.1.3: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nanoid@3.3.12: {} + + natural-compare@1.4.0: {} + + node-releases@2.0.47: {} + + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + object-keys@1.1.1: {} + + object.assign@4.1.7: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.2 + has-symbols: 1.1.0 + object-keys: 1.1.1 + + obug@2.1.4: {} + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + own-keys@1.0.1: + dependencies: + get-intrinsic: 1.3.0 + object-keys: 1.1.1 + safe-push-apply: 1.0.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + package-json-from-dist@1.0.1: {} + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-scurry@2.0.2: + dependencies: + lru-cache: 11.5.1 + minipass: 7.1.3 + + pathe@2.0.3: {} + + picocolors@1.1.1: {} + + picomatch@4.0.4: {} + + pirates@4.0.7: {} + + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.8.2 + pathe: 2.0.3 + + possible-typed-array-names@1.1.0: {} + + postcss-load-config@6.0.1(jiti@2.7.0)(postcss@8.5.15): + dependencies: + lilconfig: 3.1.3 + optionalDependencies: + jiti: 2.7.0 + postcss: 8.5.15 + + postcss@8.5.15: + dependencies: + nanoid: 3.3.12 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + pretty-bytes@5.6.0: {} + + pretty-bytes@6.1.1: {} + + punycode@2.3.1: {} + + react-dom@19.2.7(react@19.2.7): + dependencies: + react: 19.2.7 + scheduler: 0.27.0 + + react-refresh@0.17.0: {} + + react-router-dom@7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + dependencies: + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + react-router: 7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + + react-router@7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + dependencies: + cookie: 1.1.1 + react: 19.2.7 + set-cookie-parser: 2.7.2 + optionalDependencies: + react-dom: 19.2.7(react@19.2.7) + + react@19.2.7: {} + + readdirp@4.1.2: {} + + reflect.getprototypeof@1.0.10: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + which-builtin-type: 1.2.1 + + regenerate-unicode-properties@10.2.2: + dependencies: + regenerate: 1.4.2 + + regenerate@1.4.2: {} + + regexp.prototype.flags@1.5.4: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + set-function-name: 2.0.2 + + regexpu-core@6.4.0: + dependencies: + regenerate: 1.4.2 + regenerate-unicode-properties: 10.2.2 + regjsgen: 0.8.0 + regjsparser: 0.13.1 + unicode-match-property-ecmascript: 2.0.0 + unicode-match-property-value-ecmascript: 2.2.1 + + regjsgen@0.8.0: {} + + regjsparser@0.13.1: + dependencies: + jsesc: 3.1.0 + + require-from-string@2.0.2: {} + + resolve-from@5.0.0: {} + + resolve@1.22.12: + dependencies: + es-errors: 1.3.0 + is-core-module: 2.16.2 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + rollup@4.61.1: + dependencies: + '@types/estree': 1.0.9 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.61.1 + '@rollup/rollup-android-arm64': 4.61.1 + '@rollup/rollup-darwin-arm64': 4.61.1 + '@rollup/rollup-darwin-x64': 4.61.1 + '@rollup/rollup-freebsd-arm64': 4.61.1 + '@rollup/rollup-freebsd-x64': 4.61.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.61.1 + '@rollup/rollup-linux-arm-musleabihf': 4.61.1 + '@rollup/rollup-linux-arm64-gnu': 4.61.1 + '@rollup/rollup-linux-arm64-musl': 4.61.1 + '@rollup/rollup-linux-loong64-gnu': 4.61.1 + '@rollup/rollup-linux-loong64-musl': 4.61.1 + '@rollup/rollup-linux-ppc64-gnu': 4.61.1 + '@rollup/rollup-linux-ppc64-musl': 4.61.1 + '@rollup/rollup-linux-riscv64-gnu': 4.61.1 + '@rollup/rollup-linux-riscv64-musl': 4.61.1 + '@rollup/rollup-linux-s390x-gnu': 4.61.1 + '@rollup/rollup-linux-x64-gnu': 4.61.1 + '@rollup/rollup-linux-x64-musl': 4.61.1 + '@rollup/rollup-openbsd-x64': 4.61.1 + '@rollup/rollup-openharmony-arm64': 4.61.1 + '@rollup/rollup-win32-arm64-msvc': 4.61.1 + '@rollup/rollup-win32-ia32-msvc': 4.61.1 + '@rollup/rollup-win32-x64-gnu': 4.61.1 + '@rollup/rollup-win32-x64-msvc': 4.61.1 + fsevents: 2.3.3 + + safe-array-concat@1.1.4: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + isarray: 2.0.5 + + safe-push-apply@1.0.0: + dependencies: + es-errors: 1.3.0 + isarray: 2.0.5 + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + + scheduler@0.27.0: {} + + semver@6.3.1: {} + + semver@7.8.5: {} + + serialize-javascript@7.0.5: {} + + set-cookie-parser@2.7.2: {} + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + set-proto@1.0.0: + dependencies: + dunder-proto: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + siginfo@2.0.0: {} + + signal-exit@4.1.0: {} + + smob@1.6.2: {} + + source-map-js@1.2.1: {} + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + source-map@0.7.6: {} + + source-map@0.8.0-beta.0: + dependencies: + whatwg-url: 7.1.0 + + stackback@0.0.2: {} + + std-env@4.2.0: {} + + stop-iteration-iterator@1.1.0: + dependencies: + es-errors: 1.3.0 + internal-slot: 1.1.0 + + string.prototype.matchall@4.0.12: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + regexp.prototype.flags: 1.5.4 + set-function-name: 2.0.2 + side-channel: 1.1.0 + + string.prototype.trim@1.2.10: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-data-property: 1.1.4 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.2 + has-property-descriptors: 1.0.2 + + string.prototype.trimend@1.0.9: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.2 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-object-atoms: 1.1.2 + + stringify-object@3.3.0: + dependencies: + get-own-enumerable-property-symbols: 3.0.2 + is-obj: 1.0.1 + is-regexp: 1.0.0 + + strip-comments@2.0.1: {} + + sucrase@3.35.1: + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + commander: 4.1.1 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.7 + tinyglobby: 0.2.17 + ts-interface-checker: 0.1.13 + + supports-preserve-symlinks-flag@1.0.0: {} + + tailwind-merge@2.5.4: {} + + tailwind-variants@0.3.1(tailwindcss@4.3.0): + dependencies: + tailwind-merge: 2.5.4 + tailwindcss: 4.3.0 + + tailwindcss@4.3.0: {} + + tapable@2.3.3: {} + + temp-dir@2.0.0: {} + + tempy@0.6.0: + dependencies: + is-stream: 2.0.1 + temp-dir: 2.0.0 + type-fest: 0.16.0 + unique-string: 2.0.0 + + terser@5.48.0: + dependencies: + '@jridgewell/source-map': 0.3.11 + acorn: 8.16.0 + commander: 2.20.3 + source-map-support: 0.5.21 + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinyexec@1.3.0: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + tinyrainbow@3.1.1: {} + + tr46@1.0.1: + dependencies: + punycode: 2.3.1 + + tree-kill@1.2.2: {} + + ts-api-utils@2.5.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + + ts-interface-checker@0.1.13: {} + + tslib@2.8.1: {} + + tsup@8.5.1(jiti@2.7.0)(postcss@8.5.15)(typescript@5.9.3): + dependencies: + bundle-require: 5.1.0(esbuild@0.27.7) + cac: 6.7.14 + chokidar: 4.0.3 + consola: 3.4.2 + debug: 4.4.3 + esbuild: 0.27.7 + fix-dts-default-cjs-exports: 1.0.1 + joycon: 3.1.1 + picocolors: 1.1.1 + postcss-load-config: 6.0.1(jiti@2.7.0)(postcss@8.5.15) + resolve-from: 5.0.0 + rollup: 4.61.1 + source-map: 0.7.6 + sucrase: 3.35.1 + tinyexec: 0.3.2 + tinyglobby: 0.2.17 + tree-kill: 1.2.2 + optionalDependencies: + postcss: 8.5.15 + typescript: 5.9.3 + transitivePeerDependencies: + - jiti + - supports-color + - tsx + - yaml + + tyme4ts@1.5.1: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-fest@0.16.0: {} + + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + + typed-array-byte-length@1.0.3: + dependencies: + call-bind: 1.0.9 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + + typed-array-byte-offset@1.0.4: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.9 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.10 + + typed-array-length@1.0.8: + dependencies: + call-bind: 1.0.9 + for-each: 0.3.5 + gopd: 1.2.0 + is-typed-array: 1.1.15 + possible-typed-array-names: 1.1.0 + reflect.getprototypeof: 1.0.10 + + typescript-eslint@8.65.0(eslint@10.8.0(jiti@2.7.0))(typescript@5.9.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.65.0(@typescript-eslint/parser@8.65.0(eslint@10.8.0(jiti@2.7.0))(typescript@5.9.3))(eslint@10.8.0(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/parser': 8.65.0(eslint@10.8.0(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.65.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.65.0(eslint@10.8.0(jiti@2.7.0))(typescript@5.9.3) + eslint: 10.8.0(jiti@2.7.0) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + typescript@5.9.3: {} + + ufo@1.6.4: {} + + unbox-primitive@1.1.0: + dependencies: + call-bound: 1.0.4 + has-bigints: 1.1.0 + has-symbols: 1.1.0 + which-boxed-primitive: 1.1.1 + + unicode-canonical-property-names-ecmascript@2.0.1: {} + + unicode-match-property-ecmascript@2.0.0: + dependencies: + unicode-canonical-property-names-ecmascript: 2.0.1 + unicode-property-aliases-ecmascript: 2.2.0 + + unicode-match-property-value-ecmascript@2.2.1: {} + + unicode-property-aliases-ecmascript@2.2.0: {} + + unique-string@2.0.0: + dependencies: + crypto-random-string: 2.0.0 + + universalify@2.0.1: {} + + upath@1.2.0: {} + + update-browserslist-db@1.2.3(browserslist@4.28.2): + dependencies: + browserslist: 4.28.2 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + vite-plugin-pwa@0.21.2(vite@6.4.3(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0))(workbox-build@7.4.1(@types/babel__core@7.20.5))(workbox-window@7.4.1): + dependencies: + debug: 4.4.3 + pretty-bytes: 6.1.1 + tinyglobby: 0.2.17 + vite: 6.4.3(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0) + workbox-build: 7.4.1(@types/babel__core@7.20.5) + workbox-window: 7.4.1 + transitivePeerDependencies: + - supports-color + + vite@6.4.3(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0): + dependencies: + esbuild: 0.25.12 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + postcss: 8.5.15 + rollup: 4.61.1 + tinyglobby: 0.2.17 + optionalDependencies: + fsevents: 2.3.3 + jiti: 2.7.0 + lightningcss: 1.32.0 + terser: 5.48.0 + + vitest@4.1.10(vite@6.4.3(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)): + dependencies: + '@vitest/expect': 4.1.10 + '@vitest/mocker': 4.1.10(vite@6.4.3(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)) + '@vitest/pretty-format': 4.1.10 + '@vitest/runner': 4.1.10 + '@vitest/snapshot': 4.1.10 + '@vitest/spy': 4.1.10 + '@vitest/utils': 4.1.10 + es-module-lexer: 2.3.1 + expect-type: 1.4.0 + magic-string: 0.30.21 + obug: 2.1.4 + pathe: 2.0.3 + picomatch: 4.0.4 + std-env: 4.2.0 + tinybench: 2.9.0 + tinyexec: 1.3.0 + tinyglobby: 0.2.17 + tinyrainbow: 3.1.1 + vite: 6.4.3(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0) + why-is-node-running: 2.3.0 + transitivePeerDependencies: + - msw + + webidl-conversions@4.0.2: {} + + whatwg-url@7.1.0: + dependencies: + lodash.sortby: 4.7.0 + tr46: 1.0.1 + webidl-conversions: 4.0.2 + + which-boxed-primitive@1.1.1: + dependencies: + is-bigint: 1.1.0 + is-boolean-object: 1.2.2 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 + + which-builtin-type@1.2.1: + dependencies: + call-bound: 1.0.4 + function.prototype.name: 1.1.8 + has-tostringtag: 1.0.2 + is-async-function: 2.1.1 + is-date-object: 1.1.0 + is-finalizationregistry: 1.1.1 + is-generator-function: 1.1.2 + is-regex: 1.2.1 + is-weakref: 1.1.1 + isarray: 2.0.5 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.21 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 + + which-typed-array@1.1.21: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.9 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + word-wrap@1.2.5: {} + + workbox-background-sync@7.4.1: + dependencies: + idb: 7.1.1 + workbox-core: 7.4.1 + + workbox-broadcast-update@7.4.1: + dependencies: + workbox-core: 7.4.1 + + workbox-build@7.4.1(@types/babel__core@7.20.5): + dependencies: + '@apideck/better-ajv-errors': 0.3.7(ajv@8.20.0) + '@babel/core': 7.29.7 + '@babel/preset-env': 7.29.7(@babel/core@7.29.7) + '@babel/runtime': 7.29.7 + '@rollup/plugin-babel': 6.1.0(@babel/core@7.29.7)(@types/babel__core@7.20.5)(rollup@4.61.1) + '@rollup/plugin-node-resolve': 16.0.3(rollup@4.61.1) + '@rollup/plugin-replace': 6.0.3(rollup@4.61.1) + '@rollup/plugin-terser': 1.0.0(rollup@4.61.1) + '@trickfilm400/rollup-plugin-off-main-thread': 3.0.0-pre1 + ajv: 8.20.0 + common-tags: 1.8.2 + eta: 4.6.0 + fast-json-stable-stringify: 2.1.0 + fs-extra: 9.1.0 + glob: 11.1.0 + pretty-bytes: 5.6.0 + rollup: 4.61.1 + source-map: 0.8.0-beta.0 + stringify-object: 3.3.0 + strip-comments: 2.0.1 + tempy: 0.6.0 + upath: 1.2.0 + workbox-background-sync: 7.4.1 + workbox-broadcast-update: 7.4.1 + workbox-cacheable-response: 7.4.1 + workbox-core: 7.4.1 + workbox-expiration: 7.4.1 + workbox-google-analytics: 7.4.1 + workbox-navigation-preload: 7.4.1 + workbox-precaching: 7.4.1 + workbox-range-requests: 7.4.1 + workbox-recipes: 7.4.1 + workbox-routing: 7.4.1 + workbox-strategies: 7.4.1 + workbox-streams: 7.4.1 + workbox-sw: 7.4.1 + workbox-window: 7.4.1 + transitivePeerDependencies: + - '@types/babel__core' + - supports-color + + workbox-cacheable-response@7.4.1: + dependencies: + workbox-core: 7.4.1 + + workbox-core@7.4.1: {} + + workbox-expiration@7.4.1: + dependencies: + idb: 7.1.1 + workbox-core: 7.4.1 + + workbox-google-analytics@7.4.1: + dependencies: + workbox-background-sync: 7.4.1 + workbox-core: 7.4.1 + workbox-routing: 7.4.1 + workbox-strategies: 7.4.1 + + workbox-navigation-preload@7.4.1: + dependencies: + workbox-core: 7.4.1 + + workbox-precaching@7.4.1: + dependencies: + workbox-core: 7.4.1 + workbox-routing: 7.4.1 + workbox-strategies: 7.4.1 + + workbox-range-requests@7.4.1: + dependencies: + workbox-core: 7.4.1 + + workbox-recipes@7.4.1: + dependencies: + workbox-cacheable-response: 7.4.1 + workbox-core: 7.4.1 + workbox-expiration: 7.4.1 + workbox-precaching: 7.4.1 + workbox-routing: 7.4.1 + workbox-strategies: 7.4.1 + + workbox-routing@7.4.1: + dependencies: + workbox-core: 7.4.1 + + workbox-strategies@7.4.1: + dependencies: + workbox-core: 7.4.1 + + workbox-streams@7.4.1: + dependencies: + workbox-core: 7.4.1 + workbox-routing: 7.4.1 + + workbox-sw@7.4.1: {} + + workbox-window@7.4.1: + dependencies: + '@types/trusted-types': 2.0.7 + workbox-core: 7.4.1 + + yallist@3.1.1: {} + + yocto-queue@0.1.0: {} + + zustand@5.0.14(@types/react@19.2.16)(react@19.2.7): + optionalDependencies: + '@types/react': 19.2.16 + react: 19.2.7 diff --git a/web/pnpm-workspace.yaml b/web/pnpm-workspace.yaml new file mode 100644 index 0000000..578b40f --- /dev/null +++ b/web/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +packages: + - 'packages/*' +allowBuilds: + esbuild: true diff --git a/web/tsconfig.base.json b/web/tsconfig.base.json new file mode 100644 index 0000000..142945c --- /dev/null +++ b/web/tsconfig.base.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "lib": ["ES2022"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "isolatedModules": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noFallthroughCasesInSwitch": true, + "useDefineForClassFields": true + } +}