feat: redesign pricing section with beta notice and i18n pricing

- 4-card layout: Free / Monthly / Quarterly / Lifetime License
- zh shows RMB ¥, en shows $ with localized prices
- Monthly ¥19.9 (edu ¥12.9 / $1.99), Quarterly ¥49.9 (edu ¥29.9 / $7.99)
- Diagonal corner ribbons (green "限时免费") on Monthly & Quarterly
- Desktop card renamed to "永久授权 / Lifetime License" with Coming Soon ribbon
- Desktop tag badge distinguishes it as a native offline app
- Beta notice banner: all plans free during beta, no payment required
- CTA unified to "免费体验 / Try Free" linking to /app
- Cards use flex-column + align-items:stretch for consistent equal height

Made-with: Cursor
This commit is contained in:
2026-03-27 00:06:39 +08:00
parent a6eb79f530
commit 6e4df89b23
16 changed files with 2300 additions and 389 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@@ -70,144 +70,287 @@ export default function AuthModal({ onClose, mandatory = false }: AuthModalProps
await beginGoogleOAuth();
};
const s = {
overlay: {
position: 'fixed' as const,
inset: 0,
background: 'rgba(31,26,23,0.45)',
backdropFilter: 'blur(4px)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
zIndex: 1000,
padding: '16px',
},
card: {
background: '#FFFDF9',
borderRadius: '24px',
boxShadow: '0 24px 64px rgba(198,134,85,0.18)',
maxWidth: '420px',
width: '100%',
padding: '32px',
fontFamily: "'DM Sans', sans-serif",
border: '1px solid #F1E6D8',
},
header: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '24px',
},
title: {
fontSize: '22px',
fontWeight: 700,
color: '#1F1A17',
fontFamily: "'Lora', serif",
},
closeBtn: {
width: '32px',
height: '32px',
borderRadius: '50%',
border: '1px solid #F1E6D8',
background: 'transparent',
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: '#AA9685',
transition: 'all 0.15s',
},
tabs: {
display: 'grid',
gridTemplateColumns: '1fr 1fr',
gap: '4px',
background: '#F5DDD0',
borderRadius: '14px',
padding: '4px',
marginBottom: '24px',
},
tabActive: {
padding: '8px 12px',
borderRadius: '11px',
border: 'none',
background: '#FFFDF9',
color: '#C8622A',
fontWeight: 600,
fontSize: '14px',
cursor: 'pointer',
fontFamily: "'DM Sans', sans-serif",
boxShadow: '0 1px 4px rgba(200,98,42,0.12)',
transition: 'all 0.15s',
},
tabInactive: {
padding: '8px 12px',
borderRadius: '11px',
border: 'none',
background: 'transparent',
color: '#6F6257',
fontWeight: 500,
fontSize: '14px',
cursor: 'pointer',
fontFamily: "'DM Sans', sans-serif",
transition: 'all 0.15s',
},
fieldGroup: {
marginBottom: '16px',
},
label: {
display: 'block',
fontSize: '13px',
fontWeight: 600,
color: '#6F6257',
marginBottom: '6px',
letterSpacing: '0.02em',
},
input: {
width: '100%',
padding: '10px 14px',
border: '1.5px solid #F1E6D8',
borderRadius: '12px',
fontSize: '15px',
color: '#1F1A17',
background: '#FFFFFF',
outline: 'none',
fontFamily: "'DM Sans', sans-serif",
transition: 'border-color 0.15s',
},
inputError: {
border: '1.5px solid #d97a6a',
},
fieldError: {
marginTop: '4px',
fontSize: '12px',
color: '#c0503c',
},
fieldHint: {
marginTop: '4px',
fontSize: '12px',
color: '#AA9685',
},
errorBox: {
padding: '10px 14px',
background: '#fff0ed',
border: '1px solid #f5c0b0',
color: '#c0503c',
borderRadius: '12px',
fontSize: '13px',
fontWeight: 500,
marginBottom: '16px',
},
submitBtn: {
width: '100%',
height: '48px',
background: '#C8622A',
color: 'white',
border: 'none',
borderRadius: '14px',
fontSize: '15px',
fontWeight: 600,
fontFamily: "'DM Sans', sans-serif",
cursor: 'pointer',
boxShadow: '0 2px 12px rgba(200,98,42,0.28)',
transition: 'all 0.2s',
marginBottom: '16px',
},
divider: {
display: 'flex',
alignItems: 'center',
gap: '12px',
marginBottom: '16px',
},
dividerLine: {
flex: 1,
height: '1px',
background: '#F1E6D8',
},
dividerText: {
fontSize: '12px',
color: '#AA9685',
letterSpacing: '0.05em',
},
googleBtn: {
width: '100%',
height: '48px',
border: '1.5px solid #F1E6D8',
borderRadius: '14px',
background: '#FFFFFF',
color: '#1F1A17',
fontSize: '15px',
fontWeight: 500,
fontFamily: "'DM Sans', sans-serif",
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: '10px',
transition: 'all 0.2s',
},
};
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
<div className="bg-white rounded-xl shadow-xl max-w-md w-full p-6">
<div className="flex justify-between items-center mb-6">
<h2 className="text-2xl font-bold text-gray-900">
<div style={s.overlay}>
<div style={s.card}>
<div style={s.header}>
<h2 style={s.title}>
{mode === 'signup' ? t.auth.signUpTitle : t.auth.signInTitle}
</h2>
{!mandatory && (
<button
type="button"
onClick={onClose}
className="p-2 hover:bg-gray-100 rounded-lg transition-colors"
style={s.closeBtn}
aria-label="close"
disabled={isBusy}
>
<X size={20} />
<X size={15} />
</button>
)}
</div>
<div className="grid grid-cols-2 gap-2 rounded-lg bg-gray-100 p-1 mb-4">
<div style={s.tabs}>
<button
type="button"
onClick={() => {
setMode('signin');
setFieldErrors({});
setLocalError('');
}}
onClick={() => { setMode('signin'); setFieldErrors({}); setLocalError(''); }}
aria-pressed={mode === 'signin'}
disabled={isBusy}
className={`rounded-md px-3 py-2 text-sm font-medium transition-colors ${
mode === 'signin' ? 'bg-white text-gray-900 shadow-sm' : 'text-gray-600 hover:text-gray-900'
}`}
style={mode === 'signin' ? s.tabActive : s.tabInactive}
>
{t.auth.signIn}
</button>
<button
type="button"
onClick={() => {
setMode('signup');
setFieldErrors({});
setLocalError('');
}}
onClick={() => { setMode('signup'); setFieldErrors({}); setLocalError(''); }}
aria-pressed={mode === 'signup'}
disabled={isBusy}
className={`rounded-md px-3 py-2 text-sm font-medium transition-colors ${
mode === 'signup' ? 'bg-white text-gray-900 shadow-sm' : 'text-gray-600 hover:text-gray-900'
}`}
style={mode === 'signup' ? s.tabActive : s.tabInactive}
>
{t.auth.signUp}
</button>
</div>
<form onSubmit={handleSubmit} noValidate className="space-y-4">
<div>
<label htmlFor="auth-email" className="block text-sm font-medium text-gray-700 mb-1">
{t.auth.email}
</label>
<form onSubmit={handleSubmit} noValidate>
<div style={s.fieldGroup}>
<label htmlFor="auth-email" style={s.label}>{t.auth.email}</label>
<input
id="auth-email"
type="email"
value={email}
onChange={(e) => {
setEmail(e.target.value);
if (fieldErrors.email) {
setFieldErrors((prev) => ({ ...prev, email: undefined }));
}
if (fieldErrors.email) setFieldErrors((prev) => ({ ...prev, email: undefined }));
}}
className={`w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent ${
fieldErrors.email ? 'border-red-400' : 'border-gray-300'
}`}
style={fieldErrors.email ? { ...s.input, ...s.inputError } : s.input}
placeholder="your@email.com"
required
disabled={isBusy}
/>
{fieldErrors.email && <p className="mt-1 text-xs text-red-600">{fieldErrors.email}</p>}
{mode === 'signup' && (
<p className="mt-1 text-xs text-gray-500">{t.auth.emailHint}</p>
)}
{fieldErrors.email && <p style={s.fieldError}>{fieldErrors.email}</p>}
{mode === 'signup' && <p style={s.fieldHint}>{t.auth.emailHint}</p>}
</div>
<div>
<label htmlFor="auth-password" className="block text-sm font-medium text-gray-700 mb-1">
{t.auth.password}
</label>
<div style={s.fieldGroup}>
<label htmlFor="auth-password" style={s.label}>{t.auth.password}</label>
<input
id="auth-password"
type="password"
value={password}
onChange={(e) => {
setPassword(e.target.value);
if (fieldErrors.password) {
setFieldErrors((prev) => ({ ...prev, password: undefined }));
}
if (fieldErrors.password) setFieldErrors((prev) => ({ ...prev, password: undefined }));
}}
className={`w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent ${
fieldErrors.password ? 'border-red-400' : 'border-gray-300'
}`}
style={fieldErrors.password ? { ...s.input, ...s.inputError } : s.input}
placeholder="••••••••"
required
minLength={6}
disabled={isBusy}
/>
{fieldErrors.password && <p className="mt-1 text-xs text-red-600">{fieldErrors.password}</p>}
{mode === 'signup' && (
<p className="mt-1 text-xs text-gray-500">{t.auth.passwordHint}</p>
)}
{fieldErrors.password && <p style={s.fieldError}>{fieldErrors.password}</p>}
{mode === 'signup' && <p style={s.fieldHint}>{t.auth.passwordHint}</p>}
</div>
{mode === 'signup' && (
<div>
<label htmlFor="auth-password-confirm" className="block text-sm font-medium text-gray-700 mb-1">
{t.auth.confirmPassword}
</label>
<div style={s.fieldGroup}>
<label htmlFor="auth-password-confirm" style={s.label}>{t.auth.confirmPassword}</label>
<input
id="auth-password-confirm"
type="password"
value={confirmPassword}
onChange={(e) => {
setConfirmPassword(e.target.value);
if (fieldErrors.confirmPassword) {
setFieldErrors((prev) => ({ ...prev, confirmPassword: undefined }));
}
if (fieldErrors.confirmPassword) setFieldErrors((prev) => ({ ...prev, confirmPassword: undefined }));
}}
className={`w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent ${
fieldErrors.confirmPassword ? 'border-red-400' : 'border-gray-300'
}`}
style={fieldErrors.confirmPassword ? { ...s.input, ...s.inputError } : s.input}
placeholder="••••••••"
required
minLength={6}
disabled={isBusy}
/>
{fieldErrors.confirmPassword && <p className="mt-1 text-xs text-red-600">{fieldErrors.confirmPassword}</p>}
{fieldErrors.confirmPassword && <p style={s.fieldError}>{fieldErrors.confirmPassword}</p>}
</div>
)}
{(localError || authError) && (
<div className="p-3 bg-red-100 border border-red-300 text-red-700 rounded-lg text-sm font-medium">
<div style={s.errorBox}>
{t.auth.error}: {localError || authError}
</div>
)}
@@ -215,31 +358,28 @@ export default function AuthModal({ onClose, mandatory = false }: AuthModalProps
<button
type="submit"
disabled={isBusy}
className="w-full py-3 px-4 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors font-medium disabled:opacity-80 disabled:cursor-wait"
style={{ ...s.submitBtn, opacity: isBusy ? 0.75 : 1, cursor: isBusy ? 'wait' : 'pointer' }}
>
{submitText}
</button>
<div className="relative py-1">
<div className="absolute inset-0 flex items-center" aria-hidden="true">
<div className="w-full border-t border-gray-200" />
</div>
<div className="relative flex justify-center text-xs">
<span className="bg-white px-2 text-gray-400">OR</span>
</div>
<div style={s.divider}>
<div style={s.dividerLine} />
<span style={s.dividerText}>OR</span>
<div style={s.dividerLine} />
</div>
<button
type="button"
onClick={handleGoogleOAuth}
disabled={isBusy}
className="w-full py-3 px-4 border border-gray-300 text-gray-800 rounded-lg hover:bg-gray-50 transition-colors font-medium disabled:opacity-80 disabled:cursor-wait inline-flex items-center justify-center gap-2"
style={{ ...s.googleBtn, opacity: isBusy ? 0.75 : 1, cursor: isBusy ? 'wait' : 'pointer' }}
>
<img
src="https://upload.wikimedia.org/wikipedia/commons/7/7e/Gmail_icon_%282020%29.svg"
alt=""
aria-hidden="true"
className="w-[18px] h-[18px]"
style={{ width: '18px', height: '18px' }}
loading="lazy"
decoding="async"
/>

View File

@@ -1,29 +1,43 @@
import { useEffect, useRef } from 'react';
import { useEffect, useRef, useState } from 'react';
import { Link } from 'react-router-dom';
import { useLanguage } from '../../contexts/LanguageContext';
const LATEX_LINES = [
'<span class="code-kw">\\frac</span>{-b \\pm <span class="code-kw">\\sqrt</span>{b² - 4ac}}{2a}',
'<span class="code-kw">\\int</span>_0^1 x^2\\,dx',
'<span class="code-kw">\\sum</span>_{i=<span class="code-num">1</span>}^n \\frac{1}{i^2}',
const SLIDES = [
{
preview: '/demo/preview-formula.png',
previewAlt: 'Quadratic formula',
latex: '<span class="code-kw" style="color: black;">x = \\frac{- b \\pm \\sqrt{b^{2} - 4 a c}}{2 a}</span>',
},
{
preview: '/demo/preview-chinese.png',
previewAlt: 'Chinese text with integral formula',
latex: '设函数 $f(x)$ 在 $[a,b]$ 上连续,则<br><span class="code-kw" style="color: black;">\\int_a^b f(x)\\dx</span>',
},
{
preview: '/demo/preview-english.png',
previewAlt: 'English text with Maxwell equations',
latex: 'According to Maxwell\'s equations, the curl of the electric field is:<br><span class="code-kw" style="color: black;">\\nabla \\times \\mathbf{E} = - \\frac{\\partial \\mathbf{B}}{\\partial t}</span>',
},
];
export default function HeroSection() {
const { language } = useLanguage();
const codeRef = useRef<HTMLDivElement>(null);
const [slideIdx, setSlideIdx] = useState(0);
// Typing cycling effect
useEffect(() => {
let idx = 0;
const interval = setInterval(() => {
idx = (idx + 1) % LATEX_LINES.length;
if (codeRef.current) {
codeRef.current.innerHTML = LATEX_LINES[idx] + '<span class="cursor-blink"></span>';
}
setSlideIdx((i) => (i + 1) % SLIDES.length);
}, 3500);
return () => clearInterval(interval);
}, []);
useEffect(() => {
if (codeRef.current) {
codeRef.current.innerHTML = SLIDES[slideIdx].latex + '<span class="cursor-blink"></span>';
}
}, [slideIdx]);
return (
<section className="hero">
<div className="container">
@@ -96,22 +110,20 @@ export default function HeroSection() {
</div>
</div>
<div className="window-body">
<div className="upload-zone">
<div className="upload-icon-wrap">
<svg viewBox="0 0 24 24">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
<polyline points="17 8 12 3 7 8" />
<line x1="12" y1="3" x2="12" y2="15" />
</svg>
</div>
<div className="upload-text">
{language === 'zh' ? '点击、拖拽或粘贴文件开始解析' : 'Click, drag or paste a file to start'}
</div>
<div className="upload-sub">
{language === 'zh' ? '支持 PNG · JPG · PDF · 手写截图' : 'PNG · JPG · PDF · Handwriting'}
</div>
{/* Upload zone — shows the uploaded document preview */}
<div className="upload-zone upload-zone-preview">
{SLIDES.map((slide, i) => (
<img
key={slide.preview}
src={slide.preview}
alt={slide.previewAlt}
className={`upload-preview-img${i === slideIdx ? ' upload-preview-active' : ''}`}
/>
))}
</div>
{/* Output zone — LaTeX result */}
<div className="output-zone">
<div className="output-header">
<span className="output-label">LaTeX Output</span>
@@ -122,7 +134,7 @@ export default function HeroSection() {
ref={codeRef}
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{
__html: LATEX_LINES[0] + '<span class="cursor-blink"></span>',
__html: SLIDES[0].latex + '<span class="cursor-blink"></span>',
}}
/>
<div className="output-actions">
@@ -143,6 +155,18 @@ export default function HeroSection() {
<div className="fmt-tag">Word</div>
</div>
</div>
{/* Slide indicator dots */}
<div className="window-slide-dots">
{SLIDES.map((_, i) => (
<button
key={i}
className={`window-dot-btn${i === slideIdx ? ' window-dot-active' : ''}`}
onClick={() => setSlideIdx(i)}
aria-label={`Demo ${i + 1}`}
/>
))}
</div>
</div>
</div>
</div>

View File

@@ -14,68 +14,117 @@ export default function PricingSection() {
{zh ? '选择适合你的方案' : 'Choose the plan that fits your workflow'}
</h2>
<p className="section-desc">
{zh ? '从免费试用到永久桌面版,按需选择,无需绑定订阅。' : 'From free trial to lifetime desktop — pick what fits, no subscription lock-in.'}
{zh
? '从免费试用到永久授权,按需选择,无需绑定订阅。'
: 'From free trial to lifetime license — pick what fits, no subscription lock-in.'}
</p>
</div>
{/* Beta notice */}
<div className="pricing-beta-notice reveal">
<span className="pricing-beta-icon">🎉</span>
<span>
{zh
? '内测期间全部方案免费开放 · 无需绑定支付方式,注册即可解锁全部功能'
: 'Free during beta · No payment required — sign up and unlock all features now'}
</span>
</div>
<div className="pricing-cards">
{/* Free */}
<div className="pricing-card reveal reveal-delay-1">
<div className="plan-name">Free</div>
<div className="plan-price">$<span style={{ fontSize: '44px', color: 'var(--text-strong)' }}>0</span></div>
<div className="plan-price">
{zh ? <><span>¥</span>0</> : <><span>$</span>0</>}
</div>
<div className="plan-period">{zh ? '永久免费' : 'Forever free'}</div>
<div className="plan-desc">For first-time use and quick screenshots.</div>
<div className="plan-edu-price plan-edu-price-placeholder">&nbsp;</div>
<ul className="plan-features">
<li>{zh ? '每月 30 次识别' : '30 recognitions / month'}</li>
<li>LaTeX {zh ? '输出' : 'output'}</li>
<li>Web App {zh ? '访问' : 'access'}</li>
<li>{zh ? '基础公式支持' : 'Basic formula support'}</li>
</ul>
<Link to="/app" className="plan-btn">{zh ? '开始使用' : 'Get Started'}</Link>
<Link to="/app" className="plan-btn">{zh ? '免费体验' : 'Try Free'}</Link>
</div>
{/* Monthly */}
<div className="pricing-card reveal reveal-delay-2">
<div className="plan-name">Monthly</div>
<div className="plan-price"><span>$</span>9</div>
<div className="plan-period">{zh ? '每月 / 随时取消' : '/month · cancel anytime'}</div>
<div className="plan-desc">Unlimited recognition for everyday study.</div>
<div className="plan-ribbon plan-ribbon-free">
{zh ? '限时免费' : 'Free Now'}
</div>
<div className="plan-name">{zh ? '月度会员' : 'Monthly'}</div>
<div className="plan-price">
{zh
? <><span>RMB ¥</span>19.9</>
: <><span>$</span>2.99</>}
</div>
<div className="plan-edu-price">
{zh ? '教育优惠 ¥12.9 / 月' : 'Edu discount $1.99 / mo'}
</div>
<ul className="plan-features">
<li>{zh ? '无限次识别' : 'Unlimited recognitions'}</li>
<li>LaTeX + Markdown</li>
<li>{zh ? 'Word 原生公式' : 'Native Word equations'}</li>
<li>{zh ? '优先处理队列' : 'Priority queue'}</li>
<li>{zh ? '每月 1,000 次请求' : '1,000 requests / month'}</li>
<li>{zh ? '优先处理队列' : 'Priority processing'}</li>
<li>{zh ? '插件使用权限' : 'Plugin access'}</li>
</ul>
<Link to="/app" className="plan-btn">{zh ? '开始使用' : 'Get Started'}</Link>
<Link to="/app" className="plan-btn">{zh ? '免费体验' : 'Try Free'}</Link>
</div>
<div className="pricing-card reveal reveal-delay-3">
<div className="plan-name">Quarterly</div>
<div className="plan-price"><span>$</span>24</div>
<div className="plan-period">{zh ? '每季 · 省 $3/月' : '/quarter · save $3/mo'}</div>
<div className="plan-desc">Best value for semester-long heavy usage.</div>
{/* Quarterly — featured */}
<div className="pricing-card featured reveal reveal-delay-3">
<div className="featured-badge">{zh ? '最优惠' : 'Best Value'}</div>
<div className="plan-ribbon plan-ribbon-free">
{zh ? '限时免费' : 'Free Now'}
</div>
<div className="plan-name">{zh ? '季度会员' : 'Quarterly'}</div>
<div className="plan-price">
{zh
? <><span>RMB ¥</span>49.9</>
: <><span>$</span>9.9</>}
</div>
<div className="plan-edu-price">
{zh ? '教育优惠 ¥29.9 / 季' : 'Edu discount $7.99 / quarter'}
</div>
<ul className="plan-features">
<li>{zh ? '无限次识别' : 'Unlimited recognitions'}</li>
<li>{zh ? '全格式输出' : 'All output formats'}</li>
<li>{zh ? '批量 PDF 提取' : 'Batch PDF extraction'}</li>
<li>{zh ? '每月 3,000 积分' : '3,000 credits / month'}</li>
<li>{zh ? '优先处理队列' : 'Priority processing'}</li>
<li>{zh ? '插件使用权限' : 'Plugin access'}</li>
<li>API {zh ? '访问Beta' : 'access (Beta)'}</li>
</ul>
<Link to="/app" className="plan-btn">{zh ? '开始使用' : 'Get Started'}</Link>
<Link to="/app" className="plan-btn featured-btn">{zh ? '免费体验' : 'Try Free'}</Link>
</div>
<div className="pricing-card featured reveal">
<div className="featured-badge">{zh ? '永久版' : 'Lifetime'}</div>
<div className="plan-name">Desktop</div>
<div className="plan-price"><span>$</span>79</div>
<div className="plan-period">{zh ? '一次购买 · 终身使用' : 'one-time · lifetime access'}</div>
<div className="plan-desc">Lifetime access for offline and sensitive files.</div>
{/* Lifetime License — coming soon */}
<div className="pricing-card pricing-card-desktop reveal">
<div className="plan-ribbon plan-ribbon-soon">Coming Soon</div>
<div className="plan-desktop-tag">
<span>🖥</span>
{zh ? '桌面应用 · 离线运行' : 'Desktop App · Offline'}
</div>
<div className="plan-name">{zh ? '永久授权' : 'Lifetime License'}</div>
<div className="plan-price">
{zh
? <><span>RMB ¥</span>149</>
: <><span>$</span>29.9</>}
</div>
<div className="plan-period">
{zh ? '一次购买 · 终身使用' : 'one-time · lifetime access'}
</div>
<div className="plan-edu-price">
{zh ? '教育优惠 ¥99' : 'Edu discount $24.9'}
</div>
<ul className="plan-features">
<li>{zh ? '完全离线运行' : 'Fully offline'}</li>
<li>{zh ? '无限次识别' : 'Unlimited recognitions'}</li>
<li>{zh ? '批量 PDF 处理' : 'Batch PDF processing'}</li>
<li>{zh ? '本地隐私保护' : 'Local privacy'}</li>
<li>{zh ? '终身免费更新' : 'Lifetime free updates'}</li>
</ul>
<Link to="/app" className="plan-btn featured-btn">{zh ? '购买桌面版' : 'Buy Desktop'}</Link>
<button className="plan-btn plan-btn-desktop-disabled" disabled>
{zh ? '敬请期待' : 'Coming Soon'}
</button>
</div>
</div>
</div>
</section>

View File

@@ -10,12 +10,12 @@ export default function ProductSuiteSection() {
<div className="section-header reveal">
<div className="eyebrow">Product Matrix</div>
<h2 className="section-title">
{language === 'zh' ? '覆盖所有公式工作流' : 'Built for every formula workflow'}
{language === 'zh' ? '三端灵活,覆盖所有使用场景' : 'Three ways to use, every workflow covered'}
</h2>
<p className="section-desc">
{language === 'zh'
? '浏览器即即用,扩展一键复制,再到桌面端离线处理——一个工具,覆盖学生写作业、研究者整理文献、工程师记录推导的全部场景。'
: 'From instant browser use to one-click extension copy to offline desktop — one tool for students, researchers, and engineers.'}
? '浏览器即即用,扩展随手一键,桌面端离线无忧——学生轻松完成作业,教师高效备课制件,研究者快速整理文献,总有一种方式最适合你。'
: 'Browser-ready, extension-powered, desktop offline — for students finishing homework, teachers preparing materials, and researchers organizing papers.'}
</p>
</div>
@@ -30,8 +30,8 @@ export default function ProductSuiteSection() {
<div className="card-title">Web App</div>
<div className="card-desc">
{language === 'zh'
? '浏览器内即时识别,无需安装。上传截图或手写图片,秒级输出 LaTeX。'
: 'Instant recognition in browser, no install needed. Upload screenshot or handwriting, get LaTeX in seconds.'}
? '无需安装,打开浏览器即可使用。上传截图或手写图片,秒级输出 LaTeX——学生写作业、教师课堂即时识别都毫不费力。'
: 'No install needed. Upload a screenshot or handwriting, get LaTeX in seconds — perfect for students and teachers alike.'}
</div>
<Link to="/app" className="card-link">
{language === 'zh' ? '浏览器即时识别 →' : 'Instant formula recognition in browser →'}
@@ -49,8 +49,8 @@ export default function ProductSuiteSection() {
<div className="card-title">Extension</div>
<div className="card-desc">
{language === 'zh'
? '浏览器扩展,一键将 ChatGPT、Claude 输出的公式复制为 Word 原生数学公式。'
: 'Browser extension — one-click copy of formulas from ChatGPT or Claude as native Word equations.'}
? '一键将 ChatGPT、Claude 输出的公式为 Word 原生数学公式。教师备课制件、学生写报告,不再手动排版。'
: 'One-click copy of formulas from ChatGPT or Claude as native Word equations — ideal for teachers making slides and students writing reports.'}
</div>
<a href="#" className="card-link">
{language === 'zh' ? '从 LLM 复制公式到 Word →' : 'Copy formulas from LLMs to Word →'}
@@ -67,8 +67,8 @@ export default function ProductSuiteSection() {
<div className="card-title">Desktop</div>
<div className="card-desc">
{language === 'zh'
? '桌面端离线处理,适合论文批量提取与隐私保护场景,一次购买终身使用。'
: 'Offline desktop app for batch PDF extraction and privacy-sensitive work. One-time purchase.'}
? '本地离线运行,数据不出机。研究者批量提取论文公式、保护敏感数据的首选。一次购买终身使用。'
: 'Fully offline, data stays local. The go-to for researchers extracting formulas from papers at scale. One-time purchase.'}
</div>
<a href="#pricing" className="card-link">
{language === 'zh' ? '查看桌面版定价 →' : 'See Desktop pricing →'}

View File

@@ -46,19 +46,19 @@ export default function Footer() {
<div>
<div className="footer-col-title">{zh ? '公司' : 'Company'}</div>
<ul className="footer-links">
<li><a href="#">{zh ? '关于我们' : 'About'}</a></li>
<li><a href="#pricing">{zh ? '价格' : 'Pricing'}</a></li>
<li><Link to="/about">{zh ? '关于我们' : 'About'}</Link></li>
<li><Link to="/#pricing">{zh ? '价格' : 'Pricing'}</Link></li>
<li><Link to="/blog">{zh ? '博客' : 'Blog'}</Link></li>
<li><a href="#">{zh ? '联系我们' : 'Contact'}</a></li>
<li><Link to="/contact">{zh ? '联系我们' : 'Contact'}</Link></li>
</ul>
</div>
<div>
<div className="footer-col-title">{zh ? '法律' : 'Legal'}</div>
<ul className="footer-links">
<li><a href="#">{zh ? '服务条款' : 'Terms of Service'}</a></li>
<li><a href="#">{zh ? '隐私政策' : 'Privacy Policy'}</a></li>
<li><a href="#">{zh ? 'Cookie 政策' : 'Cookie Policy'}</a></li>
<li><Link to="/terms">{zh ? '服务条款' : 'Terms of Service'}</Link></li>
<li><Link to="/privacy">{zh ? '隐私政策' : 'Privacy Policy'}</Link></li>
<li><Link to="/cookies">{zh ? 'Cookie 政策' : 'Cookie Policy'}</Link></li>
</ul>
</div>
</div>

View File

@@ -2,6 +2,7 @@ import { useState, useEffect, useRef } from 'react';
import { Link, useLocation } from 'react-router-dom';
import { useLanguage } from '../../contexts/LanguageContext';
import { useAuth } from '../../contexts/AuthContext';
import AuthModal from '../AuthModal';
export default function MarketingNavbar() {
const { language, setLanguage } = useLanguage();
@@ -11,6 +12,7 @@ export default function MarketingNavbar() {
const [scrolled, setScrolled] = useState(false);
const [activeSection, setActiveSection] = useState('');
const [userMenuOpen, setUserMenuOpen] = useState(false);
const [showAuthModal, setShowAuthModal] = useState(false);
const userMenuRef = useRef<HTMLDivElement>(null);
// Scroll: sticky style + active nav section
@@ -52,90 +54,94 @@ export default function MarketingNavbar() {
: [];
return (
<nav style={{ opacity: scrolled ? 1 : undefined }}>
<div className="nav-inner">
<Link to="/" className="nav-logo">
<div className="logo-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2">
<path d="M4 6h16M4 10h10M4 14h12M4 18h8" />
</svg>
</div>
<span style={{ fontFamily: "'Lora', serif" }}>TexPixel</span>
</Link>
<>
<nav style={{ opacity: scrolled ? 1 : undefined }}>
<div className="nav-inner">
<Link to="/" className="nav-logo">
<div className="logo-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2">
<path d="M4 6h16M4 10h10M4 14h12M4 18h8" />
</svg>
</div>
<span style={{ fontFamily: "'Lora', serif" }}>TexPixel</span>
</Link>
<ul className="nav-links">
{navLinks.map((link) => (
<li key={link.href}>
<Link
to={link.href}
className={location.pathname === link.href ? 'active' : ''}
>
{link.label}
</Link>
</li>
))}
{anchorLinks.map((link) => (
<li key={link.href}>
<a
href={link.href}
className={activeSection === link.href.slice(1) ? 'active' : ''}
>
{link.label}
</a>
</li>
))}
</ul>
<ul className="nav-links">
{navLinks.map((link) => (
<li key={link.href}>
<Link
to={link.href}
className={location.pathname === link.href ? 'active' : ''}
>
{link.label}
</Link>
</li>
))}
{anchorLinks.map((link) => (
<li key={link.href}>
<a
href={link.href}
className={activeSection === link.href.slice(1) ? 'active' : ''}
>
{link.label}
</a>
</li>
))}
</ul>
<div className="nav-right">
<button
className="lang-switch"
onClick={() => setLanguage(language === 'zh' ? 'en' : 'zh')}
>
{language === 'zh' ? 'EN' : '中文'}
</button>
<div className="nav-right">
<button
className="lang-switch"
onClick={() => setLanguage(language === 'zh' ? 'en' : 'zh')}
>
{language === 'zh' ? 'EN' : '中文'}
</button>
{user ? (
<div className="nav-user" ref={userMenuRef} style={{ position: 'relative' }}>
<div
className="nav-avatar"
onClick={() => setUserMenuOpen((o) => !o)}
style={{ cursor: 'pointer' }}
>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8">
<circle cx="12" cy="8" r="4" />
<path d="M4 20c0-4 3.6-7 8-7s8 3 8 7" />
</svg>
</div>
{userMenuOpen && (
<div className="nav-user-menu" style={{ display: 'block' }}>
<div className="nav-menu-divider" />
<Link to="/app" className="nav-menu-item" onClick={() => setUserMenuOpen(false)}>
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8">
<rect x="2" y="3" width="20" height="14" rx="2" />
<path d="M8 21h8M12 17v4" />
</svg>
{language === 'zh' ? '启动应用' : 'Launch App'}
</Link>
<div className="nav-menu-divider" />
<button
className="nav-menu-item nav-menu-logout"
onClick={() => { signOut(); setUserMenuOpen(false); }}
style={{ background: 'none', border: 'none', width: '100%', textAlign: 'left', cursor: 'pointer' }}
>
{language === 'zh' ? '退出登录' : 'Sign Out'}
</button>
{user ? (
<div className="nav-user" ref={userMenuRef} style={{ position: 'relative' }}>
<div
className="nav-avatar"
onClick={() => setUserMenuOpen((o) => !o)}
style={{ cursor: 'pointer' }}
>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8">
<circle cx="12" cy="8" r="4" />
<path d="M4 20c0-4 3.6-7 8-7s8 3 8 7" />
</svg>
</div>
)}
</div>
) : (
<div className="nav-login-btn">
<Link to="/app" className="btn-cta" style={{ display: 'inline-block', lineHeight: '52px', padding: '0 24px', textDecoration: 'none' }}>
{language === 'zh' ? '免费试用' : 'Try Free'}
</Link>
</div>
)}
{userMenuOpen && (
<div className="nav-user-menu" style={{ display: 'block' }}>
<div className="nav-menu-divider" />
<Link to="/app" className="nav-menu-item" onClick={() => setUserMenuOpen(false)}>
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8">
<rect x="2" y="3" width="20" height="14" rx="2" />
<path d="M8 21h8M12 17v4" />
</svg>
{language === 'zh' ? '启动应用' : 'Launch App'}
</Link>
<div className="nav-menu-divider" />
<button
className="nav-menu-item nav-menu-logout"
onClick={() => { signOut(); setUserMenuOpen(false); }}
style={{ background: 'none', border: 'none', width: '100%', textAlign: 'left', cursor: 'pointer' }}
>
{language === 'zh' ? '退出登录' : 'Sign Out'}
</button>
</div>
)}
</div>
) : (
<div className="nav-login-btn">
<button className="btn-cta" onClick={() => setShowAuthModal(true)}>
{language === 'zh' ? '登录' : 'Login'}
</button>
</div>
)}
</div>
</div>
</div>
</nav>
</nav>
{showAuthModal && <AuthModal onClose={() => setShowAuthModal(false)} />}
</>
);
}

168
src/pages/AboutPage.tsx Normal file
View File

@@ -0,0 +1,168 @@
import { Link } from 'react-router-dom';
import { useLanguage } from '../contexts/LanguageContext';
import SEOHead from '../components/seo/SEOHead';
export default function AboutPage() {
const { language } = useLanguage();
const zh = language === 'zh';
return (
<>
<SEOHead
title={zh ? '关于我们 — TexPixel' : 'About — TexPixel'}
description={zh
? 'TexPixel 致力于让每位学生和研究者都能轻松将数学公式转换为 LaTeX。'
: 'TexPixel is on a mission to make math typesetting effortless for every student and researcher.'}
path="/about"
/>
<div className="docs-detail">
<Link to="/" className="docs-back-link">
<svg viewBox="0 0 24 24"><path d="M15 18l-6-6 6-6" /></svg>
{zh ? '返回首页' : 'Back to home'}
</Link>
<div className="docs-article-header">
<div className="docs-article-tags">
<span className="docs-tag">{zh ? '公司' : 'Company'}</span>
</div>
<h1 className="docs-article-h1">{zh ? '关于 TexPixel' : 'About TexPixel'}</h1>
<div className="docs-meta-row">
<span>{zh ? '让数学排版触手可及' : 'Making math typesetting effortless'}</span>
</div>
</div>
<div className="docs-prose">
{zh ? <AboutZh /> : <AboutEn />}
</div>
</div>
</>
);
}
function AboutEn() {
return (
<>
<h2>Our Mission</h2>
<p>
TexPixel exists to eliminate the most tedious part of academic writing: transcribing handwritten or printed
formulas into LaTeX. We believe that students, researchers, and educators should spend their time thinking
about ideas not fighting with syntax.
</p>
<p>
A photograph of a chalkboard, a scan of a textbook, a snapshot of your own handwriting TexPixel turns any
of these into clean, copy-paste-ready LaTeX, Markdown, or Word equations in under a second.
</p>
<h2>What We Build</h2>
<p>
Our core product is an AI-powered document recognition engine trained specifically on mathematical notation.
Unlike general-purpose OCR tools, TexPixel understands the structure of formulas fractions, integrals,
summations, matrices, and multi-line expressions and produces output that actually works the first time.
</p>
<p>We offer:</p>
<ul>
<li><strong>Web App</strong> instant recognition in the browser, no installation required.</li>
<li><strong>API (Beta)</strong> integrate formula recognition directly into your tools and workflows.</li>
<li><strong>Desktop App</strong> fully offline processing for privacy-sensitive documents.</li>
</ul>
<h2>Who Uses TexPixel</h2>
<p>
Our users range from undergraduate students digitizing lecture notes to researchers processing thousands of
equations from scanned papers. TexPixel is used in over 50 countries, with particular strength in China,
the United States, Germany, Japan, and India.
</p>
<h2>Our Principles</h2>
<ul>
<li><strong>Speed over friction.</strong> Every extra click is one too many. We optimize for the fastest possible path from image to output.</li>
<li><strong>Accuracy where it matters.</strong> A wrong minus sign or missing exponent can invalidate an entire equation. We hold our models to a high bar.</li>
<li><strong>Privacy by design.</strong> Your documents belong to you. We do not use uploaded content to train models without your explicit consent.</li>
<li><strong>Accessible pricing.</strong> Students should not have to pay enterprise prices. Our free tier is generous, and our paid plans are priced for individuals.</li>
</ul>
<h2>The Team</h2>
<p>
TexPixel is a small, focused team of engineers and researchers who care deeply about tools that make
scientific writing easier. We are based in China, with contributors around the world.
</p>
<p>
We're always looking for people who share our obsession with accuracy and clean user interfaces.
If that sounds like you, reach out at <a href="mailto:hello@texpixel.com">hello@texpixel.com</a>.
</p>
<h2>Get in Touch</h2>
<p>
For general inquiries: <a href="mailto:hello@texpixel.com">hello@texpixel.com</a><br />
For support: <a href="mailto:support@texpixel.com">support@texpixel.com</a><br />
For legal and privacy matters: <a href="mailto:legal@texpixel.com">legal@texpixel.com</a>
</p>
<p>
<Link to="/contact">Send us a message </Link>
</p>
</>
);
}
function AboutZh() {
return (
<>
<h2>使</h2>
<p>
TexPixel LaTeX
</p>
<p>
TexPixel
使 LaTeXMarkdown Word
</p>
<h2></h2>
<p>
AI OCR
TexPixel
</p>
<p></p>
<ul>
<li><strong>Web </strong></li>
<li><strong>APIBeta</strong></li>
<li><strong></strong>线</li>
</ul>
<h2>使 TexPixel</h2>
<p>
TexPixel 50 广使
</p>
<h2></h2>
<ul>
<li><strong></strong></li>
<li><strong></strong></li>
<li><strong></strong>使</li>
<li><strong></strong></li>
</ul>
<h2></h2>
<p>
TexPixel 便
</p>
<p>
<a href="mailto:hello@texpixel.com">hello@texpixel.com</a>
</p>
<h2></h2>
<p>
<a href="mailto:hello@texpixel.com">hello@texpixel.com</a><br />
<a href="mailto:support@texpixel.com">support@texpixel.com</a><br />
<a href="mailto:legal@texpixel.com">legal@texpixel.com</a>
</p>
<p>
<Link to="/contact"> </Link>
</p>
</>
);
}

185
src/pages/ContactPage.tsx Normal file
View File

@@ -0,0 +1,185 @@
import { Link } from 'react-router-dom';
import { useLanguage } from '../contexts/LanguageContext';
import SEOHead from '../components/seo/SEOHead';
export default function ContactPage() {
const { language } = useLanguage();
const zh = language === 'zh';
return (
<>
<SEOHead
title={zh ? '联系我们 — TexPixel' : 'Contact — TexPixel'}
description={zh
? '通过邮件联系 TexPixel 团队,获取支持、商务合作或反馈建议。'
: 'Get in touch with the TexPixel team for support, partnerships, or feedback.'}
path="/contact"
/>
<div className="docs-detail">
<Link to="/" className="docs-back-link">
<svg viewBox="0 0 24 24"><path d="M15 18l-6-6 6-6" /></svg>
{zh ? '返回首页' : 'Back to home'}
</Link>
<div className="docs-article-header">
<div className="docs-article-tags">
<span className="docs-tag">{zh ? '公司' : 'Company'}</span>
</div>
<h1 className="docs-article-h1">{zh ? '联系我们' : 'Contact Us'}</h1>
<div className="docs-meta-row">
<span>{zh ? '我们通常在 1 个工作日内回复' : 'We typically reply within 1 business day'}</span>
</div>
</div>
<div className="docs-prose">
{zh ? <ContactZh /> : <ContactEn />}
</div>
</div>
</>
);
}
function ContactEn() {
return (
<>
<p>
Have a question, a bug to report, or an idea to share? We'd love to hear from you.
Choose the right channel below and we'll get back to you as quickly as we can.
</p>
<h2>Support</h2>
<p>
For issues with recognition quality, account problems, credit purchases, or any technical questions about
the product:
</p>
<p>
<a href="mailto:support@texpixel.com">support@texpixel.com</a>
</p>
<p>
When writing in, please include:
</p>
<ul>
<li>A brief description of the issue</li>
<li>The task ID (shown in your history panel) if the issue is recognition-related</li>
<li>Your browser and operating system (for web app issues)</li>
</ul>
<h2>Billing & Credits</h2>
<p>
For questions about charges, credit balance discrepancies, or refund requests:
</p>
<p>
<a href="mailto:billing@texpixel.com">billing@texpixel.com</a>
</p>
<p>
Please include your account email and a description of the issue. For potential billing errors,
include the transaction date and amount.
</p>
<h2>Partnerships & API Access</h2>
<p>
Interested in integrating TexPixel into your platform, LMS, or research pipeline? Looking for volume
pricing or a custom agreement?
</p>
<p>
<a href="mailto:partnerships@texpixel.com">partnerships@texpixel.com</a>
</p>
<h2>Legal & Privacy</h2>
<p>
For privacy requests (data access, deletion, correction), legal inquiries, or DMCA notices:
</p>
<p>
<a href="mailto:legal@texpixel.com">legal@texpixel.com</a>
</p>
<h2>General Inquiries</h2>
<p>
For everything else press, feedback, or just saying hello:
</p>
<p>
<a href="mailto:hello@texpixel.com">hello@texpixel.com</a>
</p>
<h2>Response Times</h2>
<p>
We aim to respond to all inquiries within 1 business day (China Standard Time, UTC+8).
During periods of high volume, support requests may take up to 3 business days.
</p>
<p>
For self-service help, check our <Link to="/docs">documentation</Link> most common questions are
answered there.
</p>
</>
);
}
function ContactZh() {
return (
<>
<p>
Bug
</p>
<h2></h2>
<p>
</p>
<p>
<a href="mailto:support@texpixel.com">support@texpixel.com</a>
</p>
<p>便</p>
<ul>
<li></li>
<li> ID</li>
<li> Web </li>
</ul>
<h2></h2>
<p>
退
</p>
<p>
<a href="mailto:billing@texpixel.com">billing@texpixel.com</a>
</p>
<p>
</p>
<h2> API </h2>
<p>
TexPixel
</p>
<p>
<a href="mailto:partnerships@texpixel.com">partnerships@texpixel.com</a>
</p>
<h2></h2>
<p>
访 DMCA
</p>
<p>
<a href="mailto:legal@texpixel.com">legal@texpixel.com</a>
</p>
<h2></h2>
<p>
访
</p>
<p>
<a href="mailto:hello@texpixel.com">hello@texpixel.com</a>
</p>
<h2></h2>
<p>
1 UTC+8
3
</p>
<p>
<Link to="/docs"></Link>
</p>
</>
);
}

View File

@@ -0,0 +1,304 @@
import { Link } from 'react-router-dom';
import { useLanguage } from '../contexts/LanguageContext';
import SEOHead from '../components/seo/SEOHead';
const EFFECTIVE_DATE_EN = 'March 26, 2026';
const EFFECTIVE_DATE_ZH = '2026年3月26日';
export default function CookiePolicyPage() {
const { language } = useLanguage();
const zh = language === 'zh';
return (
<>
<SEOHead
title={zh ? 'Cookie 政策 — TexPixel' : 'Cookie Policy — TexPixel'}
description={zh
? 'TexPixel 如何使用 Cookie 及本地存储技术。'
: 'How TexPixel uses cookies and local storage technologies.'}
path="/cookies"
/>
<div className="docs-detail">
<Link to="/" className="docs-back-link">
<svg viewBox="0 0 24 24"><path d="M15 18l-6-6 6-6" /></svg>
{zh ? '返回首页' : 'Back to home'}
</Link>
<div className="docs-article-header">
<div className="docs-article-tags">
<span className="docs-tag">Legal</span>
</div>
<h1 className="docs-article-h1">{zh ? 'Cookie 政策' : 'Cookie Policy'}</h1>
<div className="docs-meta-row">
<span>{zh ? `生效日期:${EFFECTIVE_DATE_ZH}` : `Effective: ${EFFECTIVE_DATE_EN}`}</span>
<span className="docs-meta-sep">·</span>
<span>TexPixel</span>
</div>
</div>
<div className="docs-prose">
{zh ? <CookieZh /> : <CookieEn />}
</div>
</div>
</>
);
}
function CookieEn() {
return (
<>
<p>
This Cookie Policy explains how TexPixel ("we", "us", or "our") uses cookies and similar technologies on our
website and web application. By using the Service, you consent to the use of cookies as described here.
</p>
<h2>1. What Are Cookies?</h2>
<p>
Cookies are small text files placed on your device by websites you visit. They are widely used to make websites
work efficiently and to provide information to site owners. Beyond traditional cookies, we also use browser
<strong> localStorage</strong> a similar technology that stores data in your browser without an expiration date.
</p>
<h2>2. Cookies and Storage We Use</h2>
<p>We use the following types of storage technologies:</p>
<h3 style={{ fontSize: '18px', fontWeight: 600, marginTop: '28px', marginBottom: '10px' }}>
Strictly Necessary
</h3>
<p>
These are essential for the Service to function. They cannot be disabled without breaking core functionality.
</p>
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '14px', marginBottom: '8px' }}>
<thead>
<tr style={{ borderBottom: '1px solid var(--color-border)' }}>
<th style={{ textAlign: 'left', padding: '8px 12px', fontWeight: 600 }}>Name</th>
<th style={{ textAlign: 'left', padding: '8px 12px', fontWeight: 600 }}>Type</th>
<th style={{ textAlign: 'left', padding: '8px 12px', fontWeight: 600 }}>Purpose</th>
<th style={{ textAlign: 'left', padding: '8px 12px', fontWeight: 600 }}>Retention</th>
</tr>
</thead>
<tbody>
<tr style={{ borderBottom: '1px solid var(--color-border-light)' }}>
<td style={{ padding: '8px 12px', fontFamily: 'monospace' }}>texpixel_token</td>
<td style={{ padding: '8px 12px' }}>localStorage</td>
<td style={{ padding: '8px 12px' }}>Stores your JWT authentication token to keep you logged in</td>
<td style={{ padding: '8px 12px' }}>Until sign-out or token expiry</td>
</tr>
<tr style={{ borderBottom: '1px solid var(--color-border-light)' }}>
<td style={{ padding: '8px 12px', fontFamily: 'monospace' }}>language</td>
<td style={{ padding: '8px 12px' }}>localStorage</td>
<td style={{ padding: '8px 12px' }}>Remembers your language preference (en/zh)</td>
<td style={{ padding: '8px 12px' }}>Persistent (until cleared)</td>
</tr>
<tr>
<td style={{ padding: '8px 12px', fontFamily: 'monospace' }}>texpixel_guest_usage_count</td>
<td style={{ padding: '8px 12px' }}>localStorage</td>
<td style={{ padding: '8px 12px' }}>Tracks free-tier usage count for guest (unauthenticated) users</td>
<td style={{ padding: '8px 12px' }}>Persistent (until cleared)</td>
</tr>
</tbody>
</table>
<h3 style={{ fontSize: '18px', fontWeight: 600, marginTop: '28px', marginBottom: '10px' }}>
Functional
</h3>
<p>
These enhance your experience but are not strictly required. Disabling them may affect some features.
</p>
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '14px', marginBottom: '8px' }}>
<thead>
<tr style={{ borderBottom: '1px solid var(--color-border)' }}>
<th style={{ textAlign: 'left', padding: '8px 12px', fontWeight: 600 }}>Name</th>
<th style={{ textAlign: 'left', padding: '8px 12px', fontWeight: 600 }}>Type</th>
<th style={{ textAlign: 'left', padding: '8px 12px', fontWeight: 600 }}>Purpose</th>
<th style={{ textAlign: 'left', padding: '8px 12px', fontWeight: 600 }}>Retention</th>
</tr>
</thead>
<tbody>
<tr>
<td style={{ padding: '8px 12px', fontFamily: 'monospace' }}>_ga, _gid</td>
<td style={{ padding: '8px 12px' }}>Cookie</td>
<td style={{ padding: '8px 12px' }}>Analytics (if enabled): distinguishes users and sessions for aggregate usage statistics</td>
<td style={{ padding: '8px 12px' }}>2 years / 24 hours</td>
</tr>
</tbody>
</table>
<h2>3. Third-Party Cookies</h2>
<p>
When you use <strong>Sign in with Google</strong>, Google may set its own cookies as part of the OAuth
authentication flow. These cookies are governed by{' '}
<a href="https://policies.google.com/privacy" target="_blank" rel="noopener noreferrer">Google's Privacy Policy</a>.
We do not control Google's cookies and cannot disable them on Google's behalf.
</p>
<p>
Our payment processor may also set session cookies during the checkout process. These are strictly necessary
for completing your purchase and are removed after the transaction.
</p>
<h2>4. What We Do Not Do</h2>
<ul>
<li>We do not use cookies or localStorage to track you across third-party websites.</li>
<li>We do not sell data derived from cookies to advertisers or data brokers.</li>
<li>We do not serve targeted advertising cookies.</li>
</ul>
<h2>5. Managing Cookies & Local Storage</h2>
<p>
You can control and delete cookies and localStorage data through your browser settings. Note that clearing
your authentication token (<code>texpixel_token</code>) will sign you out. Clearing the <code>language</code>{' '}
key will reset your language preference to auto-detection.
</p>
<p>Instructions for managing storage in common browsers:</p>
<ul>
<li><strong>Chrome:</strong> Settings → Privacy and security → Clear browsing data</li>
<li><strong>Firefox:</strong> Settings → Privacy & Security → Cookies and Site Data → Clear Data</li>
<li><strong>Safari:</strong> Settings → Privacy → Manage Website Data</li>
<li><strong>Edge:</strong> Settings → Privacy, search, and services → Clear browsing data</li>
</ul>
<p>
To inspect or manually delete our localStorage entries, open your browser's Developer Tools Application
tab Local Storage <code>texpixel.com</code>.
</p>
<h2>6. Changes to This Policy</h2>
<p>
We may update this Cookie Policy from time to time. The "Effective" date at the top of this page indicates
when the policy was last revised. Continued use of the Service after changes constitutes acceptance.
</p>
<h2>7. Contact Us</h2>
<p>
If you have questions about our use of cookies, contact us at{' '}
<a href="mailto:privacy@texpixel.com">privacy@texpixel.com</a>.
</p>
</>
);
}
function CookieZh() {
return (
<>
<p>
Cookie TexPixel"我们" Web 使 Cookie
使使 Cookie
</p>
<h2>1. Cookie</h2>
<p>
Cookie 访广使
Cookie 使
<strong> localStorage</strong>
</p>
<h2>2. 使 Cookie </h2>
<p>使</p>
<h3 style={{ fontSize: '18px', fontWeight: 600, marginTop: '28px', marginBottom: '10px' }}>
</h3>
<p>使</p>
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '14px', marginBottom: '8px' }}>
<thead>
<tr style={{ borderBottom: '1px solid var(--color-border)' }}>
<th style={{ textAlign: 'left', padding: '8px 12px', fontWeight: 600 }}></th>
<th style={{ textAlign: 'left', padding: '8px 12px', fontWeight: 600 }}></th>
<th style={{ textAlign: 'left', padding: '8px 12px', fontWeight: 600 }}></th>
<th style={{ textAlign: 'left', padding: '8px 12px', fontWeight: 600 }}></th>
</tr>
</thead>
<tbody>
<tr style={{ borderBottom: '1px solid var(--color-border-light)' }}>
<td style={{ padding: '8px 12px', fontFamily: 'monospace' }}>texpixel_token</td>
<td style={{ padding: '8px 12px' }}>localStorage</td>
<td style={{ padding: '8px 12px' }}> JWT </td>
<td style={{ padding: '8px 12px' }}>退</td>
</tr>
<tr style={{ borderBottom: '1px solid var(--color-border-light)' }}>
<td style={{ padding: '8px 12px', fontFamily: 'monospace' }}>language</td>
<td style={{ padding: '8px 12px' }}>localStorage</td>
<td style={{ padding: '8px 12px' }}>en/zh</td>
<td style={{ padding: '8px 12px' }}></td>
</tr>
<tr>
<td style={{ padding: '8px 12px', fontFamily: 'monospace' }}>texpixel_guest_usage_count</td>
<td style={{ padding: '8px 12px' }}>localStorage</td>
<td style={{ padding: '8px 12px' }}>访使</td>
<td style={{ padding: '8px 12px' }}></td>
</tr>
</tbody>
</table>
<h3 style={{ fontSize: '18px', fontWeight: 600, marginTop: '28px', marginBottom: '10px' }}>
</h3>
<p>使</p>
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '14px', marginBottom: '8px' }}>
<thead>
<tr style={{ borderBottom: '1px solid var(--color-border)' }}>
<th style={{ textAlign: 'left', padding: '8px 12px', fontWeight: 600 }}></th>
<th style={{ textAlign: 'left', padding: '8px 12px', fontWeight: 600 }}></th>
<th style={{ textAlign: 'left', padding: '8px 12px', fontWeight: 600 }}></th>
<th style={{ textAlign: 'left', padding: '8px 12px', fontWeight: 600 }}></th>
</tr>
</thead>
<tbody>
<tr>
<td style={{ padding: '8px 12px', fontFamily: 'monospace' }}>_ga, _gid</td>
<td style={{ padding: '8px 12px' }}>Cookie</td>
<td style={{ padding: '8px 12px' }}>使</td>
<td style={{ padding: '8px 12px' }}>2 / 24</td>
</tr>
</tbody>
</table>
<h2>3. Cookie</h2>
<p>
使<strong></strong>Google OAuth Cookie
Cookie {' '}
<a href="https://policies.google.com/privacy" target="_blank" rel="noopener noreferrer">Google </a>
Google Cookie Google
</p>
<p>
Cookie Cookie
</p>
<h2>4. </h2>
<ul>
<li>使 Cookie localStorage </li>
<li> Cookie 广</li>
<li>广 Cookie</li>
</ul>
<h2>5. Cookie </h2>
<p>
Cookie localStorage
<code>texpixel_token</code>退 <code>language</code>
</p>
<p></p>
<ul>
<li><strong>Chrome</strong> </li>
<li><strong>Firefox</strong> Cookie </li>
<li><strong>Safari</strong> </li>
<li><strong>Edge</strong> </li>
</ul>
<p>
localStorage Application
Local Storage <code>texpixel.com</code>
</p>
<h2>6. </h2>
<p>
Cookie "生效日期"
使
</p>
<h2>7. </h2>
<p>
使 Cookie {' '}
<a href="mailto:privacy@texpixel.com">privacy@texpixel.com</a>
</p>
</>
);
}

283
src/pages/PrivacyPage.tsx Normal file
View File

@@ -0,0 +1,283 @@
import { Link } from 'react-router-dom';
import { useLanguage } from '../contexts/LanguageContext';
import SEOHead from '../components/seo/SEOHead';
const EFFECTIVE_DATE_EN = 'March 26, 2026';
const EFFECTIVE_DATE_ZH = '2026年3月26日';
export default function PrivacyPage() {
const { language } = useLanguage();
const zh = language === 'zh';
return (
<>
<SEOHead
title={zh ? '隐私政策 — TexPixel' : 'Privacy Policy — TexPixel'}
description={zh
? 'TexPixel 如何收集、使用和保护您的个人信息。'
: 'How TexPixel collects, uses, and protects your personal information.'}
path="/privacy"
/>
<div className="docs-detail">
<Link to="/" className="docs-back-link">
<svg viewBox="0 0 24 24"><path d="M15 18l-6-6 6-6" /></svg>
{zh ? '返回首页' : 'Back to home'}
</Link>
<div className="docs-article-header">
<div className="docs-article-tags">
<span className="docs-tag">Legal</span>
</div>
<h1 className="docs-article-h1">{zh ? '隐私政策' : 'Privacy Policy'}</h1>
<div className="docs-meta-row">
<span>{zh ? `生效日期:${EFFECTIVE_DATE_ZH}` : `Effective: ${EFFECTIVE_DATE_EN}`}</span>
<span className="docs-meta-sep">·</span>
<span>TexPixel</span>
</div>
</div>
<div className="docs-prose">
{zh ? <PrivacyZh /> : <PrivacyEn />}
</div>
</div>
</>
);
}
function PrivacyEn() {
return (
<>
<p>
TexPixel ("we", "us", or "our") is committed to protecting your privacy. This Privacy Policy explains what
information we collect, how we use it, and your rights regarding your data. By using the Service, you consent
to the practices described here.
</p>
<h2>1. Information We Collect</h2>
<p><strong>Information you provide directly:</strong></p>
<ul>
<li><strong>Account data:</strong> email address, password (stored as a hashed value we never see your plaintext password), and display name.</li>
<li><strong>Google OAuth data:</strong> if you sign in with Google, we receive your Google account email, name, and profile picture URL as provided by Google.</li>
<li><strong>Payment data:</strong> when you purchase credits, payment is processed by a third-party payment processor. We receive only a transaction ID and purchase amount we do not store your card details.</li>
<li><strong>User Content:</strong> documents, images, and files you upload for recognition processing.</li>
<li><strong>Support communications:</strong> messages you send to our support team.</li>
</ul>
<p><strong>Information collected automatically:</strong></p>
<ul>
<li><strong>Usage data:</strong> pages visited, features used, recognition tasks submitted, timestamps, and error logs.</li>
<li><strong>Device and technical data:</strong> IP address, browser type, operating system, and referring URL.</li>
<li><strong>Cookies and local storage:</strong> authentication tokens (JWT stored in localStorage), language preference, and guest usage count. See our <Link to="/cookies">Cookie Policy</Link> for details.</li>
</ul>
<h2>2. How We Use Your Information</h2>
<ul>
<li>Provide, operate, and improve the Service.</li>
<li>Authenticate your identity and maintain your session.</li>
<li>Process recognition tasks you submit.</li>
<li>Manage your credit balance and transaction history.</li>
<li>Send transactional emails: verification codes, payment receipts, and account security alerts.</li>
<li>Detect and prevent fraud, abuse, and violations of our Terms of Service.</li>
<li>Analyze aggregate usage patterns to improve the Service (using anonymized or pseudonymized data).</li>
<li>Comply with legal obligations.</li>
</ul>
<p>
We do <strong>not</strong> use your User Content to train AI or machine learning models without your explicit,
opt-in consent. We do not sell your personal information to third parties.
</p>
<h2>3. Information Sharing</h2>
<p>We share your information only in the following circumstances:</p>
<ul>
<li><strong>Service providers:</strong> cloud object storage (for file storage), email delivery services (for verification codes and notifications), and payment processors. These parties process data on our behalf under data processing agreements.</li>
<li><strong>Google:</strong> when you use Google OAuth, data is exchanged with Google in accordance with Google's Privacy Policy.</li>
<li><strong>Legal compliance:</strong> we may disclose information if required by law, court order, or governmental authority, or to protect the rights, property, or safety of TexPixel, our users, or the public.</li>
<li><strong>Business transfers:</strong> in the event of a merger, acquisition, or sale of assets, your information may be transferred to the successor entity, subject to the same privacy protections.</li>
</ul>
<h2>4. Data Retention</h2>
<ul>
<li><strong>Account data:</strong> retained for as long as your account is active, plus a 30-day grace period after deletion.</li>
<li><strong>User Content:</strong> uploaded files are retained for up to 90 days after task completion, then permanently deleted unless you save them to your account history.</li>
<li><strong>Task history:</strong> recognition results in your account history are retained until you delete them or close your account.</li>
<li><strong>Payment records:</strong> retained for 7 years as required by applicable financial regulations.</li>
<li><strong>Log data:</strong> server and access logs are retained for up to 90 days.</li>
</ul>
<h2>5. Data Security</h2>
<p>
We implement industry-standard security measures including TLS encryption for data in transit, encrypted storage
for sensitive credentials, and access controls limiting who can access production data. However, no system is
completely secure. We encourage you to use a strong, unique password and to contact us immediately if you suspect
unauthorized access to your account.
</p>
<h2>6. Your Rights</h2>
<p>
Depending on your location, you may have the following rights regarding your personal data:
</p>
<ul>
<li><strong>Access:</strong> request a copy of the personal data we hold about you.</li>
<li><strong>Correction:</strong> request correction of inaccurate or incomplete data.</li>
<li><strong>Deletion:</strong> request deletion of your personal data ("right to be forgotten"), subject to legal retention obligations.</li>
<li><strong>Portability:</strong> receive your data in a structured, machine-readable format.</li>
<li><strong>Objection / Restriction:</strong> object to or request restriction of certain processing activities.</li>
<li><strong>Withdraw Consent:</strong> where processing is based on consent, withdraw that consent at any time.</li>
</ul>
<p>
To exercise any of these rights, contact us at <a href="mailto:privacy@texpixel.com">privacy@texpixel.com</a>.
We will respond within 30 days. We may need to verify your identity before processing your request.
</p>
<h2>7. Children's Privacy</h2>
<p>
The Service is not directed to children under 13. We do not knowingly collect personal information from children
under 13. If we become aware that a child under 13 has provided us with personal information, we will delete it
promptly. If you believe a child has provided us with their data, please contact us immediately.
</p>
<h2>8. International Data Transfers</h2>
<p>
TexPixel operates primarily from servers located in China. If you access the Service from outside China,
your data will be transferred to and processed in China. By using the Service, you consent to this transfer.
We take appropriate safeguards to ensure your data is treated in accordance with this Privacy Policy regardless
of where it is processed.
</p>
<h2>9. Third-Party Links</h2>
<p>
The Service may contain links to third-party websites. This Privacy Policy does not apply to those sites.
We encourage you to review the privacy policies of any third-party services you visit.
</p>
<h2>10. Changes to This Policy</h2>
<p>
We may update this Privacy Policy from time to time. We will notify you of material changes via email or a
prominent notice on the Service at least 14 days before the changes take effect. The "Effective" date at the
top of this page indicates when the policy was last revised.
</p>
<h2>11. Contact Us</h2>
<p>
For privacy-related questions or to exercise your rights, contact us at{' '}
<a href="mailto:privacy@texpixel.com">privacy@texpixel.com</a>.
</p>
</>
);
}
function PrivacyZh() {
return (
<>
<p>
TexPixel"我们"使
使
</p>
<h2>1. </h2>
<p><strong></strong></p>
<ul>
<li><strong></strong></li>
<li><strong>Google OAuth </strong> Google Google URL</li>
<li><strong></strong> ID </li>
<li><strong></strong></li>
<li><strong></strong></li>
</ul>
<p><strong></strong></p>
<ul>
<li><strong>使</strong>访使</li>
<li><strong></strong>IP URL</li>
<li><strong>Cookie </strong>JWT localStorage访使 <Link to="/cookies">Cookie </Link></li>
</ul>
<h2>2. 使</h2>
<ul>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li>使使</li>
<li></li>
</ul>
<p>
<strong></strong>使 AI
</p>
<h2>3. </h2>
<p></p>
<ul>
<li><strong></strong></li>
<li><strong>Google</strong>使 Google OAuth Google Google </li>
<li><strong></strong> TexPixel</li>
<li><strong></strong></li>
</ul>
<h2>4. </h2>
<ul>
<li><strong></strong> 30 </li>
<li><strong></strong> 90 </li>
<li><strong></strong></li>
<li><strong></strong> 7 </li>
<li><strong></strong>访 90 </li>
</ul>
<h2>5. </h2>
<p>
TLS
访访
使怀访
</p>
<h2>6. </h2>
<p></p>
<ul>
<li><strong>访</strong></li>
<li><strong></strong></li>
<li><strong></strong>"被遗忘权"</li>
<li><strong></strong></li>
<li><strong>/</strong></li>
<li><strong></strong></li>
</ul>
<p>
使{' '}
<a href="mailto:privacy@texpixel.com">privacy@texpixel.com</a>
30
</p>
<h2>7. </h2>
<p>
13 13
13
</p>
<h2>8. </h2>
<p>
TexPixel 访
使
</p>
<h2>9. </h2>
<p>
访
</p>
<h2>10. </h2>
<p>
14 "生效日期"
</p>
<h2>11. </h2>
<p>
使{' '}
<a href="mailto:privacy@texpixel.com">privacy@texpixel.com</a>
</p>
</>
);
}

322
src/pages/TermsPage.tsx Normal file
View File

@@ -0,0 +1,322 @@
import { Link } from 'react-router-dom';
import { useLanguage } from '../contexts/LanguageContext';
import SEOHead from '../components/seo/SEOHead';
const EFFECTIVE_DATE_EN = 'March 26, 2026';
const EFFECTIVE_DATE_ZH = '2026年3月26日';
export default function TermsPage() {
const { language } = useLanguage();
const zh = language === 'zh';
return (
<>
<SEOHead
title={zh ? '服务条款 — TexPixel' : 'Terms of Service — TexPixel'}
description={zh
? 'TexPixel 服务条款:账户注册、积分制付费、用户内容与使用限制。'
: 'TexPixel Terms of Service: account registration, credit-based billing, user content, and usage restrictions.'}
path="/terms"
/>
<div className="docs-detail">
<Link to="/" className="docs-back-link">
<svg viewBox="0 0 24 24"><path d="M15 18l-6-6 6-6" /></svg>
{zh ? '返回首页' : 'Back to home'}
</Link>
<div className="docs-article-header">
<div className="docs-article-tags">
<span className="docs-tag">Legal</span>
</div>
<h1 className="docs-article-h1">{zh ? '服务条款' : 'Terms of Service'}</h1>
<div className="docs-meta-row">
<span>{zh ? `生效日期:${EFFECTIVE_DATE_ZH}` : `Effective: ${EFFECTIVE_DATE_EN}`}</span>
<span className="docs-meta-sep">·</span>
<span>TexPixel</span>
</div>
</div>
<div className="docs-prose">
{zh ? <TermsZh /> : <TermsEn />}
</div>
</div>
</>
);
}
function TermsEn() {
return (
<>
<p>
Please read these Terms of Service ("Terms") carefully before using TexPixel ("Service", "we", "us", or "our").
By creating an account or using the Service, you agree to be bound by these Terms. If you do not agree, do not use the Service.
</p>
<h2>1. Eligibility</h2>
<p>
You must be at least 13 years old to use the Service. If you are under 18, you must have your parent or legal guardian's
permission. By using the Service, you represent and warrant that you meet these requirements. Paid features (credits
purchases) are available only to users who are 18 or older or have legal capacity to enter into contracts in their jurisdiction.
</p>
<h2>2. Account Registration</h2>
<p>
You may register using an email address and one-time verification code, or by linking a Google account via OAuth 2.0.
You are responsible for maintaining the confidentiality of your account credentials and for all activities that occur
under your account. You must provide accurate, current, and complete information and keep your account information updated.
We reserve the right to suspend or terminate accounts that contain false information or that are used fraudulently.
</p>
<h2>3. Credits & Payment</h2>
<p>
Access to certain features of the Service requires purchasing credits. Credits are a prepaid, non-refundable virtual
currency used to pay for recognition tasks (image-to-LaTeX, PDF-to-Markdown, handwriting OCR, etc.).
</p>
<ul>
<li><strong>No Refunds.</strong> Credits are non-refundable except where required by applicable law. If you believe
you were charged in error, contact us within 30 days of the charge.</li>
<li><strong>Expiration.</strong> Credits do not expire as long as your account remains active. We reserve the right
to introduce expiration policies with at least 60 days' notice.</li>
<li><strong>Price Changes.</strong> We may change credit prices at any time. Changes will not affect credits already purchased.</li>
<li><strong>Taxes.</strong> You are responsible for all applicable taxes on credit purchases in your jurisdiction.</li>
<li><strong>Free Tier.</strong> Guest users receive a limited number of free recognition tasks. Free usage is subject
to change without notice.</li>
<li><strong>Chargebacks.</strong> Initiating a chargeback without first contacting us may result in immediate account suspension.</li>
</ul>
<h2>4. User Content</h2>
<p>
You retain all intellectual property rights in the documents, images, and files you upload ("User Content"). By
uploading User Content, you grant TexPixel a limited, non-exclusive, royalty-free license to process, store, and
transmit your content solely to provide the Service.
</p>
<ul>
<li>We do not use your User Content to train AI models without your explicit consent.</li>
<li>We do not share your User Content with third parties except as necessary to operate the Service (e.g., cloud
storage providers).</li>
<li>You are responsible for ensuring you have the right to upload and process any content you submit.</li>
<li>Do not upload content that is illegal, infringes third-party rights, or contains personally identifiable
information of others without their consent.</li>
</ul>
<h2>5. Accuracy Disclaimer</h2>
<p>
TexPixel uses AI-based recognition technology. Recognition results (LaTeX, Markdown, text) are provided on an
"as-is" basis. We do not warrant that results will be accurate, complete, or suitable for any particular purpose.
You are responsible for verifying all outputs before use in academic, professional, or published work.
</p>
<h2>6. Prohibited Uses</h2>
<p>You agree not to:</p>
<ul>
<li>Use the Service to process content that is illegal, obscene, defamatory, or violates third-party rights.</li>
<li>Reverse-engineer, decompile, or attempt to extract source code from the Service.</li>
<li>Automate requests to the Service in a manner that exceeds your plan limits or degrades service for others.</li>
<li>Use the Service to build a competing product or service without our written permission.</li>
<li>Share, sell, or transfer your account or credits to another party.</li>
<li>Use bots, scrapers, or automated tools to access the Service beyond what is permitted by the API documentation.</li>
<li>Circumvent any technical measures we use to limit usage or enforce these Terms.</li>
</ul>
<h2>7. Intellectual Property</h2>
<p>
The Service, including its software, design, trademarks, and content (excluding User Content), is owned by TexPixel
and protected by applicable intellectual property laws. You may not copy, modify, distribute, sell, or lease any part
of the Service without our prior written consent.
</p>
<h2>8. Third-Party Services</h2>
<p>
The Service integrates with third-party services including Google (for OAuth), cloud object storage providers (for
file storage), and payment processors. Your use of these services is also subject to their respective terms and
privacy policies. We are not responsible for the practices of third-party services.
</p>
<h2>9. Disclaimer of Warranties</h2>
<p>
THE SERVICE IS PROVIDED "AS IS" AND "AS AVAILABLE" WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.
WE DO NOT WARRANT THAT THE SERVICE WILL BE UNINTERRUPTED, ERROR-FREE, OR SECURE.
</p>
<h2>10. Limitation of Liability</h2>
<p>
TO THE MAXIMUM EXTENT PERMITTED BY LAW, TEXPIXEL SHALL NOT BE LIABLE FOR ANY INDIRECT, INCIDENTAL, SPECIAL,
CONSEQUENTIAL, OR PUNITIVE DAMAGES, INCLUDING LOSS OF DATA, LOSS OF PROFITS, OR BUSINESS INTERRUPTION, ARISING
FROM YOUR USE OF THE SERVICE, EVEN IF WE HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
</p>
<p>
OUR TOTAL LIABILITY TO YOU FOR ANY CLAIMS ARISING FROM YOUR USE OF THE SERVICE SHALL NOT EXCEED THE AMOUNT YOU
PAID US IN THE 12 MONTHS PRECEDING THE CLAIM.
</p>
<h2>11. Termination</h2>
<p>
We may suspend or terminate your account at any time, with or without notice, for conduct that we determine violates
these Terms or is harmful to other users, us, or third parties. You may delete your account at any time by contacting
us. Upon termination, your right to use the Service ceases immediately. Unused credits at the time of termination for
cause are forfeited; credits remaining upon voluntary account closure may be refunded at our discretion.
</p>
<h2>12. Indemnification</h2>
<p>
You agree to indemnify, defend, and hold harmless TexPixel and its officers, directors, employees, and agents from
any claims, liabilities, damages, and expenses (including reasonable attorneys' fees) arising from your use of the
Service, your User Content, or your violation of these Terms.
</p>
<h2>13. Governing Law & Dispute Resolution</h2>
<p>
These Terms are governed by the laws of the People's Republic of China without regard to conflict-of-law principles.
Any disputes arising from these Terms shall first be subject to good-faith negotiation. If unresolved within 30 days,
disputes shall be submitted to binding arbitration in accordance with applicable rules, or to the competent courts of
the jurisdiction where TexPixel is incorporated.
</p>
<h2>14. Changes to These Terms</h2>
<p>
We may modify these Terms at any time. We will provide notice of material changes via email or a prominent notice on
the Service at least 14 days before changes take effect. Your continued use of the Service after the effective date
constitutes your acceptance of the revised Terms.
</p>
<h2>15. Contact Us</h2>
<p>
If you have questions about these Terms, please contact us at{' '}
<a href="mailto:legal@texpixel.com">legal@texpixel.com</a>.
</p>
</>
);
}
function TermsZh() {
return (
<>
<p>
使 TexPixel"服务""我们""条款"
使使
</p>
<h2>1. 使</h2>
<p>
13 使 18
使
</p>
<h2>2. </h2>
<p>
Google OAuth 2.0 Google
</p>
<h2>3. </h2>
<p>
使 LaTeXPDF Markdown
</p>
<ul>
<li><strong>退</strong>退 30 </li>
<li><strong></strong> 60 </li>
<li><strong></strong></li>
<li><strong></strong></li>
<li><strong></strong>访</li>
<li><strong></strong></li>
</ul>
<h2>4. </h2>
<p>
"用户内容" TexPixel
</p>
<ul>
<li>使 AI </li>
<li></li>
<li></li>
<li></li>
</ul>
<h2>5. </h2>
<p>
TexPixel AI LaTeXMarkdown"现状"
</p>
<h2>6. </h2>
<p></p>
<ul>
<li>使</li>
<li></li>
<li></li>
<li></li>
<li></li>
<li>使 API 访</li>
<li>使</li>
</ul>
<h2>7. </h2>
<p>
TexPixel
</p>
<h2>8. </h2>
<p>
Google OAuth
使
</p>
<h2>9. </h2>
<p>
"现状""可用状态"
</p>
<h2>10. </h2>
<p>
TexPixel 使
便
</p>
<p>
使 12
</p>
<h2>11. </h2>
<p>
使
使退
</p>
<h2>12. </h2>
<p>
使
TexPixel
</p>
<h2>13. </h2>
<p>
30 TexPixel
</p>
<h2>14. </h2>
<p>
14 使
</p>
<h2>15. </h2>
<p>
{' '}
<a href="mailto:legal@texpixel.com">legal@texpixel.com</a>
</p>
</>
);
}

View File

@@ -10,6 +10,11 @@ const DocsListPage = lazy(() => import('../pages/DocsListPage'));
const DocDetailPage = lazy(() => import('../pages/DocDetailPage'));
const BlogListPage = lazy(() => import('../pages/BlogListPage'));
const BlogDetailPage = lazy(() => import('../pages/BlogDetailPage'));
const TermsPage = lazy(() => import('../pages/TermsPage'));
const PrivacyPage = lazy(() => import('../pages/PrivacyPage'));
const CookiePolicyPage = lazy(() => import('../pages/CookiePolicyPage'));
const AboutPage = lazy(() => import('../pages/AboutPage'));
const ContactPage = lazy(() => import('../pages/ContactPage'));
function LoadingFallback() {
return (
@@ -29,6 +34,11 @@ export default function AppRouter() {
<Route path="/docs/:slug" element={<DocDetailPage />} />
<Route path="/blog" element={<BlogListPage />} />
<Route path="/blog/:slug" element={<BlogDetailPage />} />
<Route path="/terms" element={<TermsPage />} />
<Route path="/privacy" element={<PrivacyPage />} />
<Route path="/cookies" element={<CookiePolicyPage />} />
<Route path="/about" element={<AboutPage />} />
<Route path="/contact" element={<ContactPage />} />
</Route>
<Route element={<AppLayout />}>
<Route path="/app" element={<WorkspacePage />} />

File diff suppressed because it is too large Load Diff