Update web compliance disclosures

This commit is contained in:
zl-q
2026-05-12 17:51:10 +08:00
parent 2b8984edbc
commit d712645754
13 changed files with 72 additions and 32 deletions
+6
View File
@@ -8,6 +8,12 @@ MeeYao Divination is designed based on traditional oriental culture. Our core go
--- ---
## AI Model Disclosure
MeeYao Divination's AI Analysis feature is powered by DeepSeek's deepseek-v4-flash model.
---
## Company Info ## Company Info
**Developer:** Ann Lee **Developer:** Ann Lee
@@ -25,6 +25,7 @@ You represent and warrant that you are at least 13 years of age to use this App.
This App provides AI-assisted cultural interpretation content related to traditional I Ching and Six-Line culture, for daily reference and cultural appreciation only. This App provides AI-assisted cultural interpretation content related to traditional I Ching and Six-Line culture, for daily reference and cultural appreciation only.
- The AI Analysis feature is powered by DeepSeek's deepseek-v4-flash model.
- All AI-generated content and cultural reference materials are for entertainment and personal reference purposes solely. - All AI-generated content and cultural reference materials are for entertainment and personal reference purposes solely.
- Content shall not be regarded as professional advice, including without limitation finance, investment, law, medical treatment, career or business decision-making. - Content shall not be regarded as professional advice, including without limitation finance, investment, law, medical treatment, career or business decision-making.
- I do not guarantee the accuracy, completeness or practicality of any AI-generated content within the App. - I do not guarantee the accuracy, completeness or practicality of any AI-generated content within the App.
+6
View File
@@ -8,6 +8,12 @@
--- ---
## AI 模型披露
觅爻 MeeYao 的 AI 解卦分析功能由 DeepSeek 的 deepseek-v4-flash 模型提供支持。
---
## 开发者信息 ## 开发者信息
**开发者**Ann Lee **开发者**Ann Lee
@@ -25,6 +25,7 @@
本应用提供与传统易经和六爻文化相关的 AI 辅助文化解读内容,仅供日常参考和文化赏析。 本应用提供与传统易经和六爻文化相关的 AI 辅助文化解读内容,仅供日常参考和文化赏析。
- AI 解卦分析功能由 DeepSeek 的 deepseek-v4-flash 模型提供支持。
- 所有 AI 生成内容和文化参考资料仅供娱乐和个人参考目的。 - 所有 AI 生成内容和文化参考资料仅供娱乐和个人参考目的。
- 内容不得视为专业建议,包括但不限于金融、投资、法律、医疗、职业或商业决策。 - 内容不得视为专业建议,包括但不限于金融、投资、法律、医疗、职业或商业决策。
- 我不保证本应用内任何 AI 生成内容的准确性、完整性或实用性。 - 我不保证本应用内任何 AI 生成内容的准确性、完整性或实用性。
@@ -8,6 +8,12 @@
--- ---
## AI 模型披露
覓爻 MeeYao 的 AI 解卦分析功能由 DeepSeek 的 deepseek-v4-flash 模型提供支持。
---
## 開發者信息 ## 開發者信息
**開發者**Ann Lee **開發者**Ann Lee
@@ -25,6 +25,7 @@
本應用提供與傳統易經和六爻文化相關的 AI 輔助文化解讀內容,僅供日常參考和文化賞析。 本應用提供與傳統易經和六爻文化相關的 AI 輔助文化解讀內容,僅供日常參考和文化賞析。
- AI 解卦分析功能由 DeepSeek 的 deepseek-v4-flash 模型提供支持。
- 所有 AI 生成內容和文化參考資料僅供娛樂和個人參考目的。 - 所有 AI 生成內容和文化參考資料僅供娛樂和個人參考目的。
- 內容不得視為專業建議,包括但不限於金融、投資、法律、醫療、職業或商業決策。 - 內容不得視為專業建議,包括但不限於金融、投資、法律、醫療、職業或商業決策。
- 我不保證本應用內任何 AI 生成內容的準確性、完整性或實用性。 - 我不保證本應用內任何 AI 生成內容的準確性、完整性或實用性。
+1 -1
View File
@@ -153,7 +153,7 @@ export default function AppShell({ locale, brandName, navItems, userName, userEm
<aside className={`fixed md:static inset-y-0 left-0 z-50 w-[260px] bg-white border-r border-slate-200 flex flex-col gap-2 p-4 transition-[width,transform] duration-300 ${sidebarCollapsed ? 'md:w-[72px] md:px-3' : ''} ${sidebarOpen ? 'translate-x-0' : '-translate-x-full md:translate-x-0'}`}> <aside className={`fixed md:static inset-y-0 left-0 z-50 w-[260px] bg-white border-r border-slate-200 flex flex-col gap-2 p-4 transition-[width,transform] duration-300 ${sidebarCollapsed ? 'md:w-[72px] md:px-3' : ''} ${sidebarOpen ? 'translate-x-0' : '-translate-x-full md:translate-x-0'}`}>
<div className={`flex items-center ${sidebarCollapsed ? 'md:justify-center' : 'justify-between'} px-2 py-2`}> <div className={`flex items-center ${sidebarCollapsed ? 'md:justify-center' : 'justify-between'} px-2 py-2`}>
<a href={`/${locale}/dashboard`} onClick={(event) => { event.preventDefault(); navigate(`/${locale}/dashboard`); }} className="flex items-center gap-3 min-w-0"> <a href={`/${locale}/dashboard`} onClick={(event) => { event.preventDefault(); navigate(`/${locale}/dashboard`); }} className="flex items-center gap-3 min-w-0">
<img src="/images/logo.png" alt="MeiYao" className="w-9 h-9 rounded-lg" /> <img src="/images/logo.png" alt="MeeYao" className="w-9 h-9 rounded-lg" />
<span className={`text-slate-900 text-lg font-bold whitespace-nowrap ${sidebarCollapsed ? 'md:hidden' : ''}`}>{brandName}</span> <span className={`text-slate-900 text-lg font-bold whitespace-nowrap ${sidebarCollapsed ? 'md:hidden' : ''}`}>{brandName}</span>
</a> </a>
<button onClick={() => setSidebarOpen(false)} className="md:hidden text-slate-400 hover:text-slate-600" aria-label="Close sidebar"> <button onClick={() => setSidebarOpen(false)} className="md:hidden text-slate-400 hover:text-slate-600" aria-label="Close sidebar">
+3 -3
View File
@@ -13,7 +13,7 @@ const footer = t(locale, 'footer');
<div class="max-w-7xl mx-auto flex flex-col md:flex-row gap-10 md:gap-16"> <div class="max-w-7xl mx-auto flex flex-col md:flex-row gap-10 md:gap-16">
<div class="w-full md:w-64 flex flex-col gap-4 shrink-0"> <div class="w-full md:w-64 flex flex-col gap-4 shrink-0">
<a href={localePath(locale, '/')} class="flex items-center gap-3"> <a href={localePath(locale, '/')} class="flex items-center gap-3">
<img src="/images/logo.png" alt="MeiYao" class="w-8 h-8" /> <img src="/images/logo.png" alt="MeeYao" class="w-8 h-8" />
<span class="text-white text-lg font-bold">{footer.brandName}</span> <span class="text-white text-lg font-bold">{footer.brandName}</span>
</a> </a>
<p class="text-slate-400 text-sm leading-relaxed">{footer.desc}</p> <p class="text-slate-400 text-sm leading-relaxed">{footer.desc}</p>
@@ -27,8 +27,8 @@ const footer = t(locale, 'footer');
</div> </div>
<div class="flex flex-col gap-3 min-w-[120px]"> <div class="flex flex-col gap-3 min-w-[120px]">
<span class="text-white text-sm font-semibold">{footer.col2Title}</span> <span class="text-white text-sm font-semibold">{footer.col2Title}</span>
<a href="#" class="text-slate-400 text-sm hover:text-white">{footer.col2Link1}</a> <a href={localePath(locale, '/about')} class="text-slate-400 text-sm hover:text-white">{footer.col2Link1}</a>
<a href="#" class="text-slate-400 text-sm hover:text-white">{footer.col2Link2}</a> <a href="mailto:feedback@xunmee.com" class="text-slate-400 text-sm hover:text-white">{footer.col2Link2}</a>
</div> </div>
<div class="flex flex-col gap-3 min-w-[120px]"> <div class="flex flex-col gap-3 min-w-[120px]">
<span class="text-white text-sm font-semibold">{footer.col3Title}</span> <span class="text-white text-sm font-semibold">{footer.col3Title}</span>
+1 -1
View File
@@ -135,7 +135,7 @@ export default function LoginForm({ locale, translations: i18n, privacyUrl, term
{/* Header */} {/* Header */}
<div className="flex flex-col items-center gap-2"> <div className="flex flex-col items-center gap-2">
<div className="w-14 h-14 rounded-[14px] overflow-hidden"> <div className="w-14 h-14 rounded-[14px] overflow-hidden">
<img src="/images/logo.png" alt="MeiYao" className="w-full h-full object-contain" /> <img src="/images/logo.png" alt="MeeYao" className="w-full h-full object-contain" />
</div> </div>
<h1 className="text-slate-900 text-2xl font-bold">{i18n.welcome}</h1> <h1 className="text-slate-900 text-2xl font-bold">{i18n.welcome}</h1>
<p className="text-slate-500 text-sm">{i18n.subtitle}</p> <p className="text-slate-500 text-sm">{i18n.subtitle}</p>
+1 -1
View File
@@ -15,7 +15,7 @@ const otherLocales: Locale[] = (['zh', 'zh_Hant', 'en'] as Locale[]).filter((l)
<header class="w-full border-b border-slate-200 bg-white sticky top-0 z-50"> <header class="w-full border-b border-slate-200 bg-white sticky top-0 z-50">
<div class="flex h-16 md:h-20 items-center justify-between gap-3 px-5 md:px-20"> <div class="flex h-16 md:h-20 items-center justify-between gap-3 px-5 md:px-20">
<a href={localePath(locale, '/')} class="flex min-w-0 items-center gap-2 md:gap-3 shrink"> <a href={localePath(locale, '/')} class="flex min-w-0 items-center gap-2 md:gap-3 shrink">
<img src="/images/logo.png" alt="MeiYao" class="w-8 h-8 md:w-9 md:h-9 shrink-0" /> <img src="/images/logo.png" alt="MeeYao" class="w-8 h-8 md:w-9 md:h-9 shrink-0" />
<span class="truncate text-slate-900 text-lg md:text-xl font-bold whitespace-nowrap">{footer.brandName}</span> <span class="truncate text-slate-900 text-lg md:text-xl font-bold whitespace-nowrap">{footer.brandName}</span>
</a> </a>
+3 -3
View File
@@ -1,5 +1,5 @@
--- ---
import { t, type Locale } from '../i18n/utils'; import { t, localePath, type Locale } from '../i18n/utils';
interface Props { interface Props {
locale: Locale; locale: Locale;
@@ -32,14 +32,14 @@ const plans = [
{plans.map((plan, i) => ( {plans.map((plan, i) => (
<div class={`reveal stagger-${(i % 4) + 1} rounded-2xl p-8 flex flex-col items-center text-center ${plan.featured ? 'bg-violet-50 border-2 border-violet-600 shadow-lg shadow-violet-100' : 'bg-white border border-slate-200'}`}> <div class={`reveal stagger-${(i % 4) + 1} rounded-2xl p-8 flex flex-col items-center text-center ${plan.featured ? 'bg-violet-50 border-2 border-violet-600 shadow-lg shadow-violet-100' : 'bg-white border border-slate-200'}`}>
<h3 class="text-slate-900 text-[22px] font-bold whitespace-nowrap">{plan.name}</h3> <h3 class="text-slate-900 text-[22px] font-bold whitespace-nowrap">{plan.name}</h3>
{plan.badge && <span class={`text-xs font-medium mt-2 px-3 py-1 rounded-full ${plan.featured ? 'bg-violet-600 text-white' : 'bg-amber-100 text-amber-700'}`}>{plan.badge}</span>} <span class={`text-xs font-medium mt-2 px-3 py-1 rounded-full ${plan.badge ? (plan.featured ? 'bg-violet-600 text-white' : 'bg-amber-100 text-amber-700') : 'bg-transparent text-transparent'}`}>{plan.badge || 'placeholder'}</span>
<p class="text-slate-900 text-4xl font-extrabold mt-4">{plan.price}</p> <p class="text-slate-900 text-4xl font-extrabold mt-4">{plan.price}</p>
<p class="text-violet-600 text-sm font-medium">{plan.credits}</p> <p class="text-violet-600 text-sm font-medium">{plan.credits}</p>
<div class={`w-full h-px my-5 ${plan.featured ? 'bg-violet-200' : 'bg-slate-100'}`}></div> <div class={`w-full h-px my-5 ${plan.featured ? 'bg-violet-200' : 'bg-slate-100'}`}></div>
<p class="text-slate-500 text-sm">{plan.desc}</p> <p class="text-slate-500 text-sm">{plan.desc}</p>
{plan.detail && <p class="text-slate-600 text-sm leading-relaxed mt-2">{plan.detail}</p>} {plan.detail && <p class="text-slate-600 text-sm leading-relaxed mt-2">{plan.detail}</p>}
<div class="flex-1 min-h-4"></div> <div class="flex-1 min-h-4"></div>
<a href="#" class={`w-full text-center py-3 rounded-lg font-semibold transition-all duration-300 mt-6 ${plan.featured ? 'cyber-gradient cyber-glow text-white hover:-translate-y-0.5' : 'bg-white text-violet-600 border border-violet-200 hover:bg-violet-50'}`}>{p.buyNow}</a> <a href={localePath(locale, '/store')} class={`w-full text-center py-3 rounded-lg font-semibold transition-all duration-300 mt-6 ${plan.featured ? 'cyber-gradient cyber-glow text-white hover:-translate-y-0.5' : 'bg-white text-violet-600 border border-violet-200 hover:bg-violet-50'}`}>{p.buyNow}</a>
</div> </div>
))} ))}
</div> </div>
+13 -13
View File
@@ -42,13 +42,13 @@ export interface Translations {
const translations: Record<Locale, Translations> = { const translations: Record<Locale, Translations> = {
zh: { zh: {
nav: { features: '功能', pricing: '定价', about: '关于', getStarted: '开始使用' }, nav: { features: '功能', pricing: '定价', about: '关于', getStarted: '开始使用' },
hero: { badge: '传承千年的东方智慧', headline: '以易经之名 寻心中所惑', subtext: '每一次签问,都是与自己的对话。觅爻将古老易经智慧与现代体验结合,让你在宁静中找到属于此刻的指引。', primaryCta: '免费开始签问', secondaryCta: '了解更多', trust: '已为 10,000+ 用户提供签问服务' }, hero: { badge: '传承千年的东方智慧', headline: '以易经之名 寻心中所惑', subtext: '每一次签问,都是与自己的对话。觅爻将古老易经智慧与现代体验结合,让你在宁静中找到属于此刻的指引。', primaryCta: '免费开始签问', secondaryCta: '了解更多', trust: '' },
showcase: { title: '仪式感的签问体验', desc: '不同于简单的随机算法,觅爻在每一次签问中融入易经的哲学思考。静心、默念、抽取三步完成,却是一次内心的沉淀之旅。', feature1Title: '64卦精解', feature1Desc: '每一卦配有详细爻辞与今译' }, showcase: { title: '仪式感的签问体验', desc: '不同于简单的随机算法,觅爻在每一次签问中融入易经的哲学思考。静心、默念、抽取三步完成,却是一次内心的沉淀之旅。', feature1Title: '64卦精解', feature1Desc: '每一卦配有详细爻辞与今译' },
testimonials: { title: '用户心声', t1Quote: '在最迷茫的时候,觅爻给了我一个方向。不管结果如何,那种静下心来的过程本身就很有帮助。', t1Name: '林小姐 · 产品经理', t2Quote: '界面很清爽,没有乱七八糟的广告。每次签问都像是一次心灵的短暂旅行。', t2Name: '张先生 · 创业者', t3Quote: '我是一个程序员,原本不信这些。但试了几次后发现,这种随机性反而让我看到平时忽略的可能性。', t3Name: '王先生 · 软件工程师' }, testimonials: { title: '用户心声', t1Quote: '在最迷茫的时候,觅爻给了我一个方向。不管结果如何,那种静下心来的过程本身就很有帮助。', t1Name: '林小姐 · 产品经理', t2Quote: '界面很清爽,没有乱七八糟的广告。每次签问都像是一次心灵的短暂旅行。', t2Name: '张先生 · 创业者', t3Quote: '我是一个程序员,原本不信这些。但试了几次后发现,这种随机性反而让我看到平时忽略的可能性。', t3Name: '王先生 · 软件工程师' },
cta: { title: '开始你的第一次签问', subtitle: '无需注册,立即体验。让古老的智慧,为现代的你指引方向。', button: '免费开始 →' }, cta: { title: '开始你的第一次签问', subtitle: '无需注册,立即体验。让古老的智慧,为现代的你指引方向。', button: '免费开始 →' },
footer: { brandName: '觅爻签问', desc: '以古老智慧,解读今时困惑。让每一次签问,都成为与自己对话的机会。', col1Title: '产品', col1Link1: '功能介绍', col1Link2: '定价', col2Title: '支持', col2Link1: '帮助中心', col2Link2: '联系我们', col3Title: '法律', col3Link1: '隐私政策', col3Link2: '服务条款' }, footer: { brandName: '觅爻签问', desc: '以古老智慧,解读今时困惑。让每一次签问,都成为与自己对话的机会。', col1Title: '产品', col1Link1: '功能介绍', col1Link2: '定价', col2Title: '支持', col2Link1: '帮助中心', col2Link2: '联系我们', col3Title: '法律', col3Link1: '隐私政策', col3Link2: '服务条款' },
features: { title: '功能特性', subtitle: '以古老智慧解读今时困惑,觅爻签问提供完整的易学体验', tagline: '从起卦到解读,每一步都精心设计', c1Title: '两种起卦方式', c1Desc: '手动起卦与自动起卦,灵活选择最适合你的方式。推荐使用手动起卦,卦象解读更准确。', c2Title: 'AI 解卦分析', c2Desc: '基于传统六爻卦象与周易哲学体系,结合AI智能分析提供深度卦象解读与建议。', c3Title: '九类问题覆盖', c3Desc: '事业、情感、财富、运势、解梦、健康、学业、寻物等九大领域,全面覆盖日常生活所问。', c4Title: '追问互动', c4Desc: '每次解卦后可深入追问一次,针对卦象细节获取更多洞见,让解读更加全面深入。', c5Title: '历史记录', c5Desc: '自动保存所有解卦记录,包括卦象详情与AI解读。随时回顾历史,追踪问题变化趋势。', c6Title: '点数系统', c6Desc: '提供灵活积分套餐:新人专享、入门补充、常用加量、高频进阶。按需购买,自由使用。' }, features: { title: '功能特性', subtitle: '以古老智慧解读今时困惑,觅爻签问提供完整的易学体验', tagline: '从起卦到解读,每一步都精心设计', c1Title: '两种起卦方式', c1Desc: '手动起卦与自动起卦,灵活选择最适合你的方式。推荐使用手动起卦,卦象解读更准确。', c2Title: 'AI 解卦分析', c2Desc: '基于传统六爻卦象与周易哲学体系,结合 AI 智能分析提供深度卦象解读与建议。本功能由 DeepSeek 的 deepseek-v4-flash 模型提供支持。', c3Title: '九类问题覆盖', c3Desc: '事业、情感、财富、运势、解梦、健康、学业、寻物等九大领域,全面覆盖日常生活所问。', c4Title: '追问互动', c4Desc: '每次解卦后可深入追问一次,针对卦象细节获取更多洞见,让解读更加全面深入。', c5Title: '历史记录', c5Desc: '自动保存所有解卦记录,包括卦象详情与AI解读。随时回顾历史,追踪问题变化趋势。', c6Title: '点数系统', c6Desc: '提供灵活积分套餐:新人专享、入门补充、常用加量、高频进阶。按需购买,自由使用。' },
pricing: { title: '选择适合你的套餐', subtitle: '灵活积分套餐,按需选择,随时可用', p1Name: '新人专享包', p1Badge: '限购一次', p1Price: '$0.99', p1Credits: '60 积分', p1Desc: '最适合初次体验', p2Name: '入门补充包', p2Price: '$4.99', p2Credits: '100 积分', p2Desc: '日常解卦补充', p2Detail: '适量点数补充,经济实惠之选', p3Name: '常用加量包', p3Badge: '推荐', p3Price: '$7.99', p3Credits: '210 积分', p3Desc: '最适合日常使用', p4Name: '高频进阶包', p4Price: '$12.99', p4Credits: '415 积分', p4Desc: '重度使用优选', p4Detail: '大量点数储备,超值单价', buyNow: '立即购买' }, pricing: { title: '选择适合你的套餐', subtitle: '灵活积分套餐,按需选择,随时可用', p1Name: '新人专享包', p1Badge: '限购一次', p1Price: '$1.00', p1Credits: '60 积分', p1Desc: '最适合初次体验', p2Name: '入门补充包', p2Price: '$4.99', p2Credits: '100 积分', p2Desc: '日常解卦补充', p2Detail: '适量点数补充,经济实惠之选', p3Name: '常用加量包', p3Badge: '推荐', p3Price: '$7.99', p3Credits: '210 积分', p3Desc: '最适合日常使用', p4Name: '高频进阶包', p4Price: '$12.99', p4Credits: '415 积分', p4Desc: '重度使用优选', p4Detail: '大量点数储备,超值单价', buyNow: '立即购买' },
login: { welcome: '欢迎', subtitle: '使用邮箱验证码快速登录或注册', emailLabel: '邮箱地址', emailPlaceholder: '请输入邮箱地址', codeLabel: '验证码', codePlaceholder: '请输入验证码', sendCode: '获取验证码', submit: '登录 / 注册', agreePrefix: '我已阅读并同意', privacy: '《隐私政策》', agreeAnd: '和', terms: '《服务条款》' }, login: { welcome: '欢迎', subtitle: '使用邮箱验证码快速登录或注册', emailLabel: '邮箱地址', emailPlaceholder: '请输入邮箱地址', codeLabel: '验证码', codePlaceholder: '请输入验证码', sendCode: '获取验证码', submit: '登录 / 注册', agreePrefix: '我已阅读并同意', privacy: '《隐私政策》', agreeAnd: '和', terms: '《服务条款》' },
dashboard: { brandName: '觅爻签问', navHome: '首页', navStore: '商店', navDivination: '起卦', navManual: '手动起卦', navAuto: '自动起卦', navHistory: '历史解卦', navLanguage: '语言', navSettings: '设置', greeting: '下午好', greetingSub: '今天想要探寻什么方向?', heroTitle: '开始您的卦象之旅', heroDesc: '借助AI智能,探索未来的可能。心中有问,起卦便知。', heroCta: '立即起卦', historyTitle: '历史解卦', historyViewAll: '查看全部 →', logout: '退出登录' }, dashboard: { brandName: '觅爻签问', navHome: '首页', navStore: '商店', navDivination: '起卦', navManual: '手动起卦', navAuto: '自动起卦', navHistory: '历史解卦', navLanguage: '语言', navSettings: '设置', greeting: '下午好', greetingSub: '今天想要探寻什么方向?', heroTitle: '开始您的卦象之旅', heroDesc: '借助AI智能,探索未来的可能。心中有问,起卦便知。', heroCta: '立即起卦', historyTitle: '历史解卦', historyViewAll: '查看全部 →', logout: '退出登录' },
notifications: { title: '通知中心', loading: '加载中...', error: '加载失败', empty: '暂无通知', markAllRead: '全部已读', markAllReadDone: '已全部标记为已读' }, notifications: { title: '通知中心', loading: '加载中...', error: '加载失败', empty: '暂无通知', markAllRead: '全部已读', markAllReadDone: '已全部标记为已读' },
@@ -63,13 +63,13 @@ const translations: Record<Locale, Translations> = {
}, },
zh_Hant: { zh_Hant: {
nav: { features: '功能', pricing: '定價', about: '關於', getStarted: '開始使用' }, nav: { features: '功能', pricing: '定價', about: '關於', getStarted: '開始使用' },
hero: { badge: '傳承千年的東方智慧', headline: '以易經之名 尋心中所惑', subtext: '每一次簽問,都是與自己的對話。覓爻將古老易經智慧與現代體驗結合,讓你在寧靜中找到屬於此刻的指引。', primaryCta: '免費開始簽問', secondaryCta: '瞭解更多', trust: '已為 10,000+ 用戶提供簽問服務' }, hero: { badge: '傳承千年的東方智慧', headline: '以易經之名 尋心中所惑', subtext: '每一次簽問,都是與自己的對話。覓爻將古老易經智慧與現代體驗結合,讓你在寧靜中找到屬於此刻的指引。', primaryCta: '免費開始簽問', secondaryCta: '瞭解更多', trust: '' },
showcase: { title: '儀式感的簽問體驗', desc: '不同於簡單的隨機算法,覓爻在每一次簽問中融入易經的哲學思考。靜心、默念、抽取三步完成,卻是一次內心的沉澱之旅。', feature1Title: '64卦精解', feature1Desc: '每一卦配有詳細爻辭與今譯' }, showcase: { title: '儀式感的簽問體驗', desc: '不同於簡單的隨機算法,覓爻在每一次簽問中融入易經的哲學思考。靜心、默念、抽取三步完成,卻是一次內心的沉澱之旅。', feature1Title: '64卦精解', feature1Desc: '每一卦配有詳細爻辭與今譯' },
testimonials: { title: '用戶心聲', t1Quote: '在最迷茫的時候,覓爻給了我一個方向。不管結果如何,那種靜下心來的過程本身就很有幫助。', t1Name: '林小姐 · 產品經理', t2Quote: '界面很清爽,沒有亂七八糟的廣告。每次簽問都像是一次心靈的短暫旅行。', t2Name: '張先生 · 創業者', t3Quote: '我是一個程序員,原本不信這些。但試了幾次後發現,這種隨機性反而讓我看到平時忽略的可能性。', t3Name: '王先生 · 軟件工程師' }, testimonials: { title: '用戶心聲', t1Quote: '在最迷茫的時候,覓爻給了我一個方向。不管結果如何,那種靜下心來的過程本身就很有幫助。', t1Name: '林小姐 · 產品經理', t2Quote: '界面很清爽,沒有亂七八糟的廣告。每次簽問都像是一次心靈的短暫旅行。', t2Name: '張先生 · 創業者', t3Quote: '我是一個程序員,原本不信這些。但試了幾次後發現,這種隨機性反而讓我看到平時忽略的可能性。', t3Name: '王先生 · 軟件工程師' },
cta: { title: '開始你的第一次簽問', subtitle: '無需註冊,立即體驗。讓古老的智慧,為現代的你指引方向。', button: '免費開始 →' }, cta: { title: '開始你的第一次簽問', subtitle: '無需註冊,立即體驗。讓古老的智慧,為現代的你指引方向。', button: '免費開始 →' },
footer: { brandName: '覓爻簽問', desc: '以古老智慧,解讀今時困惑。讓每一次簽問,都成為與自己對話的機會。', col1Title: '產品', col1Link1: '功能介紹', col1Link2: '定價', col2Title: '支持', col2Link1: '幫助中心', col2Link2: '聯繫我們', col3Title: '法律', col3Link1: '隱私政策', col3Link2: '服務條款' }, footer: { brandName: '覓爻簽問', desc: '以古老智慧,解讀今時困惑。讓每一次簽問,都成為與自己對話的機會。', col1Title: '產品', col1Link1: '功能介紹', col1Link2: '定價', col2Title: '支持', col2Link1: '幫助中心', col2Link2: '聯繫我們', col3Title: '法律', col3Link1: '隱私政策', col3Link2: '服務條款' },
features: { title: '功能特性', subtitle: '以古老智慧解讀今時困惑,覓爻簽問提供完整的易學體驗', tagline: '從起卦到解讀,每一步都精心設計', c1Title: '兩種起卦方式', c1Desc: '手動起卦與自動起卦,靈活選擇最適合你的方式。推薦使用手動起卦,卦象解讀更準確。', c2Title: 'AI 解卦分析', c2Desc: '基於傳統六爻卦象與周易哲學體系,結合AI智能分析提供深度卦象解讀與建議。', c3Title: '九類問題覆蓋', c3Desc: '事業、情感、財富、運勢、解夢、健康、學業、尋物等九大領域,全面覆蓋日常生活所問。', c4Title: '追問互動', c4Desc: '每次解卦後可深入追問一次,針對卦象細節獲取更多洞見,讓解讀更加全面深入。', c5Title: '歷史記錄', c5Desc: '自動保存所有解卦記錄,包括卦象詳情與AI解讀。隨時回顧歷史,追蹤問題變化趨勢。', c6Title: '點數系統', c6Desc: '提供靈活積分套餐:新人專享、入門補充、常用加量、高頻進階。按需購買,自由使用。' }, features: { title: '功能特性', subtitle: '以古老智慧解讀今時困惑,覓爻簽問提供完整的易學體驗', tagline: '從起卦到解讀,每一步都精心設計', c1Title: '兩種起卦方式', c1Desc: '手動起卦與自動起卦,靈活選擇最適合你的方式。推薦使用手動起卦,卦象解讀更準確。', c2Title: 'AI 解卦分析', c2Desc: '基於傳統六爻卦象與周易哲學體系,結合 AI 智能分析提供深度卦象解讀與建議。本功能由 DeepSeek 的 deepseek-v4-flash 模型提供支持。', c3Title: '九類問題覆蓋', c3Desc: '事業、情感、財富、運勢、解夢、健康、學業、尋物等九大領域,全面覆蓋日常生活所問。', c4Title: '追問互動', c4Desc: '每次解卦後可深入追問一次,針對卦象細節獲取更多洞見,讓解讀更加全面深入。', c5Title: '歷史記錄', c5Desc: '自動保存所有解卦記錄,包括卦象詳情與AI解讀。隨時回顧歷史,追蹤問題變化趨勢。', c6Title: '點數系統', c6Desc: '提供靈活積分套餐:新人專享、入門補充、常用加量、高頻進階。按需購買,自由使用。' },
pricing: { title: '選擇適合你的套餐', subtitle: '靈活積分套餐,按需選擇,隨時可用', p1Name: '新人專享包', p1Badge: '限購一次', p1Price: '$0.99', p1Credits: '60 積分', p1Desc: '最適合初次體驗', p2Name: '入門補充包', p2Price: '$4.99', p2Credits: '100 積分', p2Desc: '日常解卦補充', p2Detail: '適量點數補充,經濟實惠之選', p3Name: '常用加量包', p3Badge: '推薦', p3Price: '$7.99', p3Credits: '210 積分', p3Desc: '最適合日常使用', p4Name: '高頻進階包', p4Price: '$12.99', p4Credits: '415 積分', p4Desc: '重度使用優選', p4Detail: '大量點數儲備,超值單價', buyNow: '立即購買' }, pricing: { title: '選擇適合你的套餐', subtitle: '靈活積分套餐,按需選擇,隨時可用', p1Name: '新人專享包', p1Badge: '限購一次', p1Price: '$1.00', p1Credits: '60 積分', p1Desc: '最適合初次體驗', p2Name: '入門補充包', p2Price: '$4.99', p2Credits: '100 積分', p2Desc: '日常解卦補充', p2Detail: '適量點數補充,經濟實惠之選', p3Name: '常用加量包', p3Badge: '推薦', p3Price: '$7.99', p3Credits: '210 積分', p3Desc: '最適合日常使用', p4Name: '高頻進階包', p4Price: '$12.99', p4Credits: '415 積分', p4Desc: '重度使用優選', p4Detail: '大量點數儲備,超值單價', buyNow: '立即購買' },
login: { welcome: '歡迎', subtitle: '使用郵箱驗證碼快速登錄或註冊', emailLabel: '郵箱地址', emailPlaceholder: '請輸入郵箱地址', codeLabel: '驗證碼', codePlaceholder: '請輸入驗證碼', sendCode: '獲取驗證碼', submit: '登錄 / 註冊', agreePrefix: '我已閱讀並同意', privacy: '《隱私政策》', agreeAnd: '和', terms: '《服務條款》' }, login: { welcome: '歡迎', subtitle: '使用郵箱驗證碼快速登錄或註冊', emailLabel: '郵箱地址', emailPlaceholder: '請輸入郵箱地址', codeLabel: '驗證碼', codePlaceholder: '請輸入驗證碼', sendCode: '獲取驗證碼', submit: '登錄 / 註冊', agreePrefix: '我已閱讀並同意', privacy: '《隱私政策》', agreeAnd: '和', terms: '《服務條款》' },
dashboard: { brandName: '覓爻簽問', navHome: '首頁', navStore: '商店', navDivination: '起卦', navManual: '手動起卦', navAuto: '自動起卦', navHistory: '歷史解卦', navLanguage: '語言', navSettings: '設置', greeting: '下午好', greetingSub: '今天想要探尋什麼方向?', heroTitle: '開始您的卦象之旅', heroDesc: '借助AI智能,探索未來的可能。心中有問,起卦便知。', heroCta: '立即起卦', historyTitle: '歷史解卦', historyViewAll: '查看全部 →', logout: '退出登錄' }, dashboard: { brandName: '覓爻簽問', navHome: '首頁', navStore: '商店', navDivination: '起卦', navManual: '手動起卦', navAuto: '自動起卦', navHistory: '歷史解卦', navLanguage: '語言', navSettings: '設置', greeting: '下午好', greetingSub: '今天想要探尋什麼方向?', heroTitle: '開始您的卦象之旅', heroDesc: '借助AI智能,探索未來的可能。心中有問,起卦便知。', heroCta: '立即起卦', historyTitle: '歷史解卦', historyViewAll: '查看全部 →', logout: '退出登錄' },
notifications: { title: '通知中心', loading: '加載中...', error: '加載失敗', empty: '暫無通知', markAllRead: '全部已讀', markAllReadDone: '已全部標記為已讀' }, notifications: { title: '通知中心', loading: '加載中...', error: '加載失敗', empty: '暫無通知', markAllRead: '全部已讀', markAllReadDone: '已全部標記為已讀' },
@@ -84,15 +84,15 @@ const translations: Record<Locale, Translations> = {
}, },
en: { en: {
nav: { features: 'Features', pricing: 'Pricing', about: 'About', getStarted: 'Get Started' }, nav: { features: 'Features', pricing: 'Pricing', about: 'About', getStarted: 'Get Started' },
hero: { badge: 'Ancient Eastern Wisdom', headline: 'Seek Answers Through the Wisdom of I Ching', subtext: 'Every divination is a dialogue with yourself. MeiYao combines ancient I Ching wisdom with modern experience, guiding you to find clarity in tranquility.', primaryCta: 'Start Free', secondaryCta: 'Learn More', trust: 'Trusted by 10,000+ users' }, hero: { badge: 'Ancient Eastern Wisdom', headline: 'Seek Answers Through the Wisdom of I Ching', subtext: 'Every divination is a dialogue with yourself. MeeYao combines ancient I Ching wisdom with modern experience, guiding you to find clarity in tranquility.', primaryCta: 'Start Free', secondaryCta: 'Learn More', trust: '' },
showcase: { title: 'A Ritualistic Divination Experience', desc: 'Unlike simple random algorithms, MeiYao infuses every divination with the philosophical depth of I Ching. Three steps — calm your mind, focus your intention, draw your hexagram — yet it becomes a journey of inner reflection.', feature1Title: '64 Hexagram Interpretations', feature1Desc: 'Each hexagram comes with detailed line texts and modern commentary' }, showcase: { title: 'A Ritualistic Divination Experience', desc: 'Unlike simple random algorithms, MeeYao infuses every divination with the philosophical depth of I Ching. Three steps — calm your mind, focus your intention, draw your hexagram — yet it becomes a journey of inner reflection.', feature1Title: '64 Hexagram Interpretations', feature1Desc: 'Each hexagram comes with detailed line texts and modern commentary' },
testimonials: { title: 'What Users Say', t1Quote: 'When I was most lost, MeiYao gave me direction. Regardless of the result, the process of calming down was itself very helpful.', t1Name: 'Ms. Lin · Product Manager', t2Quote: 'The interface is clean, no annoying ads. Each divination feels like a brief journey for the soul.', t2Name: 'Mr. Zhang · Entrepreneur', t3Quote: "I'm a programmer and didn't believe in this stuff. But after trying it a few times, the randomness actually helped me see possibilities I'd been overlooking.", t3Name: 'Mr. Wang · Software Engineer' }, testimonials: { title: 'What Users Say', t1Quote: 'When I was most lost, MeeYao gave me direction. Regardless of the result, the process of calming down was itself very helpful.', t1Name: 'Ms. Lin · Product Manager', t2Quote: 'The interface is clean, no annoying ads. Each divination feels like a brief journey for the soul.', t2Name: 'Mr. Zhang · Entrepreneur', t3Quote: "I'm a programmer and didn't believe in this stuff. But after trying it a few times, the randomness actually helped me see possibilities I'd been overlooking.", t3Name: 'Mr. Wang · Software Engineer' },
cta: { title: 'Begin Your First Divination', subtitle: 'No registration needed. Let ancient wisdom guide your modern life.', button: 'Start Free →' }, cta: { title: 'Begin Your First Divination', subtitle: 'No registration needed. Let ancient wisdom guide your modern life.', button: 'Start Free →' },
footer: { brandName: 'MeiYao Divination', desc: 'Using ancient wisdom to interpret modern confusion. Let every divination become a chance to dialogue with yourself.', col1Title: 'Product', col1Link1: 'Features', col1Link2: 'Pricing', col2Title: 'Support', col2Link1: 'Help Center', col2Link2: 'Contact Us', col3Title: 'Legal', col3Link1: 'Privacy Policy', col3Link2: 'Terms of Service' }, footer: { brandName: 'MeeYao Divination', desc: 'Using ancient wisdom to interpret modern confusion. Let every divination become a chance to dialogue with yourself.', col1Title: 'Product', col1Link1: 'Features', col1Link2: 'Pricing', col2Title: 'Support', col2Link1: 'Help Center', col2Link2: 'Contact Us', col3Title: 'Legal', col3Link1: 'Privacy Policy', col3Link2: 'Terms of Service' },
features: { title: 'Features', subtitle: 'Ancient wisdom meets modern困惑, MeiYao provides a complete I Ching experience', tagline: 'From casting to interpretation, every step is carefully designed', c1Title: 'Two Casting Methods', c1Desc: 'Manual and auto casting — choose what suits you best. Manual casting is recommended for more accurate readings.', c2Title: 'AI Analysis', c2Desc: 'Combining traditional Six-Line hexagrams with AI intelligence for in-depth interpretation and suggestions.', c3Title: '9 Question Categories', c3Desc: 'Career, love, wealth, fortune, dreams, health, study, lost items, and more — covering all aspects of daily life.', c4Title: 'Follow-up Questions', c4Desc: 'Ask one follow-up question after each reading for deeper insights into specific hexagram details.', c5Title: 'History', c5Desc: 'All readings are automatically saved with full hexagram details and AI interpretations. Review anytime.', c6Title: 'Credits System', c6Desc: 'Flexible credit packages: starter, basic, popular, and premium. Purchase as needed, use freely.' }, features: { title: 'Features', subtitle: 'Ancient wisdom meets modern confusion, MeeYao provides a complete I Ching experience', tagline: 'From casting to interpretation, every step is carefully designed', c1Title: 'Two Casting Methods', c1Desc: 'Manual and auto casting — choose what suits you best. Manual casting is recommended for more accurate readings.', c2Title: 'AI Analysis', c2Desc: 'Combining traditional Six-Line hexagrams with AI intelligence for in-depth interpretation and suggestions. This feature is powered by DeepSeek\'s deepseek-v4-flash model.', c3Title: '9 Question Categories', c3Desc: 'Career, love, wealth, fortune, dreams, health, study, lost items, and more — covering all aspects of daily life.', c4Title: 'Follow-up Questions', c4Desc: 'Ask one follow-up question after each reading for deeper insights into specific hexagram details.', c5Title: 'History', c5Desc: 'All readings are automatically saved with full hexagram details and AI interpretations. Review anytime.', c6Title: 'Credits System', c6Desc: 'Flexible credit packages: starter, basic, popular, and premium. Purchase as needed, use freely.' },
pricing: { title: 'Choose Your Plan', subtitle: 'Flexible credit packages, pay as you go', p1Name: 'Starter Pack', p1Badge: 'Once Only', p1Price: '$0.99', p1Credits: '60 credits', p1Desc: 'Best for first-timers', p2Name: 'Basic Pack', p2Price: '$4.99', p2Credits: '100 credits', p2Desc: 'Daily supplement', p2Detail: 'Affordable credit refill', p3Name: 'Popular Pack', p3Badge: 'Popular', p3Price: '$7.99', p3Credits: '210 credits', p3Desc: 'Best for daily use', p4Name: 'Premium Pack', p4Price: '$12.99', p4Credits: '415 credits', p4Desc: 'Best value per credit', p4Detail: 'Bulk credits at best unit price', buyNow: 'Buy Now' }, pricing: { title: 'Choose Your Plan', subtitle: 'Flexible credit packages, pay as you go', p1Name: 'Starter Pack', p1Badge: 'Once Only', p1Price: '$1.00', p1Credits: '60 credits', p1Desc: 'Best for first-timers', p2Name: 'Basic Pack', p2Price: '$4.99', p2Credits: '100 credits', p2Desc: 'Daily supplement', p2Detail: 'Affordable credit refill', p3Name: 'Popular Pack', p3Badge: 'Popular', p3Price: '$7.99', p3Credits: '210 credits', p3Desc: 'Best for daily use', p4Name: 'Premium Pack', p4Price: '$12.99', p4Credits: '415 credits', p4Desc: 'Best value per credit', p4Detail: 'Bulk credits at best unit price', buyNow: 'Buy Now' },
login: { welcome: 'Welcome', subtitle: 'Sign in or sign up with email verification code', emailLabel: 'Email', emailPlaceholder: 'Enter your email', codeLabel: 'Verification Code', codePlaceholder: 'Enter code', sendCode: 'Send Code', submit: 'Sign In / Sign Up', agreePrefix: 'I have read and agree to the ', privacy: 'Privacy Policy', agreeAnd: ' and ', terms: 'Terms of Service' }, login: { welcome: 'Welcome', subtitle: 'Sign in or sign up with email verification code', emailLabel: 'Email', emailPlaceholder: 'Enter your email', codeLabel: 'Verification Code', codePlaceholder: 'Enter code', sendCode: 'Send Code', submit: 'Sign In / Sign Up', agreePrefix: 'I have read and agree to the ', privacy: 'Privacy Policy', agreeAnd: ' and ', terms: 'Terms of Service' },
dashboard: { brandName: 'MeiYao Divination', navHome: 'Home', navStore: 'Store', navDivination: 'Divination', navManual: 'Manual Cast', navAuto: 'Auto Cast', navHistory: 'History', navLanguage: 'Language', navSettings: 'Settings', greeting: 'Good afternoon', greetingSub: 'What would you like to explore today?', heroTitle: 'Begin Your Hexagram Journey', heroDesc: 'Explore future possibilities with AI. Ask your question, cast your hexagram.', heroCta: 'Start Divination', historyTitle: 'Recent Readings', historyViewAll: 'View All →', logout: 'Sign Out' }, dashboard: { brandName: 'MeeYao Divination', navHome: 'Home', navStore: 'Store', navDivination: 'Divination', navManual: 'Manual Cast', navAuto: 'Auto Cast', navHistory: 'History', navLanguage: 'Language', navSettings: 'Settings', greeting: 'Good afternoon', greetingSub: 'What would you like to explore today?', heroTitle: 'Begin Your Hexagram Journey', heroDesc: 'Explore future possibilities with AI. Ask your question, cast your hexagram.', heroCta: 'Start Divination', historyTitle: 'Recent Readings', historyViewAll: 'View All →', logout: 'Sign Out' },
notifications: { title: 'Notifications', loading: 'Loading...', error: 'Failed to load', empty: 'No notifications', markAllRead: 'Mark All Read', markAllReadDone: 'All marked as read' }, notifications: { title: 'Notifications', loading: 'Loading...', error: 'Failed to load', empty: 'No notifications', markAllRead: 'Mark All Read', markAllReadDone: 'All marked as read' },
store: { title: 'Credits Store', currentPoints: 'Current Credits', pointsLabel: 'credits', rulesTitle: 'Credits Rules', rule1: '1 divination costs a fixed number of credits', rule2: 'Credits are added instantly after purchase', rule3: 'Starter Pack is limited to one per account', popularLabel: 'Recommended', popularText: 'Popular Pack offers the best value for most users.', stepsTitle: 'Payment Steps', step1: 'Select a package', step2: 'Complete payment', step3: 'Credits added automatically', autoCredit: 'Auto-delivery after purchase', sideTitle: 'Auto-delivery after purchase', sideDesc: 'Credits are synced to your account immediately after payment.' }, store: { title: 'Credits Store', currentPoints: 'Current Credits', pointsLabel: 'credits', rulesTitle: 'Credits Rules', rule1: '1 divination costs a fixed number of credits', rule2: 'Credits are added instantly after purchase', rule3: 'Starter Pack is limited to one per account', popularLabel: 'Recommended', popularText: 'Popular Pack offers the best value for most users.', stepsTitle: 'Payment Steps', step1: 'Select a package', step2: 'Complete payment', step3: 'Credits added automatically', autoCredit: 'Auto-delivery after purchase', sideTitle: 'Auto-delivery after purchase', sideDesc: 'Credits are synced to your account immediately after payment.' },
settings: { title: 'Settings', profileTitle: 'Profile', emailLabel: 'Email', nameLabel: 'Name', joinedLabel: 'Joined', pointsTitle: 'Credits Balance', pointsBalance: 'credits', accountTitle: 'Account Settings', changeName: 'Change Name', changeAvatar: 'Change Avatar', changeLanguage: 'Change Language', legalTitle: 'Legal', privacy: 'Privacy Policy', terms: 'Terms of Service', logout: 'Sign Out', logoutConfirm: 'Are you sure you want to sign out?' }, settings: { title: 'Settings', profileTitle: 'Profile', emailLabel: 'Email', nameLabel: 'Name', joinedLabel: 'Joined', pointsTitle: 'Credits Balance', pointsBalance: 'credits', accountTitle: 'Account Settings', changeName: 'Change Name', changeAvatar: 'Change Avatar', changeLanguage: 'Change Language', legalTitle: 'Legal', privacy: 'Privacy Policy', terms: 'Terms of Service', logout: 'Sign Out', logoutConfirm: 'Are you sure you want to sign out?' },
+21 -2
View File
@@ -77,9 +77,17 @@ export function setAuth(data: AuthData): void {
localStorage.setItem(STORAGE_KEY, JSON.stringify(data)); localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
} }
let isClearing = false;
export function clearAuth(): void { export function clearAuth(): void {
if (isClearing) return;
isClearing = true;
try {
localStorage.removeItem(STORAGE_KEY); localStorage.removeItem(STORAGE_KEY);
clearDataCache(); clearDataCache();
} finally {
isClearing = false;
}
} }
// --- Token status --- // --- Token status ---
@@ -199,21 +207,28 @@ export async function logout(): Promise<void> {
// --- authFetch --- // --- authFetch ---
export async function authFetch<T>(path: string, options?: RequestInit): Promise<T> { export async function authFetch<T>(path: string, options?: RequestInit): Promise<T> {
// 0. If already clearing auth, fail immediately
if (isClearing) {
throw new Error('Session expired');
}
// 1. Ensure token is fresh // 1. Ensure token is fresh
if (isTokenExpired()) { if (isTokenExpired()) {
try { try {
await refreshAccessToken(); await refreshAccessToken();
} catch { } catch {
// refresh failed, redirect to login // refresh failed, redirect to login
if (!isClearing) {
clearAuth(); clearAuth();
redirectToLogin(); redirectToLogin();
}
throw new Error('Session expired'); throw new Error('Session expired');
} }
} }
const auth = getAuth(); const auth = getAuth();
if (!auth) { if (!auth) {
redirectToLogin(); if (!isClearing) redirectToLogin();
throw new Error('Not authenticated'); throw new Error('Not authenticated');
} }
@@ -229,13 +244,15 @@ export async function authFetch<T>(path: string, options?: RequestInit): Promise
try { try {
await refreshAccessToken(); await refreshAccessToken();
} catch { } catch {
if (!isClearing) {
clearAuth(); clearAuth();
redirectToLogin(); redirectToLogin();
}
throw new Error('Session expired'); throw new Error('Session expired');
} }
const refreshed = getAuth(); const refreshed = getAuth();
if (!refreshed) { if (!refreshed) {
redirectToLogin(); if (!isClearing) redirectToLogin();
throw new Error('Not authenticated'); throw new Error('Not authenticated');
} }
const retryHeaders = jsonHeaders(options); const retryHeaders = jsonHeaders(options);
@@ -244,8 +261,10 @@ export async function authFetch<T>(path: string, options?: RequestInit): Promise
} }
if (res.status === 401) { if (res.status === 401) {
if (!isClearing) {
clearAuth(); clearAuth();
redirectToLogin(); redirectToLogin();
}
throw new Error('Not authenticated'); throw new Error('Not authenticated');
} }