feat(web): rebuild web with Astro 6 + React 19 + Tailwind 4
Replace static HTML website with Astro SSG framework: - Astro 6 + React 19 (client islands) + Tailwind CSS 4 + shadcn/ui - i18n: zh/zh_Hant/en with URL prefix routing - Pages: Landing, Features, Pricing, About, Privacy, Terms (3 locales) - Responsive full-width layout with scroll reveal animations - Cyber gradient theme with particle effects inspired by Kimi - Features page: alternating layout with hexagram illustrations - Legal pages: markdown rendering with side info card - Language switcher preserves current page path - Assets shared via symlinks to web/design/assets/ (no duplication) Tech decisions documented in .trellis/spec/web/index.md Task: .trellis/tasks/05-08-web-astro-react-tailwind-shadcn-ui
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
---
|
||||
import { t, localePath, type Locale } from '../i18n/utils';
|
||||
|
||||
interface Props {
|
||||
locale: Locale;
|
||||
}
|
||||
|
||||
const { locale } = Astro.props;
|
||||
const a = t(locale, 'about');
|
||||
const footer = t(locale, 'footer');
|
||||
---
|
||||
|
||||
<section class="bg-gradient-to-b from-white to-violet-50 py-16 md:py-20 px-5 md:px-20 text-center">
|
||||
<h1 class="reveal text-slate-900 text-4xl md:text-[48px] font-extrabold">{a.title}</h1>
|
||||
<p class="reveal stagger-1 text-slate-500 text-lg mt-4">{a.subtitle}</p>
|
||||
</section>
|
||||
|
||||
<section class="bg-white py-16 md:py-20 px-5 md:px-20">
|
||||
<div class="max-w-[1200px] mx-auto flex flex-col md:flex-row gap-16">
|
||||
<div class="reveal-left flex-1 flex flex-col gap-6">
|
||||
<h2 class="text-slate-900 text-[28px] font-bold">{a.storyTitle}</h2>
|
||||
<div class="w-12 h-1 bg-violet-600 rounded"></div>
|
||||
<p class="text-slate-600 text-base leading-loose">{a.p1}</p>
|
||||
<p class="text-slate-600 text-base leading-loose">{a.p2}</p>
|
||||
<p class="text-slate-600 text-base leading-loose">{a.p3}</p>
|
||||
</div>
|
||||
|
||||
<div class="reveal-right w-full md:w-[400px] bg-slate-50 border border-slate-200 rounded-2xl p-8 flex flex-col gap-6 shrink-0">
|
||||
<h3 class="text-slate-900 text-xl font-bold">{a.companyInfo}</h3>
|
||||
<div class="h-px bg-slate-200"></div>
|
||||
<p class="text-slate-600 text-base">{a.company}</p>
|
||||
<div>
|
||||
<p class="text-slate-400 text-sm font-semibold">{a.emailLabel}</p>
|
||||
<p class="text-slate-600 text-[15px]">{a.email}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-slate-400 text-sm font-semibold">{a.devLabel}</p>
|
||||
<p class="text-slate-600 text-[15px]">{a.developer}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-slate-400 text-sm font-semibold">{a.icpLabel}</p>
|
||||
<p class="text-slate-600 text-[15px]">{a.icp}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="bg-amber-50 py-20 px-5 md:px-20">
|
||||
<div class="reveal max-w-[800px] mx-auto text-center flex flex-col gap-5">
|
||||
<h3 class="text-amber-800 text-2xl font-bold">{a.warningTitle}</h3>
|
||||
<p class="text-amber-700 text-[15px] leading-loose">{a.warningBody}</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="bg-slate-50 py-12 px-5 md:px-20">
|
||||
<div class="reveal max-w-[600px] mx-auto text-center flex flex-col gap-5">
|
||||
<h3 class="text-slate-900 text-xl font-bold">{a.legalTitle}</h3>
|
||||
<div class="flex justify-center gap-8">
|
||||
<a href={localePath(locale, '/privacy')} class="text-violet-600 text-[15px] hover:underline">{footer.col3Link1}</a>
|
||||
<a href={localePath(locale, '/terms')} class="text-violet-600 text-[15px] hover:underline">{footer.col3Link2}</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
import { t, localePath, type Locale } from '../i18n/utils';
|
||||
|
||||
interface Props {
|
||||
locale: Locale;
|
||||
}
|
||||
|
||||
const { locale } = Astro.props;
|
||||
const cta = t(locale, 'cta');
|
||||
---
|
||||
|
||||
<section class="reveal-scale w-full py-24 md:py-32 relative overflow-hidden">
|
||||
<div class="glow-bg glow-bg-wide absolute inset-0 pointer-events-none"></div>
|
||||
|
||||
<div class="relative z-10 max-w-3xl mx-auto px-6 flex flex-col items-center gap-6 text-center">
|
||||
<h2 class="text-slate-900 text-3xl md:text-[48px] font-bold">
|
||||
{cta.title}
|
||||
</h2>
|
||||
<p class="text-slate-500 text-lg max-w-xl">
|
||||
{cta.subtitle}
|
||||
</p>
|
||||
<a href={localePath(locale, '/login')} class="cyber-gradient cyber-glow text-white text-lg font-semibold px-12 py-5 rounded-xl hover:-translate-y-0.5 transition-all duration-300 mt-4">
|
||||
{cta.button}
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,90 @@
|
||||
---
|
||||
import { t, type Locale } from '../i18n/utils';
|
||||
|
||||
interface Props {
|
||||
locale: Locale;
|
||||
}
|
||||
|
||||
const { locale } = Astro.props;
|
||||
const f = t(locale, 'features');
|
||||
|
||||
const icons = [
|
||||
// 01 - 两种起卦方式: sparkle/sparkles
|
||||
`<svg class="w-5 h-5 text-violet-600" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"/></svg>`,
|
||||
// 02 - AI 解卦分析: brain
|
||||
`<svg class="w-5 h-5 text-violet-600" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5a3 3 0 1 0-5.997.125 4 4 0 0 0-2.526 5.77 4 4 0 0 0 .556 6.588A4 4 0 1 0 12 18Z"/><path d="M12 5a3 3 0 1 1 5.997.125 4 4 0 0 1 2.526 5.77 4 4 0 0 1-.556 6.588A4 4 0 1 1 12 18Z"/><path d="M15 13a4.5 4.5 0 0 1-3-4 4.5 4.5 0 0 1-3 4"/><path d="M17.599 6.5a3 3 0 0 0 .399-1.375"/><path d="M6.003 5.125A3 3 0 0 0 6.401 6.5"/><path d="M3.477 10.896a4.5 4.5 0 0 1 .555-.396"/><path d="M20.523 10.896a4.5 4.5 0 0 0-.555-.396"/></svg>`,
|
||||
// 03 - 九类问题覆盖: grid/layout
|
||||
`<svg class="w-5 h-5 text-violet-600" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="7" height="7" x="3" y="3" rx="1"/><rect width="7" height="7" x="14" y="3" rx="1"/><rect width="7" height="7" x="14" y="14" rx="1"/><rect width="7" height="7" x="3" y="14" rx="1"/></svg>`,
|
||||
// 04 - 追问互动: message/chat
|
||||
`<svg class="w-5 h-5 text-violet-600" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M7.9 20A9 9 0 1 0 4 16.1L2 22Z"/></svg>`,
|
||||
// 05 - 历史记录: clock/history
|
||||
`<svg class="w-5 h-5 text-violet-600" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>`,
|
||||
// 06 - 点数系统: coins
|
||||
`<svg class="w-5 h-5 text-violet-600" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="8" cy="8" r="6"/><path d="M18.09 10.37A6 6 0 1 1 10.34 18"/><path d="M7 6h1v4"/><path d="m16.71 13.88.7.71V9"/></svg>`,
|
||||
];
|
||||
|
||||
const cards = [
|
||||
{ num: '01', title: f.c1Title, desc: f.c1Desc },
|
||||
{ num: '02', title: f.c2Title, desc: f.c2Desc },
|
||||
{ num: '03', title: f.c3Title, desc: f.c3Desc },
|
||||
{ num: '04', title: f.c4Title, desc: f.c4Desc },
|
||||
{ num: '05', title: f.c5Title, desc: f.c5Desc },
|
||||
{ num: '06', title: f.c6Title, desc: f.c6Desc },
|
||||
];
|
||||
---
|
||||
|
||||
<!-- Page Header -->
|
||||
<section class="w-full pt-32 md:pt-40 pb-16 md:pb-20 relative">
|
||||
<div class="glow-bg absolute inset-0 pointer-events-none"></div>
|
||||
<div class="relative text-center px-6 max-w-3xl mx-auto">
|
||||
<h1 class="reveal text-slate-900 text-3xl md:text-4xl lg:text-5xl font-bold mb-4">{f.title}</h1>
|
||||
<p class="reveal stagger-1 text-slate-500 text-lg">{f.subtitle}</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Feature Details - alternating layout -->
|
||||
<section class="w-full py-16 md:py-24" style="background-color: #F8F7FC;">
|
||||
<div class="max-w-6xl mx-auto px-6 space-y-24 md:space-y-32">
|
||||
{cards.map((card, index) => {
|
||||
const isEven = index % 2 === 1;
|
||||
const lines = [0,1,2,3,4,5].map(i => (index + i) % 2 === 0);
|
||||
return (
|
||||
<div class={`reveal flex flex-col ${isEven ? 'md:flex-row-reverse' : 'md:flex-row'} gap-10 md:gap-16 items-center`}>
|
||||
<!-- Text -->
|
||||
<div class="flex-1">
|
||||
<span class="font-mono text-6xl md:text-7xl font-bold select-none block mb-2 text-violet-100">{card.num}</span>
|
||||
<div class="flex items-center gap-3 mb-4 -mt-4">
|
||||
<div class="w-10 h-10 rounded-lg flex items-center justify-center bg-violet-50 shrink-0">
|
||||
<Fragment set:html={icons[index]} />
|
||||
</div>
|
||||
<h2 class="text-2xl md:text-3xl font-bold text-slate-900">{card.title}</h2>
|
||||
</div>
|
||||
<p class="text-slate-500 leading-relaxed max-w-lg">{card.desc}</p>
|
||||
</div>
|
||||
|
||||
<!-- Hexagram Illustration -->
|
||||
<div class="flex-1 w-full">
|
||||
<div class="relative w-full h-48 md:h-64 rounded-xl border border-violet-100 flex items-center justify-center bg-violet-50/30">
|
||||
<div class="flex flex-col gap-2.5 items-center">
|
||||
{lines.map((isYang) => isYang ? (
|
||||
<div class="hex-yang"></div>
|
||||
) : (
|
||||
<div class="hex-yin"><div></div><div></div></div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CTA -->
|
||||
<section class="w-full py-24 md:py-32 relative">
|
||||
<div class="glow-bg absolute inset-0 pointer-events-none"></div>
|
||||
<div class="reveal relative text-center px-6 max-w-3xl mx-auto">
|
||||
<h2 class="text-slate-900 text-3xl md:text-4xl font-bold mb-4">{f.tagline}</h2>
|
||||
<p class="text-slate-500 text-lg mb-8">{f.subtitle}</p>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
import { t, localePath, type Locale } from '../i18n/utils';
|
||||
|
||||
interface Props {
|
||||
locale: Locale;
|
||||
}
|
||||
|
||||
const { locale } = Astro.props;
|
||||
const footer = t(locale, 'footer');
|
||||
---
|
||||
|
||||
<footer class="w-full bg-slate-950 px-6 md:px-20 py-16 md:py-12">
|
||||
<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">
|
||||
<a href={localePath(locale, '/')} class="flex items-center gap-3">
|
||||
<img src="/images/logo.png" alt="MeiYao" class="w-8 h-8" />
|
||||
<span class="text-white text-lg font-bold">{footer.brandName}</span>
|
||||
</a>
|
||||
<p class="text-slate-400 text-sm leading-relaxed">{footer.desc}</p>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 flex flex-wrap gap-10 md:gap-16">
|
||||
<div class="flex flex-col gap-3 min-w-[120px]">
|
||||
<span class="text-white text-sm font-semibold">{footer.col1Title}</span>
|
||||
<a href={localePath(locale, '/features')} class="text-slate-400 text-sm hover:text-white">{footer.col1Link1}</a>
|
||||
<a href={localePath(locale, '/pricing')} class="text-slate-400 text-sm hover:text-white">{footer.col1Link2}</a>
|
||||
</div>
|
||||
<div class="flex flex-col gap-3 min-w-[120px]">
|
||||
<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="#" class="text-slate-400 text-sm hover:text-white">{footer.col2Link2}</a>
|
||||
</div>
|
||||
<div class="flex flex-col gap-3 min-w-[120px]">
|
||||
<span class="text-white text-sm font-semibold">{footer.col3Title}</span>
|
||||
<a href={localePath(locale, '/privacy')} class="text-slate-400 text-sm hover:text-white">{footer.col3Link1}</a>
|
||||
<a href={localePath(locale, '/terms')} class="text-slate-400 text-sm hover:text-white">{footer.col3Link2}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
@@ -0,0 +1,46 @@
|
||||
---
|
||||
import { t, localePath, type Locale } from '../i18n/utils';
|
||||
|
||||
interface Props {
|
||||
locale: Locale;
|
||||
}
|
||||
|
||||
const { locale } = Astro.props;
|
||||
const hero = t(locale, 'hero');
|
||||
---
|
||||
|
||||
<section class="relative w-full min-h-screen flex items-center justify-center overflow-hidden">
|
||||
<!-- Particles -->
|
||||
<div class="particle-lines"></div>
|
||||
<div class="particle"></div><div class="particle"></div><div class="particle"></div>
|
||||
<div class="particle"></div><div class="particle"></div><div class="particle"></div>
|
||||
<div class="particle"></div><div class="particle"></div>
|
||||
|
||||
<!-- Purple glow -->
|
||||
<div class="glow-bg absolute inset-0 pointer-events-none"></div>
|
||||
|
||||
<div class="relative z-10 text-center px-6 max-w-3xl mx-auto">
|
||||
<span class="reveal stagger-1 inline-block px-4 py-1.5 rounded-full text-xs font-medium tracking-widest text-violet-600 border border-violet-200 bg-violet-50 mb-8">
|
||||
{hero.badge}
|
||||
</span>
|
||||
|
||||
<h1 class="reveal stagger-2 text-slate-900 text-4xl sm:text-5xl md:text-6xl font-bold leading-tight mb-6">
|
||||
{hero.headline}
|
||||
</h1>
|
||||
|
||||
<p class="reveal stagger-3 text-slate-500 text-lg md:text-xl leading-relaxed max-w-xl mx-auto mb-10">
|
||||
{hero.subtext}
|
||||
</p>
|
||||
|
||||
<div class="reveal stagger-4 flex flex-col sm:flex-row items-center justify-center gap-4">
|
||||
<a href={localePath(locale, '/login')} class="w-full sm:w-auto text-center cyber-gradient cyber-glow text-white text-sm font-medium px-8 py-3.5 rounded-lg hover:-translate-y-0.5 transition-all duration-300">
|
||||
{hero.primaryCta}
|
||||
</a>
|
||||
<a href={localePath(locale, '/features')} class="w-full sm:w-auto text-center text-violet-600 border border-violet-200 hover:bg-violet-50 text-sm font-medium px-8 py-3.5 rounded-lg transition-all duration-300">
|
||||
{hero.secondaryCta}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<p class="reveal stagger-5 text-slate-400 text-sm mt-12">{hero.trust}</p>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,122 @@
|
||||
---
|
||||
import { t, localePath, type Locale } from '../i18n/utils';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { marked } from 'marked';
|
||||
|
||||
interface Props {
|
||||
locale: Locale;
|
||||
docType: 'privacy_policy' | 'terms_of_service' | 'about_us';
|
||||
}
|
||||
|
||||
const { locale, docType } = Astro.props;
|
||||
|
||||
const footer = t(locale, 'footer');
|
||||
const about = t(locale, 'about');
|
||||
|
||||
const titleMap: Record<string, Record<Locale, string>> = {
|
||||
privacy_policy: { zh: '隐私政策', zh_Hant: '隱私政策', en: 'Privacy Policy' },
|
||||
terms_of_service: { zh: '服务条款', zh_Hant: '服務條款', en: 'Terms of Service' },
|
||||
};
|
||||
const subtitleMap: Record<string, Record<Locale, string>> = {
|
||||
privacy_policy: { zh: '最后更新日期:2026年4月27日', zh_Hant: '最後更新日期:2026年4月27日', en: 'Last updated: April 27, 2026' },
|
||||
terms_of_service: { zh: '最后更新日期:2026年4月27日', zh_Hant: '最後更新日期:2026年4月27日', en: 'Last updated: April 27, 2026' },
|
||||
};
|
||||
const title = titleMap[docType]?.[locale] ?? '';
|
||||
const subtitle = subtitleMap[docType]?.[locale] ?? '';
|
||||
|
||||
const filePath = path.resolve('public/legal', locale, `${docType}.md`);
|
||||
let raw = '';
|
||||
try {
|
||||
raw = fs.readFileSync(filePath, 'utf-8');
|
||||
raw = raw.replace(/^#\s+.+\n*/m, '');
|
||||
} catch {
|
||||
raw = `Content not available.`;
|
||||
}
|
||||
const content = await marked(raw);
|
||||
|
||||
const sideInfo: Record<string, Record<Locale, { type: string; date: string; law?: string; email: string }>> = {
|
||||
privacy_policy: {
|
||||
zh: { type: '隐私政策', date: '2026年4月27日', email: 'ann@xunmee.com' },
|
||||
zh_Hant: { type: '隱私政策', date: '2026年4月27日', email: 'ann@xunmee.com' },
|
||||
en: { type: 'Privacy Policy', date: 'April 27, 2026', email: 'ann@xunmee.com' },
|
||||
},
|
||||
terms_of_service: {
|
||||
zh: { type: '服务条款', date: '2026年4月27日', law: '美国加利福尼亚州法律', email: 'ann@xunmee.com' },
|
||||
zh_Hant: { type: '服務條款', date: '2026年4月27日', law: '美國加利福尼亞州法律', email: 'ann@xunmee.com' },
|
||||
en: { type: 'Terms of Service', date: 'April 27, 2026', law: 'California, USA', email: 'ann@xunmee.com' },
|
||||
},
|
||||
};
|
||||
const info = sideInfo[docType]?.[locale];
|
||||
|
||||
const isActive = (linkType: string) => linkType === docType;
|
||||
---
|
||||
|
||||
<!-- Header -->
|
||||
<section class="w-full bg-gradient-to-b from-white to-violet-50 py-16 md:py-20 px-6 md:px-20 text-center">
|
||||
<h1 class="reveal text-slate-900 text-4xl md:text-[48px] font-extrabold">{title}</h1>
|
||||
<p class="reveal stagger-1 text-slate-500 text-lg mt-4">{subtitle}</p>
|
||||
</section>
|
||||
|
||||
<!-- Content: Left article + Right info card -->
|
||||
<section class="w-full py-16 md:py-20 px-6 md:px-20 bg-white">
|
||||
<div class="max-w-6xl mx-auto flex flex-col md:flex-row gap-16">
|
||||
<!-- Article -->
|
||||
<div class="reveal flex-1 prose prose-slate max-w-none
|
||||
[&_h2]:text-[28px] [&_h2]:font-bold [&_h2]:text-slate-900 [&_h2]:mt-8 [&_h2]:mb-2
|
||||
[&_h3]:text-2xl [&_h3]:font-bold [&_h3]:text-slate-900 [&_h3]:mt-6 [&_h3]:mb-2
|
||||
[&_p]:text-slate-500 [&_p]:text-base [&_p]:leading-loose
|
||||
[&_strong]:text-slate-700 [&_ul]:list-disc [&_ul]:pl-6 [&_li]:text-slate-500 [&_li]:leading-loose
|
||||
[&_a]:text-violet-600 [&_hr]:border-slate-200 [&_hr]:my-6">
|
||||
<Fragment set:html={content} />
|
||||
</div>
|
||||
|
||||
<!-- Side card -->
|
||||
{info && (
|
||||
<div class="reveal-right w-full md:w-[320px] bg-slate-50 border border-slate-200 rounded-2xl p-8 flex flex-col gap-6 shrink-0 self-start sticky top-28">
|
||||
<h3 class="text-slate-900 text-xl font-bold">
|
||||
{locale === 'en' ? 'Document Info' : '文档信息'}
|
||||
</h3>
|
||||
<div class="h-px bg-slate-200"></div>
|
||||
|
||||
<div>
|
||||
<p class="text-slate-400 text-sm font-semibold">{locale === 'en' ? 'Type' : '文档类型'}</p>
|
||||
<p class="text-slate-600 text-[15px]">{info.type}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-slate-400 text-sm font-semibold">{locale === 'en' ? 'Last Updated' : '最后更新'}</p>
|
||||
<p class="text-slate-600 text-[15px]">{info.date}</p>
|
||||
</div>
|
||||
{info.law && (
|
||||
<div>
|
||||
<p class="text-slate-400 text-sm font-semibold">{locale === 'en' ? 'Governing Law' : '适用法律'}</p>
|
||||
<p class="text-slate-600 text-[15px]">{info.law}</p>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<p class="text-slate-400 text-sm font-semibold">{locale === 'en' ? 'Contact' : '联系邮箱'}</p>
|
||||
<p class="text-slate-600 text-[15px]">{info.email}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Warning -->
|
||||
<section class="w-full bg-amber-50 py-20 px-6 md:px-20">
|
||||
<div class="reveal max-w-[800px] mx-auto text-center flex flex-col gap-5">
|
||||
<h3 class="text-amber-600 text-2xl font-bold">{about.warningTitle}</h3>
|
||||
<p class="text-amber-700 text-[15px] leading-loose">{about.warningBody}</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Legal Links -->
|
||||
<section class="w-full bg-slate-50 py-12 px-6 md:px-20">
|
||||
<div class="reveal max-w-[600px] mx-auto text-center flex flex-col gap-5">
|
||||
<h3 class="text-slate-900 text-xl font-bold">{about.legalTitle}</h3>
|
||||
<div class="flex justify-center gap-8">
|
||||
<a href={localePath(locale, '/privacy')} class={`text-[15px] hover:underline ${isActive('privacy_policy') ? 'text-violet-600 font-semibold' : 'text-slate-500'}`}>{footer.col3Link1}</a>
|
||||
<a href={localePath(locale, '/terms')} class={`text-[15px] hover:underline ${isActive('terms_of_service') ? 'text-violet-600 font-semibold' : 'text-slate-500'}`}>{footer.col3Link2}</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
import { t, localePath, getLocaleLabel, type Locale } from '../i18n/utils';
|
||||
|
||||
interface Props {
|
||||
locale: Locale;
|
||||
}
|
||||
|
||||
const { locale } = Astro.props;
|
||||
const currentPath = Astro.url.pathname.replace(new RegExp(`^/(zh|zh_Hant|en)`), '');
|
||||
const nav = t(locale, 'nav');
|
||||
const footer = t(locale, 'footer');
|
||||
const otherLocales: Locale[] = (['zh', 'zh_Hant', 'en'] as Locale[]).filter((l) => l !== locale);
|
||||
---
|
||||
|
||||
<header class="w-full flex items-center justify-between h-16 md:h-20 px-5 md:px-20 border-b border-slate-200 bg-white sticky top-0 z-50">
|
||||
<a href={localePath(locale, '/')} class="flex items-center gap-2 md:gap-3 shrink-0">
|
||||
<img src="/images/logo.png" alt="MeiYao" class="w-8 h-8 md:w-9 md:h-9" />
|
||||
<span class="text-slate-900 text-lg md:text-xl font-bold whitespace-nowrap">{footer.brandName}</span>
|
||||
</a>
|
||||
|
||||
<nav class="flex items-center gap-4 md:gap-8">
|
||||
<a href={localePath(locale, '/features')} class="hidden sm:block text-slate-600 text-sm hover:text-slate-900 whitespace-nowrap">{nav.features}</a>
|
||||
<a href={localePath(locale, '/pricing')} class="hidden sm:block text-slate-600 text-sm hover:text-slate-900 whitespace-nowrap">{nav.pricing}</a>
|
||||
<a href={localePath(locale, '/about')} class="hidden sm:block text-slate-600 text-sm hover:text-slate-900 whitespace-nowrap">{nav.about}</a>
|
||||
|
||||
<div class="relative group">
|
||||
<button class="flex items-center gap-1 px-2 py-1.5 text-xs text-slate-600 rounded-lg border border-slate-200 bg-white hover:bg-slate-50 whitespace-nowrap">
|
||||
{getLocaleLabel(locale)}
|
||||
<svg class="w-3 h-3 text-slate-400" viewBox="0 0 12 12" fill="none"><path d="M3 5l3 3 3-3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
</button>
|
||||
<div class="absolute right-0 top-full mt-1 bg-white border border-slate-200 rounded-lg shadow-lg py-1 opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all z-50">
|
||||
{otherLocales.map((l) => (
|
||||
<a href={localePath(l, currentPath)} class="block px-4 py-2 text-sm text-slate-600 hover:bg-slate-50 hover:text-slate-900 whitespace-nowrap">
|
||||
{getLocaleLabel(l)}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a href={localePath(locale, '/login')} class="bg-violet-600 text-white text-xs md:text-sm font-semibold px-4 md:px-5 py-2 md:py-2.5 rounded-lg hover:bg-violet-700 transition-colors whitespace-nowrap">
|
||||
{nav.getStarted}
|
||||
</a>
|
||||
</nav>
|
||||
</header>
|
||||
@@ -0,0 +1,47 @@
|
||||
---
|
||||
import { t, type Locale } from '../i18n/utils';
|
||||
|
||||
interface Props {
|
||||
locale: Locale;
|
||||
}
|
||||
|
||||
const { locale } = Astro.props;
|
||||
const p = t(locale, 'pricing');
|
||||
|
||||
const plans = [
|
||||
{ name: p.p1Name, badge: p.p1Badge, price: p.p1Price, credits: p.p1Credits, desc: p.p1Desc, detail: '', featured: false },
|
||||
{ name: p.p2Name, badge: '', price: p.p2Price, credits: p.p2Credits, desc: p.p2Desc, detail: p.p2Detail, featured: false },
|
||||
{ name: p.p3Name, badge: p.p3Badge, price: p.p3Price, credits: p.p3Credits, desc: p.p3Desc, detail: '', featured: true },
|
||||
{ name: p.p4Name, badge: '', price: p.p4Price, credits: p.p4Credits, desc: p.p4Desc, detail: p.p4Detail, featured: false },
|
||||
];
|
||||
---
|
||||
|
||||
<!-- Header -->
|
||||
<section class="w-full py-24 md:py-32 relative overflow-hidden">
|
||||
<div class="glow-bg absolute inset-0 pointer-events-none"></div>
|
||||
<div class="relative text-center px-6 max-w-3xl mx-auto">
|
||||
<h1 class="reveal stagger-1 text-slate-900 text-3xl md:text-4xl lg:text-5xl font-bold mb-4">{p.title}</h1>
|
||||
<p class="reveal stagger-2 text-slate-500 text-lg max-w-xl mx-auto">{p.subtitle}</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Pricing Cards -->
|
||||
<section class="w-full py-16 md:py-24 px-6 md:px-20" style="background-color: #F8F7FC;">
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
{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'}`}>
|
||||
<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>}
|
||||
<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>
|
||||
<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>
|
||||
{plan.detail && <p class="text-slate-600 text-sm leading-relaxed mt-2">{plan.detail}</p>}
|
||||
<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>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,76 @@
|
||||
---
|
||||
import { t, type Locale } from '../i18n/utils';
|
||||
|
||||
interface Props {
|
||||
locale: Locale;
|
||||
}
|
||||
|
||||
const { locale } = Astro.props;
|
||||
const showcase = t(locale, 'showcase');
|
||||
const features = t(locale, 'features');
|
||||
---
|
||||
|
||||
<!-- Features Grid Section -->
|
||||
<section class="w-full py-24 md:py-32" style="background-color: #F8F7FC;">
|
||||
<div class="max-w-7xl mx-auto px-6">
|
||||
<div class="reveal text-center mb-16">
|
||||
<p class="text-xs font-medium tracking-[0.15em] text-violet-600 uppercase mb-4">
|
||||
{showcase.title}
|
||||
</p>
|
||||
<h2 class="text-slate-900 text-3xl md:text-4xl font-bold mb-4">
|
||||
{features.c1Title}
|
||||
</h2>
|
||||
<p class="text-slate-500 text-lg max-w-xl mx-auto">{showcase.desc}</p>
|
||||
</div>
|
||||
|
||||
<div class="reveal stagger-1 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<div class="feature-card bg-white rounded-xl p-8 border border-violet-100">
|
||||
<div class="w-12 h-12 rounded-lg flex items-center justify-center mb-5 bg-violet-50">
|
||||
<svg class="w-6 h-6 text-violet-600" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2a10 10 0 100 20 10 10 0 000-20z"/><path d="M2 12h20"/></svg>
|
||||
</div>
|
||||
<h3 class="font-semibold text-lg mb-3 text-slate-900">{features.c1Title}</h3>
|
||||
<p class="text-sm leading-relaxed text-slate-500">{features.c1Desc}</p>
|
||||
</div>
|
||||
|
||||
<div class="feature-card bg-white rounded-xl p-8 border border-violet-100">
|
||||
<div class="w-12 h-12 rounded-lg flex items-center justify-center mb-5 bg-violet-50">
|
||||
<svg class="w-6 h-6 text-violet-600" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/></svg>
|
||||
</div>
|
||||
<h3 class="font-semibold text-lg mb-3 text-slate-900">{features.c2Title}</h3>
|
||||
<p class="text-sm leading-relaxed text-slate-500">{features.c2Desc}</p>
|
||||
</div>
|
||||
|
||||
<div class="feature-card bg-white rounded-xl p-8 border border-violet-100">
|
||||
<div class="w-12 h-12 rounded-lg flex items-center justify-center mb-5 bg-violet-50">
|
||||
<svg class="w-6 h-6 text-violet-600" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg>
|
||||
</div>
|
||||
<h3 class="font-semibold text-lg mb-3 text-slate-900">{features.c3Title}</h3>
|
||||
<p class="text-sm leading-relaxed text-slate-500">{features.c3Desc}</p>
|
||||
</div>
|
||||
|
||||
<div class="feature-card bg-white rounded-xl p-8 border border-violet-100">
|
||||
<div class="w-12 h-12 rounded-lg flex items-center justify-center mb-5 bg-violet-50">
|
||||
<svg class="w-6 h-6 text-violet-600" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/></svg>
|
||||
</div>
|
||||
<h3 class="font-semibold text-lg mb-3 text-slate-900">{features.c4Title}</h3>
|
||||
<p class="text-sm leading-relaxed text-slate-500">{features.c4Desc}</p>
|
||||
</div>
|
||||
|
||||
<div class="feature-card bg-white rounded-xl p-8 border border-violet-100">
|
||||
<div class="w-12 h-12 rounded-lg flex items-center justify-center mb-5 bg-violet-50">
|
||||
<svg class="w-6 h-6 text-violet-600" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
|
||||
</div>
|
||||
<h3 class="font-semibold text-lg mb-3 text-slate-900">{features.c5Title}</h3>
|
||||
<p class="text-sm leading-relaxed text-slate-500">{features.c5Desc}</p>
|
||||
</div>
|
||||
|
||||
<div class="feature-card bg-white rounded-xl p-8 border border-violet-100">
|
||||
<div class="w-12 h-12 rounded-lg flex items-center justify-center mb-5 bg-violet-50">
|
||||
<svg class="w-6 h-6 text-violet-600" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
|
||||
</div>
|
||||
<h3 class="font-semibold text-lg mb-3 text-slate-900">{features.c6Title}</h3>
|
||||
<p class="text-sm leading-relaxed text-slate-500">{features.c6Desc}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
import { t, type Locale } from '../i18n/utils';
|
||||
|
||||
interface Props {
|
||||
locale: Locale;
|
||||
}
|
||||
|
||||
const { locale } = Astro.props;
|
||||
const test = t(locale, 'testimonials');
|
||||
---
|
||||
|
||||
<section class="w-full py-24 md:py-32 px-6 md:px-20 bg-slate-900">
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<h2 class="reveal text-white text-3xl md:text-[40px] font-bold text-center mb-12">
|
||||
{test.title}
|
||||
</h2>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div class="reveal stagger-1 bg-slate-800 rounded-2xl p-8 flex flex-col justify-between min-h-[200px]">
|
||||
<p class="text-slate-300 text-base leading-relaxed">"{test.t1Quote}"</p>
|
||||
<div class="flex items-center gap-3 mt-6">
|
||||
<div class="w-10 h-10 bg-indigo-500 rounded-full shrink-0"></div>
|
||||
<span class="text-white text-sm font-medium">{test.t1Name}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="reveal stagger-2 bg-slate-800 rounded-2xl p-8 flex flex-col justify-between min-h-[200px]">
|
||||
<p class="text-slate-300 text-base leading-relaxed">"{test.t2Quote}"</p>
|
||||
<div class="flex items-center gap-3 mt-6">
|
||||
<div class="w-10 h-10 bg-violet-500 rounded-full shrink-0"></div>
|
||||
<span class="text-white text-sm font-medium">{test.t2Name}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="reveal stagger-3 bg-slate-800 rounded-2xl p-8 flex flex-col justify-between min-h-[200px]">
|
||||
<p class="text-slate-300 text-base leading-relaxed">"{test.t3Quote}"</p>
|
||||
<div class="flex items-center gap-3 mt-6">
|
||||
<div class="w-10 h-10 bg-cyan-500 rounded-full shrink-0"></div>
|
||||
<span class="text-white text-sm font-medium">{test.t3Name}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,74 @@
|
||||
export type Locale = 'zh' | 'zh_Hant' | 'en';
|
||||
export const defaultLocale: Locale = 'zh';
|
||||
export const locales: Locale[] = ['zh', 'zh_Hant', 'en'];
|
||||
|
||||
export function isValidLocale(locale: string): locale is Locale {
|
||||
return (locales as readonly string[]).includes(locale);
|
||||
}
|
||||
|
||||
export function getLocaleFromUrl(url: URL): Locale {
|
||||
const [, locale] = url.pathname.split('/');
|
||||
if (isValidLocale(locale)) return locale;
|
||||
return defaultLocale;
|
||||
}
|
||||
|
||||
export function getLocaleLabel(locale: Locale): string {
|
||||
const labels: Record<Locale, string> = { zh: '简体中文', zh_Hant: '繁體中文', en: 'English' };
|
||||
return labels[locale];
|
||||
}
|
||||
|
||||
export interface Translations {
|
||||
nav: { features: string; pricing: string; about: string; getStarted: string };
|
||||
hero: { badge: string; headline: string; subtext: string; primaryCta: string; secondaryCta: string; trust: string };
|
||||
showcase: { title: string; desc: string; feature1Title: string; feature1Desc: string };
|
||||
testimonials: { title: string; t1Quote: string; t1Name: string; t2Quote: string; t2Name: string; t3Quote: string; t3Name: string };
|
||||
cta: { title: string; subtitle: string; button: string };
|
||||
footer: { brandName: string; desc: string; col1Title: string; col1Link1: string; col1Link2: string; col2Title: string; col2Link1: string; col2Link2: string; col3Title: string; col3Link1: string; col3Link2: string };
|
||||
features: { title: string; subtitle: string; tagline: string; c1Title: string; c1Desc: string; c2Title: string; c2Desc: string; c3Title: string; c3Desc: string; c4Title: string; c4Desc: string; c5Title: string; c5Desc: string; c6Title: string; c6Desc: string };
|
||||
pricing: { title: string; subtitle: string; p1Name: string; p1Badge: string; p1Price: string; p1Credits: string; p1Desc: string; p2Name: string; p2Price: string; p2Credits: string; p2Desc: string; p2Detail: string; p3Name: string; p3Badge: string; p3Price: string; p3Credits: string; p3Desc: string; p4Name: string; p4Price: string; p4Credits: string; p4Desc: string; p4Detail: string; buyNow: string };
|
||||
about: { title: string; subtitle: string; storyTitle: string; p1: string; p2: string; p3: string; companyInfo: string; company: string; emailLabel: string; email: string; devLabel: string; developer: string; icpLabel: string; icp: string; warningTitle: string; warningBody: string; legalTitle: string };
|
||||
}
|
||||
|
||||
const translations: Record<Locale, Translations> = {
|
||||
zh: {
|
||||
nav: { features: '功能', pricing: '定价', about: '关于', getStarted: '开始使用' },
|
||||
hero: { badge: '传承千年的东方智慧', headline: '以易经之名 寻心中所惑', subtext: '每一次签问,都是与自己的对话。觅爻将古老易经智慧与现代体验结合,让你在宁静中找到属于此刻的指引。', primaryCta: '免费开始签问', secondaryCta: '了解更多', trust: '已为 10,000+ 用户提供签问服务' },
|
||||
showcase: { title: '仪式感的签问体验', desc: '不同于简单的随机算法,觅爻在每一次签问中融入易经的哲学思考。静心、默念、抽取三步完成,却是一次内心的沉淀之旅。', feature1Title: '64卦精解', feature1Desc: '每一卦配有详细爻辞与今译' },
|
||||
testimonials: { title: '用户心声', t1Quote: '在最迷茫的时候,觅爻给了我一个方向。不管结果如何,那种静下心来的过程本身就很有帮助。', t1Name: '林小姐 · 产品经理', t2Quote: '界面很清爽,没有乱七八糟的广告。每次签问都像是一次心灵的短暂旅行。', t2Name: '张先生 · 创业者', t3Quote: '我是一个程序员,原本不信这些。但试了几次后发现,这种随机性反而让我看到平时忽略的可能性。', t3Name: '王先生 · 软件工程师' },
|
||||
cta: { title: '开始你的第一次签问', subtitle: '无需注册,立即体验。让古老的智慧,为现代的你指引方向。', button: '免费开始 →' },
|
||||
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: '提供灵活积分套餐:新人专享、入门补充、常用加量、高频进阶。按需购买,自由使用。' },
|
||||
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: '立即购买' },
|
||||
about: { title: '关于觅爻签问', subtitle: '了解我们的理念与定位', storyTitle: '我们的故事', p1: '觅爻签问是一个借助于 AI 解读传统六爻卦象的平台,为用户了解中国传统易学文化提供一个窗口。六爻卦象源于《周易》深邃的哲学体系,是古人探索世界运行规律的一种独特方法。古人认为宇宙万物相互关联,在你起卦时,你的心念与时空信息会凝结成卦象的方式呈现出来。', p2: '觅爻签问基于这样的思路,帮助你跳出局限思维,从全局和演变趋势看清矛盾、机会与风险,为判断和行动提供参考。', p3: '用 AI 解锁古老智慧,让觅爻签问成为你探索趋势、明晰方向的现代助手。', companyInfo: '公司信息', company: '洵觅科技(深圳)有限公司', emailLabel: '联系邮箱', email: 'xuyunlong@xunmee.com', devLabel: '开发者', developer: 'Ann Lee', icpLabel: '备案号', icp: '粤ICP备2025428416号-1A', warningTitle: '特别提醒', warningBody: '卦象解读结果均由 AI 生成,仅供娱乐参考,切不可作为商业、医疗等专业领域的决策依据。理性看待卦象,自由掌握人生。', legalTitle: '法律条款' },
|
||||
},
|
||||
zh_Hant: {
|
||||
nav: { features: '功能', pricing: '定價', about: '關於', getStarted: '開始使用' },
|
||||
hero: { badge: '傳承千年的東方智慧', headline: '以易經之名 尋心中所惑', subtext: '每一次簽問,都是與自己的對話。覓爻將古老易經智慧與現代體驗結合,讓你在寧靜中找到屬於此刻的指引。', primaryCta: '免費開始簽問', secondaryCta: '瞭解更多', trust: '已為 10,000+ 用戶提供簽問服務' },
|
||||
showcase: { title: '儀式感的簽問體驗', desc: '不同於簡單的隨機算法,覓爻在每一次簽問中融入易經的哲學思考。靜心、默念、抽取三步完成,卻是一次內心的沉澱之旅。', feature1Title: '64卦精解', feature1Desc: '每一卦配有詳細爻辭與今譯' },
|
||||
testimonials: { title: '用戶心聲', t1Quote: '在最迷茫的時候,覓爻給了我一個方向。不管結果如何,那種靜下心來的過程本身就很有幫助。', t1Name: '林小姐 · 產品經理', t2Quote: '界面很清爽,沒有亂七八糟的廣告。每次簽問都像是一次心靈的短暫旅行。', t2Name: '張先生 · 創業者', t3Quote: '我是一個程序員,原本不信這些。但試了幾次後發現,這種隨機性反而讓我看到平時忽略的可能性。', t3Name: '王先生 · 軟件工程師' },
|
||||
cta: { title: '開始你的第一次簽問', subtitle: '無需註冊,立即體驗。讓古老的智慧,為現代的你指引方向。', button: '免費開始 →' },
|
||||
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: '提供靈活積分套餐:新人專享、入門補充、常用加量、高頻進階。按需購買,自由使用。' },
|
||||
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: '立即購買' },
|
||||
about: { title: '關於覓爻簽問', subtitle: '了解我們的理念與定位', storyTitle: '我們的故事', p1: '覓爻簽問是一個借助於 AI 解讀傳統六爻卦象的平台,為用戶了解中國傳統易學文化提供一個窗口。六爻卦象源於《周易》深邃的哲學體系,是古人探索世界運行規律的一種獨特方法。古人認為宇宙萬物相互關聯,在你起卦時,你的心念與時空信息會凝結成卦象的方式呈現出來。', p2: '覓爻簽問基於這樣的思路,幫助你跳出局限思維,從全局和演變趨勢看清矛盾、機會與風險,為判斷和行動提供參考。', p3: '用 AI 解鎖古老智慧,讓覓爻簽問成為你探索趨勢、明晰方向的現代助手。', companyInfo: '公司信息', company: '洵覓科技(深圳)有限公司', emailLabel: '聯繫郵箱', email: 'xuyunlong@xunmee.com', devLabel: '開發者', developer: 'Ann Lee', icpLabel: '備案號', icp: '粵ICP備2025428416號-1A', warningTitle: '特別提醒', warningBody: '卦象解讀結果均由 AI 生成,僅供娛樂參考,切不可作為商業、醫療等專業領域的決策依據。理性看待卦象,自由掌握人生。', legalTitle: '法律條款' },
|
||||
},
|
||||
en: {
|
||||
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' },
|
||||
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' },
|
||||
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' },
|
||||
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' },
|
||||
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.' },
|
||||
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' },
|
||||
about: { title: 'About MeiYao Divination', subtitle: 'Our vision and philosophy', storyTitle: 'Our Story', p1: 'MeiYao Divination is an AI-assisted platform for interpreting traditional Six-Line divination and opening a window into classical Chinese wisdom. Six-Line divination originates from the deep philosophical system of the I Ching.', p2: 'MeiYao Divination helps you step outside narrow thinking, understand contradictions, opportunities, and risks from a broader trend perspective, and make calmer, more thoughtful decisions.', p3: 'Unlock ancient wisdom with AI. Let MeiYao Divination be your modern companion for exploring trends and finding direction.', companyInfo: 'Company', company: 'Xunmee Technology (Shenzhen) Co., Ltd.', emailLabel: 'Email', email: 'xuyunlong@xunmee.com', devLabel: 'Developer', developer: 'Ann Lee', icpLabel: 'ICP License', icp: 'Yue ICP 2025428416-1A', warningTitle: 'Important Notice', warningBody: 'All divination interpretations are AI-generated for entertainment only. Do not use them as professional advice for business, medical, or legal decisions.', legalTitle: 'Legal' },
|
||||
},
|
||||
};
|
||||
|
||||
export function t<K extends keyof Translations>(locale: Locale, section: K): Translations[K] {
|
||||
return translations[locale][section];
|
||||
}
|
||||
|
||||
export function localePath(locale: Locale, path: string): string {
|
||||
return `/${locale}${path}`;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
import Navbar from '../components/Navbar.astro';
|
||||
import Footer from '../components/Footer.astro';
|
||||
import '../styles/global.css';
|
||||
import '../styles/animations.css';
|
||||
|
||||
interface Props {
|
||||
locale: import('../i18n/utils').Locale;
|
||||
}
|
||||
|
||||
const { locale } = Astro.props;
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html lang={locale}>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
</head>
|
||||
<body class="bg-white text-slate-900 antialiased">
|
||||
<Navbar locale={locale} />
|
||||
<main>
|
||||
<slot />
|
||||
</main>
|
||||
<Footer locale={locale} />
|
||||
<script>
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.classList.add('visible');
|
||||
observer.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
},
|
||||
{ rootMargin: '0px 0px -60px 0px', threshold: 0.1 }
|
||||
);
|
||||
document.querySelectorAll('.reveal, .reveal-left, .reveal-right, .reveal-scale').forEach((el) => {
|
||||
observer.observe(el);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
import MarketingLayout from '../../layouts/Marketing.astro';
|
||||
import AboutPage from '../../components/AboutPage.astro';
|
||||
const locale = 'en' as const;
|
||||
---
|
||||
<MarketingLayout locale={locale}>
|
||||
<AboutPage locale={locale} />
|
||||
</MarketingLayout>
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
import MarketingLayout from '../../layouts/Marketing.astro';
|
||||
import FeaturesPage from '../../components/FeaturesPage.astro';
|
||||
const locale = 'en' as const;
|
||||
---
|
||||
<MarketingLayout locale={locale}>
|
||||
<FeaturesPage locale={locale} />
|
||||
</MarketingLayout>
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
import MarketingLayout from '../../layouts/Marketing.astro';
|
||||
import Hero from '../../components/Hero.astro';
|
||||
import Showcase from '../../components/Showcase.astro';
|
||||
import CtaSection from '../../components/CtaSection.astro';
|
||||
|
||||
const locale = 'en' as const;
|
||||
---
|
||||
<MarketingLayout locale={locale}>
|
||||
<Hero locale={locale} />
|
||||
<Showcase locale={locale} />
|
||||
<CtaSection locale={locale} />
|
||||
</MarketingLayout>
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
import MarketingLayout from '../../layouts/Marketing.astro';
|
||||
import PricingPage from '../../components/PricingPage.astro';
|
||||
const locale = 'en' as const;
|
||||
---
|
||||
<MarketingLayout locale={locale}>
|
||||
<PricingPage locale={locale} />
|
||||
</MarketingLayout>
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
import MarketingLayout from '../../layouts/Marketing.astro';
|
||||
import LegalPage from '../../components/LegalPage.astro';
|
||||
const locale = 'en' as const;
|
||||
---
|
||||
<MarketingLayout locale={locale}>
|
||||
<LegalPage locale={locale} docType="privacy_policy" />
|
||||
</MarketingLayout>
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
import MarketingLayout from '../../layouts/Marketing.astro';
|
||||
import LegalPage from '../../components/LegalPage.astro';
|
||||
const locale = 'en' as const;
|
||||
---
|
||||
<MarketingLayout locale={locale}>
|
||||
<LegalPage locale={locale} docType="terms_of_service" />
|
||||
</MarketingLayout>
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
return Astro.redirect('/zh/');
|
||||
---
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
import MarketingLayout from '../../layouts/Marketing.astro';
|
||||
import AboutPage from '../../components/AboutPage.astro';
|
||||
const locale = 'zh' as const;
|
||||
---
|
||||
<MarketingLayout locale={locale}>
|
||||
<AboutPage locale={locale} />
|
||||
</MarketingLayout>
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
import MarketingLayout from '../../layouts/Marketing.astro';
|
||||
import FeaturesPage from '../../components/FeaturesPage.astro';
|
||||
const locale = 'zh' as const;
|
||||
---
|
||||
<MarketingLayout locale={locale}>
|
||||
<FeaturesPage locale={locale} />
|
||||
</MarketingLayout>
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
import MarketingLayout from '../../layouts/Marketing.astro';
|
||||
import Hero from '../../components/Hero.astro';
|
||||
import Showcase from '../../components/Showcase.astro';
|
||||
import CtaSection from '../../components/CtaSection.astro';
|
||||
|
||||
const locale = 'zh' as const;
|
||||
---
|
||||
<MarketingLayout locale={locale}>
|
||||
<Hero locale={locale} />
|
||||
<Showcase locale={locale} />
|
||||
<CtaSection locale={locale} />
|
||||
</MarketingLayout>
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
import MarketingLayout from '../../layouts/Marketing.astro';
|
||||
import PricingPage from '../../components/PricingPage.astro';
|
||||
const locale = 'zh' as const;
|
||||
---
|
||||
<MarketingLayout locale={locale}>
|
||||
<PricingPage locale={locale} />
|
||||
</MarketingLayout>
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
import MarketingLayout from '../../layouts/Marketing.astro';
|
||||
import LegalPage from '../../components/LegalPage.astro';
|
||||
const locale = 'zh' as const;
|
||||
---
|
||||
<MarketingLayout locale={locale}>
|
||||
<LegalPage locale={locale} docType="privacy_policy" />
|
||||
</MarketingLayout>
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
import MarketingLayout from '../../layouts/Marketing.astro';
|
||||
import LegalPage from '../../components/LegalPage.astro';
|
||||
const locale = 'zh' as const;
|
||||
---
|
||||
<MarketingLayout locale={locale}>
|
||||
<LegalPage locale={locale} docType="terms_of_service" />
|
||||
</MarketingLayout>
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
import MarketingLayout from '../../layouts/Marketing.astro';
|
||||
import AboutPage from '../../components/AboutPage.astro';
|
||||
const locale = 'zh_Hant' as const;
|
||||
---
|
||||
<MarketingLayout locale={locale}>
|
||||
<AboutPage locale={locale} />
|
||||
</MarketingLayout>
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
import MarketingLayout from '../../layouts/Marketing.astro';
|
||||
import FeaturesPage from '../../components/FeaturesPage.astro';
|
||||
const locale = 'zh_Hant' as const;
|
||||
---
|
||||
<MarketingLayout locale={locale}>
|
||||
<FeaturesPage locale={locale} />
|
||||
</MarketingLayout>
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
import MarketingLayout from '../../layouts/Marketing.astro';
|
||||
import Hero from '../../components/Hero.astro';
|
||||
import Showcase from '../../components/Showcase.astro';
|
||||
import CtaSection from '../../components/CtaSection.astro';
|
||||
|
||||
const locale = 'zh_Hant' as const;
|
||||
---
|
||||
<MarketingLayout locale={locale}>
|
||||
<Hero locale={locale} />
|
||||
<Showcase locale={locale} />
|
||||
<CtaSection locale={locale} />
|
||||
</MarketingLayout>
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
import MarketingLayout from '../../layouts/Marketing.astro';
|
||||
import PricingPage from '../../components/PricingPage.astro';
|
||||
const locale = 'zh_Hant' as const;
|
||||
---
|
||||
<MarketingLayout locale={locale}>
|
||||
<PricingPage locale={locale} />
|
||||
</MarketingLayout>
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
import MarketingLayout from '../../layouts/Marketing.astro';
|
||||
import LegalPage from '../../components/LegalPage.astro';
|
||||
const locale = 'zh_Hant' as const;
|
||||
---
|
||||
<MarketingLayout locale={locale}>
|
||||
<LegalPage locale={locale} docType="privacy_policy" />
|
||||
</MarketingLayout>
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
import MarketingLayout from '../../layouts/Marketing.astro';
|
||||
import LegalPage from '../../components/LegalPage.astro';
|
||||
const locale = 'zh_Hant' as const;
|
||||
---
|
||||
<MarketingLayout locale={locale}>
|
||||
<LegalPage locale={locale} docType="terms_of_service" />
|
||||
</MarketingLayout>
|
||||
@@ -0,0 +1,168 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
@keyframes fade-in-up {
|
||||
from { opacity: 0; transform: translateY(24px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes slide-in-left {
|
||||
from { opacity: 0; transform: translateX(-40px); }
|
||||
to { opacity: 1; transform: translateX(0); }
|
||||
}
|
||||
|
||||
@keyframes slide-in-right {
|
||||
from { opacity: 0; transform: translateX(40px); }
|
||||
to { opacity: 1; transform: translateX(0); }
|
||||
}
|
||||
|
||||
@keyframes scale-in {
|
||||
from { opacity: 0; transform: scale(0.95); }
|
||||
to { opacity: 1; transform: scale(1); }
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0%, 100% { transform: translateY(0px); }
|
||||
50% { transform: translateY(-10px); }
|
||||
}
|
||||
|
||||
@keyframes glow-pulse {
|
||||
0%, 100% { opacity: 0.12; }
|
||||
50% { opacity: 0.2; }
|
||||
}
|
||||
|
||||
@keyframes particle-drift {
|
||||
0% { transform: translate(0, 0); }
|
||||
25% { transform: translate(10px, -20px); }
|
||||
50% { transform: translate(-5px, -40px); }
|
||||
75% { transform: translate(15px, -20px); }
|
||||
100% { transform: translate(0, 0); }
|
||||
}
|
||||
|
||||
/* Reveal animations */
|
||||
.reveal { opacity: 0; will-change: transform, opacity; }
|
||||
.reveal.visible { animation: fade-in-up 0.6s cubic-bezier(0.22, 1, 0.36, 1) forwards; }
|
||||
|
||||
.reveal-left { opacity: 0; will-change: transform, opacity; }
|
||||
.reveal-left.visible { animation: slide-in-left 0.7s cubic-bezier(0.22, 1, 0.36, 1) forwards; }
|
||||
|
||||
.reveal-right { opacity: 0; will-change: transform, opacity; }
|
||||
.reveal-right.visible { animation: slide-in-right 0.7s cubic-bezier(0.22, 1, 0.36, 1) forwards; }
|
||||
|
||||
.reveal-scale { opacity: 0; will-change: transform, opacity; }
|
||||
.reveal-scale.visible { animation: scale-in 0.6s cubic-bezier(0.22, 1, 0.36, 1) forwards; }
|
||||
|
||||
.reveal.visible.stagger-1 { animation-delay: 0.1s; }
|
||||
.reveal.visible.stagger-2 { animation-delay: 0.2s; }
|
||||
.reveal.visible.stagger-3 { animation-delay: 0.3s; }
|
||||
.reveal.visible.stagger-4 { animation-delay: 0.4s; }
|
||||
.reveal.visible.stagger-5 { animation-delay: 0.5s; }
|
||||
.reveal.visible.stagger-6 { animation-delay: 0.6s; }
|
||||
|
||||
/* Cyber gradient */
|
||||
.cyber-gradient {
|
||||
background: linear-gradient(135deg, #8B5CF6 0%, #A78BFA 50%, #C084FC 100%);
|
||||
}
|
||||
|
||||
.cyber-glow {
|
||||
box-shadow: 0 0 20px rgba(139, 92, 246, 0.35), 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* Purple glow backgrounds */
|
||||
.glow-bg {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.glow-bg::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -10%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 800px;
|
||||
height: 600px;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(ellipse at center, rgba(139, 92, 246, 0.15) 0%, transparent 70%);
|
||||
pointer-events: none;
|
||||
animation: glow-pulse 6s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.glow-bg-wide::before {
|
||||
width: 1200px;
|
||||
height: 800px;
|
||||
}
|
||||
|
||||
/* Floating particles (pure CSS) */
|
||||
.particle {
|
||||
position: absolute;
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
border-radius: 50%;
|
||||
background: rgba(139, 92, 246, 0.4);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.particle:nth-child(1) { top: 20%; left: 15%; animation: particle-drift 8s ease-in-out infinite; }
|
||||
.particle:nth-child(2) { top: 40%; left: 75%; animation: particle-drift 12s ease-in-out infinite 1s; width: 3px; height: 3px; }
|
||||
.particle:nth-child(3) { top: 60%; left: 30%; animation: particle-drift 10s ease-in-out infinite 2s; width: 5px; height: 5px; }
|
||||
.particle:nth-child(4) { top: 30%; left: 55%; animation: particle-drift 9s ease-in-out infinite 3s; }
|
||||
.particle:nth-child(5) { top: 70%; left: 85%; animation: particle-drift 11s ease-in-out infinite 4s; width: 3px; height: 3px; }
|
||||
.particle:nth-child(6) { top: 50%; left: 10%; animation: particle-drift 7s ease-in-out infinite 5s; width: 2px; height: 2px; }
|
||||
.particle:nth-child(7) { top: 15%; left: 65%; animation: particle-drift 13s ease-in-out infinite 2s; }
|
||||
.particle:nth-child(8) { top: 80%; left: 45%; animation: particle-drift 8s ease-in-out infinite 6s; width: 3px; height: 3px; }
|
||||
|
||||
/* Connection lines between particles */
|
||||
.particle-lines {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
background-image:
|
||||
radial-gradient(circle at 15% 20%, rgba(139, 92, 246, 0.06) 0%, transparent 40%),
|
||||
radial-gradient(circle at 75% 40%, rgba(139, 92, 246, 0.04) 0%, transparent 35%),
|
||||
radial-gradient(circle at 30% 60%, rgba(139, 92, 246, 0.05) 0%, transparent 38%);
|
||||
}
|
||||
|
||||
/* Hexagram lines */
|
||||
.hex-yang {
|
||||
width: 9rem;
|
||||
height: 6px;
|
||||
border-radius: 9999px;
|
||||
background: linear-gradient(135deg, #8B5CF6, #C084FC);
|
||||
}
|
||||
|
||||
.hex-yin {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
.hex-yin > div {
|
||||
width: 3.5rem;
|
||||
height: 6px;
|
||||
border-radius: 9999px;
|
||||
background: rgba(139, 92, 246, 0.4);
|
||||
}
|
||||
|
||||
/* Feature card hover */
|
||||
.feature-card {
|
||||
transition: all 0.5s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
.feature-card:hover {
|
||||
transform: translateY(-4px);
|
||||
border-color: rgba(139, 92, 246, 0.25);
|
||||
box-shadow: 0 8px 30px rgba(139, 92, 246, 0.1);
|
||||
}
|
||||
|
||||
/* Smooth scroll */
|
||||
html { scroll-behavior: smooth; }
|
||||
|
||||
/* Reduced motion */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.reveal, .reveal-left, .reveal-right, .reveal-scale { opacity: 1; transform: none; animation: none; }
|
||||
.particle { animation: none; }
|
||||
.glow-bg::before { animation: none; }
|
||||
html { scroll-behavior: auto; }
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
@import "tailwindcss";
|
||||
Reference in New Issue
Block a user