- Rewrote DocsListPage and DocDetailPage with landing.css aesthetic (icon cards, skeleton loader, prose styles, CTA box) - Added docs-specific CSS to landing.css - Created image-to-latex, copy-to-word, ocr-accuracy, pdf-extraction articles in both English and Chinese - Updated DocsSeoSection guide cards to link to real doc slugs Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
54 KiB
Landing Page Refactor Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Replace all marketing home components with the UI from texpixel-landing.html, preserving exact visual fidelity while integrating with React Router, AuthContext, and existing app infrastructure.
Architecture: Extract the reference HTML's <style> block into a scoped landing.css (imported only in MarketingLayout), convert each HTML section into a focused React component, and convert the reference's <script> behaviors into useEffect hooks and a shared useScrollReveal hook.
Tech Stack: React 18, TypeScript, React Router v6, Tailwind CSS (workspace only), custom CSS (marketing only), useAuth / useLanguage contexts.
Spec: docs/superpowers/specs/2026-03-26-landing-refactor-design.md
Reference: texpixel-landing.html (lines 10-1593 = CSS, lines 1602-2183 = HTML, lines 2185-2289 = JS)
File Map
Create
src/styles/landing.css— all marketing CSS, scoped under.marketing-pagesrc/hooks/useScrollReveal.ts— IntersectionObserver for.revealelementssrc/components/home/ProductSuiteSection.tsx—.product-suitesectionsrc/components/home/ShowcaseSection.tsx—.showcasesectionsrc/components/home/TestimonialsSection.tsx—.user-lovecarousel sectionsrc/components/home/DocsSeoSection.tsx—.docs-seosection
Modify
index.html— add Lora + JetBrains Mono fontssrc/components/layout/MarketingLayout.tsx— import CSS, add.marketing-pagewrapper + glow blobssrc/components/layout/MarketingNavbar.tsx— replace with reference nav designsrc/components/layout/Footer.tsx— replace with reference footer designsrc/components/home/HeroSection.tsx— replace with reference hero + typing effectsrc/components/home/FeaturesSection.tsx— replace with reference core featuressrc/components/home/PricingSection.tsx— replace with reference pricingsrc/pages/HomePage.tsx— update section order, add new sections, add scroll revealsrc/lib/translations.ts— remove deadcontactkey frommarketing.nav
Delete
src/components/home/HowItWorksSection.tsxsrc/components/home/ContactSection.tsx
Task 1: Add Fonts to index.html
Files:
-
Modify:
index.html:15 -
Step 1: Add Lora and JetBrains Mono to the Google Fonts link
Replace the existing fonts line (line 15) in index.html:
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@500;600;700;800&family=DM+Sans:ital,wght@0,400;0,500;0,600;1,400&display=swap" rel="stylesheet" />
With:
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@500;600;700;800&family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;0,9..40,600;1,9..40,300&family=Lora:ital,wght@0,400;0,600;0,700;1,400&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
- Step 2: Verify build still passes
npm run typecheck
Expected: no errors
- Step 3: Commit
git add index.html
git commit -m "feat: add Lora and JetBrains Mono fonts for landing page"
Task 2: Extract Landing CSS
Files:
-
Create:
src/styles/landing.css -
Step 1: Create the styles directory and file
mkdir -p src/styles
- Step 2: Copy the CSS from the reference and apply scoping
Copy lines 10–1593 from texpixel-landing.html (everything inside the <style> tag) into src/styles/landing.css.
Then apply these transformations to prevent workspace bleed:
| Original selector | Replace with |
|---|---|
body { |
.marketing-page { |
body::before { |
.marketing-page::before { |
html { scroll-behavior: smooth; } |
Remove this line (already in index.css via Tailwind base) |
section { position: relative; z-index: 1; } |
.marketing-page section { position: relative; z-index: 1; } |
nav { |
.marketing-page nav { |
footer { (the footer padding rule) |
.marketing-page footer { |
Leave :root { ... } untouched — the variable names (--primary, --bg, etc.) don't conflict with the workspace's variables (--color-primary, --color-bg).
All class-based selectors (.hero, .btn, .product-card, etc.) need no change — they are unique enough to not affect the workspace.
At the very end of the file, add the scroll reveal animation rule used by JS:
.reveal {
opacity: 0;
transform: translateY(24px);
transition: opacity 0.55s ease, transform 0.55s ease;
}
.reveal.visible {
opacity: 1;
transform: translateY(0);
}
.reveal-delay-1 { transition-delay: 0.10s; }
.reveal-delay-2 { transition-delay: 0.20s; }
.reveal-delay-3 { transition-delay: 0.30s; }
- Step 3: Verify the file exists
ls src/styles/landing.css
Expected: file listed
- Step 4: Commit
git add src/styles/landing.css
git commit -m "feat: extract landing page CSS from reference HTML"
Task 3: Update MarketingLayout
Files:
-
Modify:
src/components/layout/MarketingLayout.tsx -
Step 1: Replace MarketingLayout.tsx
import { Outlet } from 'react-router-dom';
import MarketingNavbar from './MarketingNavbar';
import Footer from './Footer';
import '../../styles/landing.css';
export default function MarketingLayout() {
return (
<div className="marketing-page">
<div className="glow-blob glow-blob-1" />
<div className="glow-blob glow-blob-2" />
<div className="glow-blob glow-blob-3" />
<MarketingNavbar />
<main>
<Outlet />
</main>
<Footer />
</div>
);
}
- Step 2: Verify typecheck passes
npm run typecheck
Expected: no errors
- Step 3: Commit
git add src/components/layout/MarketingLayout.tsx
git commit -m "feat: wire landing.css into MarketingLayout with .marketing-page scope"
Task 4: Create useScrollReveal Hook
Files:
-
Create:
src/hooks/useScrollReveal.ts -
Step 1: Create hooks directory and hook file
// src/hooks/useScrollReveal.ts
import { useEffect } from 'react';
export function useScrollReveal() {
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((e) => {
if (e.isIntersecting) {
e.target.classList.add('visible');
observer.unobserve(e.target);
}
});
},
{ threshold: 0.12, rootMargin: '0px 0px -40px 0px' }
);
document.querySelectorAll('.reveal').forEach((el) => observer.observe(el));
return () => observer.disconnect();
}, []);
}
- Step 2: Verify typecheck
npm run typecheck
Expected: no errors
- Step 3: Commit
git add src/hooks/useScrollReveal.ts
git commit -m "feat: add useScrollReveal hook for intersection-based fade-in"
Task 5: Replace MarketingNavbar
Files:
- Modify:
src/components/layout/MarketingNavbar.tsx
The reference navbar has: sticky bar, SVG logo, nav links, lang switch, user avatar dropdown (auth-aware), CTA button.
- Step 1: Replace MarketingNavbar.tsx
import { useState, useEffect, useRef } from 'react';
import { Link, useLocation } from 'react-router-dom';
import { useLanguage } from '../../contexts/LanguageContext';
import { useAuth } from '../../contexts/AuthContext';
export default function MarketingNavbar() {
const { language, setLanguage } = useLanguage();
const { user, signOut } = useAuth();
const location = useLocation();
const isHome = location.pathname === '/';
const [scrolled, setScrolled] = useState(false);
const [activeSection, setActiveSection] = useState('');
const [userMenuOpen, setUserMenuOpen] = useState(false);
const userMenuRef = useRef<HTMLDivElement>(null);
// Scroll: sticky style + active nav section
useEffect(() => {
const handleScroll = () => {
setScrolled(window.scrollY > 10);
if (!isHome) return;
let current = '';
document.querySelectorAll('section[id]').forEach((s) => {
if (window.scrollY >= (s as HTMLElement).offsetTop - 100) {
current = s.id;
}
});
setActiveSection(current);
};
window.addEventListener('scroll', handleScroll, { passive: true });
return () => window.removeEventListener('scroll', handleScroll);
}, [isHome]);
// Close user menu on outside click
useEffect(() => {
const handle = (e: MouseEvent) => {
if (userMenuRef.current && !userMenuRef.current.contains(e.target as Node)) {
setUserMenuOpen(false);
}
};
document.addEventListener('mousedown', handle);
return () => document.removeEventListener('mousedown', handle);
}, []);
const navLinks = [
{ href: '/', label: language === 'zh' ? '首页' : 'Home', isRouter: true },
{ href: '/docs', label: language === 'zh' ? '文档' : 'Docs', isRouter: true },
{ href: '/blog', label: language === 'zh' ? '博客' : 'Blog', isRouter: true },
];
const anchorLinks = isHome
? [{ href: '#pricing', label: language === 'zh' ? '价格' : 'Pricing' }]
: [];
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>
<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>
{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>
</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>
)}
</div>
</div>
</nav>
);
}
- Step 2: Verify typecheck
npm run typecheck
Expected: no errors
- Step 3: Commit
git add src/components/layout/MarketingNavbar.tsx
git commit -m "feat: replace MarketingNavbar with reference design"
Task 6: Replace HeroSection
Files:
- Modify:
src/components/home/HeroSection.tsx
The hero has: left text column, right mock window with upload zone + typing LaTeX output.
- Step 1: Replace HeroSection.tsx
import { useEffect, useRef } 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}',
];
export default function HeroSection() {
const { language } = useLanguage();
const codeRef = useRef<HTMLDivElement>(null);
// 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>';
}
}, 3500);
return () => clearInterval(interval);
}, []);
return (
<section className="hero">
<div className="container">
<div className="hero-inner">
<div className="hero-left">
<h1 className="hero-title">
{language === 'zh' ? (
<>数学公式<br />秒级转换为<br /><em className="latex-word">LaTeX</em></>
) : (
<>Math Formulas<br />Converted to<br /><em className="latex-word">LaTeX</em></>
)}
</h1>
<p className="hero-desc">
{language === 'zh'
? 'AI 驱动的复杂数学公式识别,支持手写与论文截图,毫秒级输出 LaTeX、Markdown 与 Word 原生公式。'
: 'AI-powered recognition for complex math formulas. Supports handwriting and paper screenshots. Instant LaTeX, Markdown, and Word output.'}
</p>
<div className="hero-actions">
<Link to="/app" className="btn btn-primary">
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2">
<path d="M5 3l14 9-14 9V3z" />
</svg>
{language === 'zh' ? '免费试用 TexPixel' : 'Try TexPixel Free'}
</Link>
<a href="#products" className="btn btn-secondary">
{language === 'zh' ? '了解更多 →' : 'Learn More →'}
</a>
</div>
<div className="hero-trust">
<div className="trust-item">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2">
<circle cx="12" cy="12" r="10" /><path d="M12 8v4l3 3" />
</svg>
{language === 'zh' ? '秒级输出' : 'Sub-second output'}
</div>
<div className="trust-item">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
<polyline points="14 2 14 8 20 8" />
</svg>
{language === 'zh' ? '支持 PDF · 手写 · 截图' : 'PDF · Handwriting · Screenshots'}
</div>
<div className="trust-item">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2">
<path d="M20 7H4a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2z" />
<circle cx="12" cy="12" r="1" />
</svg>
{language === 'zh' ? '免费套餐可用' : 'Free plan available'}
</div>
</div>
</div>
<div className="hero-right">
<div className="mock-window">
<div className="window-topbar">
<div className="window-dots">
<div className="window-dot wd-red" />
<div className="window-dot wd-yellow" />
<div className="window-dot wd-green" />
</div>
<div className="window-url">
<svg className="url-lock" viewBox="0 0 12 14">
<rect x="1" y="6" width="10" height="7" rx="2" />
<path d="M3 6V4a3 3 0 0 1 6 0v2" fill="none" stroke="#8CC9BE" strokeWidth="1.4" />
</svg>
texpixel.com/app
</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>
</div>
<div className="output-zone">
<div className="output-header">
<span className="output-label">LaTeX Output</span>
<span className="output-badge">{language === 'zh' ? '识别完成' : 'Done'}</span>
</div>
<div
className="output-code"
ref={codeRef}
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{
__html: LATEX_LINES[0] + '<span class="cursor-blink"></span>',
}}
/>
<div className="output-actions">
<button className="output-btn output-btn-copy">
{language === 'zh' ? '复制 LaTeX' : 'Copy LaTeX'}
</button>
<button className="output-btn output-btn-word">
{language === 'zh' ? '复制到 Word' : 'Copy to Word'}
</button>
</div>
</div>
<div className="window-status">
<div className="status-time">{language === 'zh' ? '识别耗时 0.38s' : 'Recognized in 0.38s'}</div>
<div className="status-format">
<div className="fmt-tag">LaTeX</div>
<div className="fmt-tag">Markdown</div>
<div className="fmt-tag">Word</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
);
}
- Step 2: Verify typecheck
npm run typecheck
Expected: no errors
- Step 3: Commit
git add src/components/home/HeroSection.tsx
git commit -m "feat: replace HeroSection with reference design + typing effect"
Task 7: Create ProductSuiteSection
Files:
-
Create:
src/components/home/ProductSuiteSection.tsx -
Step 1: Create ProductSuiteSection.tsx
import { Link } from 'react-router-dom';
import { useLanguage } from '../../contexts/LanguageContext';
export default function ProductSuiteSection() {
const { language } = useLanguage();
return (
<section className="product-suite" id="products">
<div className="container">
<div className="section-header reveal">
<div className="eyebrow">Product Matrix</div>
<h2 className="section-title">
{language === 'zh' ? '覆盖所有公式工作流' : 'Built for every formula workflow'}
</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.'}
</p>
</div>
<div className="cards-3">
<div className="product-card reveal reveal-delay-1">
<div className="card-icon icon-orange">
<svg 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>
</div>
<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.'}
</div>
<Link to="/app" className="card-link">
{language === 'zh' ? '浏览器即时识别 →' : 'Instant formula recognition in browser →'}
</Link>
</div>
<div className="product-card reveal reveal-delay-2">
<div className="card-icon icon-teal">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
<polyline points="14 2 14 8 20 8" />
<path d="M8 13h8M8 17h5" />
</svg>
</div>
<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.'}
</div>
<a href="#" className="card-link">
{language === 'zh' ? '从 LLM 复制公式到 Word →' : 'Copy formulas from LLMs to Word →'}
</a>
</div>
<div className="product-card reveal reveal-delay-3">
<div className="card-icon icon-lavender">
<svg 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="M2 8h20M8 3v5" />
</svg>
</div>
<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.'}
</div>
<a href="#pricing" className="card-link">
{language === 'zh' ? '查看桌面版定价 →' : 'See Desktop pricing →'}
</a>
</div>
</div>
</div>
</section>
);
}
- Step 2: Verify typecheck
npm run typecheck
Expected: no errors
- Step 3: Commit
git add src/components/home/ProductSuiteSection.tsx
git commit -m "feat: add ProductSuiteSection"
Task 8: Replace FeaturesSection (Core Features)
Files:
-
Modify:
src/components/home/FeaturesSection.tsx -
Step 1: Replace FeaturesSection.tsx
import { useLanguage } from '../../contexts/LanguageContext';
export default function FeaturesSection() {
const { language } = useLanguage();
return (
<section className="core-features">
<div className="container">
<div className="section-header reveal">
<div className="eyebrow">Core Features</div>
<h2 className="section-title">
{language === 'zh'
? '学生留下来的三个理由'
: 'Three reasons students stay with TexPixel'}
</h2>
</div>
<div className="cards-3">
<div className="feature-card reveal reveal-delay-1">
<div className="feature-mini">
<span className="feature-speed">⚡ t < 1s</span>
</div>
<div className="card-title">{language === 'zh' ? '极速识别' : 'Sub-second Recognition'}</div>
<div className="card-desc">
{language === 'zh'
? '上传截图,LaTeX 随即出现。拍下笔记,无需等待,直接复制。'
: 'Upload a screenshot, LaTeX appears instantly. Take a photo of your notes and copy right away.'}
</div>
</div>
<div className="feature-card reveal reveal-delay-2">
<div className="feature-mini" style={{ fontSize: '13px', color: 'var(--text-body)' }}>
<span style={{ fontFamily: "'JetBrains Mono', monospace", color: 'var(--primary)' }}>
\int_0^1 x^2 dx
</span>
</div>
<div className="card-title">{language === 'zh' ? '复杂公式支持' : 'Complex Formula Support'}</div>
<div className="card-desc">
{language === 'zh'
? '矩阵、积分、求和、化学式全部支持。多行公式对齐、角标嵌套一次识别。'
: 'Matrices, integrals, summations, chemical formulas — all supported. Multi-line alignment and nested scripts in one pass.'}
</div>
</div>
<div className="feature-card reveal reveal-delay-3">
<div className="feature-mini">
<span style={{ fontFamily: "'JetBrains Mono', monospace", color: 'var(--text-body)', fontSize: '13px' }}>
\mathbf{'{A}'}<sup style={{ fontSize: '9px', color: 'var(--teal)' }}>−1</sup>b
</span>
</div>
<div className="card-title">{language === 'zh' ? '高准确度' : 'High Accuracy'}</div>
<div className="card-desc">
{language === 'zh'
? '论文级别识别准确率。在 arXiv 截图测试集上准确率超过 95%,持续迭代提升中。'
: 'Publication-grade accuracy. Over 95% on arXiv screenshot benchmarks, continuously improving.'}
</div>
</div>
</div>
</div>
</section>
);
}
- Step 2: Verify typecheck
npm run typecheck
- Step 3: Commit
git add src/components/home/FeaturesSection.tsx
git commit -m "feat: replace FeaturesSection with reference core features design"
Task 9: Create ShowcaseSection
Files:
-
Create:
src/components/home/ShowcaseSection.tsx -
Step 1: Create ShowcaseSection.tsx
import { Link } from 'react-router-dom';
import { useLanguage } from '../../contexts/LanguageContext';
export default function ShowcaseSection() {
const { language } = useLanguage();
return (
<section className="showcase">
<div className="container">
<div className="section-header reveal">
<div className="eyebrow">Live Examples</div>
<h2 className="section-title">
{language === 'zh' ? '真实案例演示' : 'Try Real Examples'}
</h2>
<p className="section-desc">
{language === 'zh'
? '看看 TexPixel 如何处理真实的论文截图与手写笔记。'
: 'See how TexPixel handles real paper screenshots and handwritten notes.'}
</p>
</div>
<div className="showcase-cards">
<div className="showcase-card reveal reveal-delay-1">
<div className="showcase-header">
<div className="showcase-tag">{language === 'zh' ? '复杂公式' : 'Complex Formula'}</div>
<div className="showcase-title">
{language === 'zh' ? '论文截图到可复制 LaTeX' : 'Paper Screenshot to Copyable LaTeX'}
</div>
<div className="showcase-sub">arXiv PDF · 0.41s</div>
</div>
<div className="showcase-body">
<div className="sc-input">
<span style={{ fontFamily: "'JetBrains Mono', monospace", fontSize: '15px', opacity: 0.6 }}>
∑ ¹/ᵢ² · · · [{language === 'zh' ? '图片占位' : 'image placeholder'}]
</span>
</div>
<div className="sc-arrow">↓</div>
<div
className="sc-output"
dangerouslySetInnerHTML={{
__html: '<span class="kw">\\sum</span>_{i=<span class="num">1</span>}^{n}<span class="kw">\\frac</span>{<span class="num">1</span>}{i^<span class="num">2</span>}',
}}
/>
</div>
<div className="showcase-footer">
<Link to="/app" className="btn btn-secondary" style={{ height: '44px', fontSize: '14px', padding: '0 20px' }}>
{language === 'zh' ? '试试这个例子 →' : 'Try this example →'}
</Link>
</div>
</div>
<div className="showcase-card reveal reveal-delay-2">
<div className="showcase-header">
<div className="showcase-tag" style={{ background: 'rgba(140,201,190,0.15)', color: 'var(--teal)' }}>
{language === 'zh' ? '手写公式' : 'Handwritten Formula'}
</div>
<div className="showcase-title">
{language === 'zh' ? '课堂笔记到标准表达式' : 'Classroom Notes to Standard Expression'}
</div>
<div className="showcase-sub">{language === 'zh' ? '手机拍摄笔记 · 0.38s' : 'Phone photo of notes · 0.38s'}</div>
</div>
<div className="showcase-body">
<div className="sc-input">
<span style={{ fontFamily: "'JetBrains Mono', monospace", fontSize: '15px', opacity: 0.6 }}>
lim sin(x)/x [{language === 'zh' ? '手写' : 'handwritten'}]
</span>
</div>
<div className="sc-arrow">↓</div>
<div
className="sc-output"
dangerouslySetInnerHTML={{
__html: '<span class="kw">\\lim</span>_{x<span class="br">\\to</span> <span class="num">0</span>}<span class="kw">\\frac</span>{<span class="kw">\\sin</span> x}{x}',
}}
/>
</div>
<div className="showcase-footer">
<Link to="/app" className="btn btn-secondary" style={{ height: '44px', fontSize: '14px', padding: '0 20px' }}>
{language === 'zh' ? '试试这个例子 →' : 'Try this example →'}
</Link>
</div>
</div>
</div>
</div>
</section>
);
}
- Step 2: Verify typecheck
npm run typecheck
- Step 3: Commit
git add src/components/home/ShowcaseSection.tsx
git commit -m "feat: add ShowcaseSection with formula examples"
Task 10: Create TestimonialsSection (Carousel)
Files:
-
Create:
src/components/home/TestimonialsSection.tsx -
Step 1: Create TestimonialsSection.tsx
6 testimonials, 3 visible at a time = 4 pages. Carousel uses CSS transform: translateX on a flex track.
import { useState, useEffect, useCallback, useRef } from 'react';
import { useLanguage } from '../../contexts/LanguageContext';
const TESTIMONIALS = [
{
quote: '写论文的时候截图粘进去,LaTeX 就出来了。以前每个公式都要手敲,现在一个截图解决,省了我大概 40% 的时间。',
name: '林同学',
role: '数学系研究生 · 北京大学',
avatarBg: 'var(--teal)',
avatarColor: '#1a5c54',
avatarLetter: '林',
stars: '★★★★★',
},
{
quote: '手写推导拍一张,马上就是干净的 LaTeX。对我这种每天要整理大量笔记的人来说,真的是刚需级别的工具。',
name: '田晓雯',
role: '物理学博士候选人 · 清华大学',
avatarBg: 'var(--lavender)',
avatarColor: '#3d3870',
avatarLetter: '田',
stars: '★★★★★',
},
{
quote: "I use it every day for my thesis. The accuracy on complex integrals and matrix expressions is surprisingly good — way better than anything I've tried before.",
name: 'Sarah M.',
role: 'Applied Math PhD · MIT',
avatarBg: 'var(--rose)',
avatarColor: '#7a2e1e',
avatarLetter: 'S',
stars: '★★★★★',
},
{
quote: 'Desktop 版的离线功能对我来说很重要,论文数据不想上传到云端。买断价格也合理,不用每个月担心订阅。',
name: '陈博士',
role: '生物信息学研究员 · 中科院',
avatarBg: 'var(--gold)',
avatarColor: '#6f5800',
avatarLetter: '陈',
stars: '★★★★★',
},
{
quote: 'The browser extension is a game changer. I copy equations from Claude or ChatGPT straight into Word as native math — no more reformatting nightmares.',
name: 'Alex K.',
role: 'Engineering Student · TU Berlin',
avatarBg: '#8CB4C9',
avatarColor: '#1a3d52',
avatarLetter: 'A',
stars: '★★★★★',
},
{
quote: '教材扫描件里的公式以前完全没法用,现在截图一框就搞定。连化学方程式都能识别,超出我的预期。',
name: '王梓涵',
role: '化学工程本科 · 浙江大学',
avatarBg: '#C9A88C',
avatarColor: '#4a2e14',
avatarLetter: '王',
stars: '★★★★☆',
},
];
const VISIBLE = 3;
const TOTAL = TESTIMONIALS.length;
const PAGES = TOTAL - VISIBLE + 1; // 4
export default function TestimonialsSection() {
const { language } = useLanguage();
const [currentPage, setCurrentPage] = useState(0);
const trackRef = useRef<HTMLDivElement>(null);
const autoTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);
const goTo = useCallback((idx: number) => {
const clamped = Math.max(0, Math.min(idx, PAGES - 1));
setCurrentPage(clamped);
if (trackRef.current) {
const cardW = trackRef.current.parentElement!.offsetWidth;
const gap = 20;
const singleW = (cardW - gap * (VISIBLE - 1)) / VISIBLE;
const offset = clamped * (singleW + gap);
trackRef.current.style.transform = `translateX(-${offset}px)`;
}
}, []);
const resetAuto = useCallback(() => {
if (autoTimerRef.current) clearInterval(autoTimerRef.current);
autoTimerRef.current = setInterval(() => {
setCurrentPage((prev) => {
const next = (prev + 1) % PAGES;
goTo(next);
return next;
});
}, 5000);
}, [goTo]);
useEffect(() => {
resetAuto();
return () => { if (autoTimerRef.current) clearInterval(autoTimerRef.current); };
}, [resetAuto]);
useEffect(() => {
const handleResize = () => goTo(currentPage);
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, [currentPage, goTo]);
const handleGoTo = (idx: number) => {
goTo(idx);
resetAuto();
};
return (
<section className="user-love">
<div className="container">
<div className="section-header reveal">
<div className="eyebrow">User Love</div>
<h2 className="section-title">
{language === 'zh' ? '全球学生都在用' : 'Loved by students worldwide'}
</h2>
<p className="section-desc">
{language === 'zh' ? '来自真实用户的反馈。' : 'Feedback from real users.'}
</p>
</div>
<div className="testimonial-wrap reveal">
<div
className="testimonial-track"
ref={trackRef}
style={{ transition: 'transform 0.4s ease' }}
>
{TESTIMONIALS.map((t, i) => (
<div key={i} className="testimonial-card">
<div className="t-quote">"</div>
<p className="t-body">{t.quote}</p>
<div className="t-footer">
<div
className="t-avatar"
style={{ background: t.avatarBg, color: t.avatarColor }}
>
{t.avatarLetter}
</div>
<div>
<div className="t-name">{t.name}</div>
<div className="t-role">{t.role}</div>
</div>
<div className="t-stars">{t.stars}</div>
</div>
</div>
))}
</div>
<div className="testimonial-nav">
<button
className="t-nav-btn"
onClick={() => handleGoTo(currentPage - 1)}
aria-label={language === 'zh' ? '上一条' : 'Previous'}
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M15 18l-6-6 6-6" />
</svg>
</button>
<div className="t-dots">
{Array.from({ length: PAGES }).map((_, i) => (
<div
key={i}
className={`t-dot${i === currentPage ? ' active' : ''}`}
onClick={() => handleGoTo(i)}
/>
))}
</div>
<button
className="t-nav-btn"
onClick={() => handleGoTo(currentPage + 1)}
aria-label={language === 'zh' ? '下一条' : 'Next'}
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M9 18l6-6-6-6" />
</svg>
</button>
</div>
</div>
</div>
</section>
);
}
- Step 2: Verify typecheck
npm run typecheck
Expected: no errors
- Step 3: Commit
git add src/components/home/TestimonialsSection.tsx
git commit -m "feat: add TestimonialsSection with auto-advancing carousel"
Task 11: Replace PricingSection
Files:
-
Modify:
src/components/home/PricingSection.tsx -
Step 1: Replace PricingSection.tsx
import { Link } from 'react-router-dom';
import { useLanguage } from '../../contexts/LanguageContext';
export default function PricingSection() {
const { language } = useLanguage();
const zh = language === 'zh';
return (
<section className="pricing" id="pricing">
<div className="container">
<div className="section-header reveal">
<div className="eyebrow">Pricing</div>
<h2 className="section-title">
{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.'}
</p>
</div>
<div className="pricing-cards">
<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-period">{zh ? '永久免费' : 'Forever free'}</div>
<div className="plan-desc">For first-time use and quick screenshots.</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>
</div>
<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>
<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>
</ul>
<Link to="/app" className="plan-btn">{zh ? '开始使用' : 'Get Started'}</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>
<ul className="plan-features">
<li>{zh ? '无限次识别' : 'Unlimited recognitions'}</li>
<li>{zh ? '全格式输出' : 'All output formats'}</li>
<li>{zh ? '批量 PDF 提取' : 'Batch PDF extraction'}</li>
<li>API {zh ? '访问(Beta)' : 'access (Beta)'}</li>
</ul>
<Link to="/app" className="plan-btn">{zh ? '开始使用' : 'Get Started'}</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>
<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>
</div>
</div>
</div>
</section>
);
}
- Step 2: Verify typecheck
npm run typecheck
- Step 3: Commit
git add src/components/home/PricingSection.tsx
git commit -m "feat: replace PricingSection with reference 4-tier pricing design"
Task 12: Create DocsSeoSection
Files:
-
Create:
src/components/home/DocsSeoSection.tsx -
Step 1: Create DocsSeoSection.tsx
import { Link } from 'react-router-dom';
import { useLanguage } from '../../contexts/LanguageContext';
const GUIDES = [
{
icon: <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>,
icon2: <polyline points="14 2 14 8 20 8"/>,
titleEn: 'How to convert image to LaTeX',
titleZh: '图片转 LaTeX 完整指南',
metaEn: '5 min read · Most popular',
metaZh: '5 分钟 · 最受欢迎',
},
{
icon: <><rect x="4" y="4" width="16" height="16" rx="2"/><path d="M8 9h8M8 12h6M8 15h4"/></>,
titleEn: 'Copy formula to Word — native equation format',
titleZh: '复制公式到 Word — 原生公式格式',
metaEn: '4 min read · Extension users',
metaZh: '4 分钟 · 扩展用户',
},
{
icon: <><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></>,
titleEn: 'OCR math formula guide — accuracy tips',
titleZh: 'OCR 数学公式指南 — 提高准确度',
metaEn: '6 min read · Power users',
metaZh: '6 分钟 · 进阶用户',
},
{
icon: <><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6M10 12l2 2 4-4"/></>,
titleEn: 'PDF formula extraction — batch workflow',
titleZh: 'PDF 公式批量提取工作流',
metaEn: '8 min read · Desktop users',
metaZh: '8 分钟 · 桌面版用户',
},
];
export default function DocsSeoSection() {
const { language } = useLanguage();
const zh = language === 'zh';
return (
<section className="docs-seo" id="docs">
<div className="container">
<div className="section-header reveal">
<div className="eyebrow">Guides</div>
<h2 className="section-title">Image to LaTeX Guides</h2>
<p className="section-desc">
{zh
? '为学生、研究者和数学写作者准备的一步步工作流指南。'
: 'Step-by-step workflows for students, researchers, and anyone writing math.'}
</p>
</div>
<div className="doc-cards reveal">
{GUIDES.map((g, i) => (
<Link key={i} to="/docs" className="doc-card">
<div className="doc-card-left">
<div className="doc-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8">
{g.icon}
{g.icon2}
</svg>
</div>
<div>
<div className="doc-title">{zh ? g.titleZh : g.titleEn}</div>
<div className="doc-meta">{zh ? g.metaZh : g.metaEn}</div>
</div>
</div>
<div className="doc-read">{zh ? '阅读指南 →' : 'Read Guide →'}</div>
</Link>
))}
</div>
</div>
</section>
);
}
- Step 2: Verify typecheck
npm run typecheck
- Step 3: Commit
git add src/components/home/DocsSeoSection.tsx
git commit -m "feat: add DocsSeoSection with guide cards"
Task 13: Replace Footer
Files:
-
Modify:
src/components/layout/Footer.tsx -
Step 1: Replace Footer.tsx
import { Link } from 'react-router-dom';
import { useLanguage } from '../../contexts/LanguageContext';
export default function Footer() {
const { language } = useLanguage();
const zh = language === 'zh';
return (
<footer>
<div className="container">
<div className="footer-grid">
<div className="footer-brand">
<Link to="/" className="footer-logo">
<div className="logo-icon" style={{ width: '32px', height: '32px', borderRadius: '9px' }}>
<svg viewBox="0 0 24 24" style={{ width: '18px', height: '18px' }} fill="none" stroke="white" strokeWidth="2">
<path d="M4 6h16M4 10h10M4 14h12M4 18h8" />
</svg>
</div>
<span>TexPixel</span>
</Link>
<p className="footer-tagline">
{zh ? '为学生、研究者和数学写作者而设计。' : 'Designed for students, researchers, and anyone writing math.'}
</p>
</div>
<div>
<div className="footer-col-title">Product</div>
<ul className="footer-links">
<li><Link to="/app">Web App</Link></li>
<li><a href="#">Extension</a></li>
<li><a href="#pricing">{zh ? '桌面版' : 'Desktop'}</a></li>
<li><a href="#">API (Beta)</a></li>
</ul>
</div>
<div>
<div className="footer-col-title">Docs</div>
<ul className="footer-links">
<li><Link to="/docs">Image to LaTeX</Link></li>
<li><Link to="/docs">PDF to Markdown</Link></li>
<li><Link to="/docs">{zh ? '手写识别' : 'Handwritten OCR'}</Link></li>
<li><Link to="/docs">Word Equations</Link></li>
</ul>
</div>
<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="/blog">{zh ? '博客' : 'Blog'}</Link></li>
<li><a href="#">{zh ? '联系我们' : 'Contact'}</a></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>
</ul>
</div>
</div>
<div className="footer-bottom">
<div className="footer-copy">© 2026 TexPixel. All rights reserved.</div>
<div className="footer-made">
Made with{' '}
<svg viewBox="0 0 24 24" style={{ display: 'inline', width: '14px', height: '14px', verticalAlign: 'middle', fill: 'var(--rose)' }}>
<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" />
</svg>
{' '}{zh ? '为全球学生而作' : 'for students worldwide'}
</div>
</div>
</div>
</footer>
);
}
- Step 2: Verify typecheck
npm run typecheck
- Step 3: Commit
git add src/components/layout/Footer.tsx
git commit -m "feat: replace Footer with reference design"
Task 14: Update HomePage + Delete Old Components + Clean Translations
Files:
-
Modify:
src/pages/HomePage.tsx -
Modify:
src/lib/translations.ts -
Delete:
src/components/home/HowItWorksSection.tsx -
Delete:
src/components/home/ContactSection.tsx -
Step 1: Replace HomePage.tsx
import SEOHead from '../components/seo/SEOHead';
import HeroSection from '../components/home/HeroSection';
import ProductSuiteSection from '../components/home/ProductSuiteSection';
import FeaturesSection from '../components/home/FeaturesSection';
import ShowcaseSection from '../components/home/ShowcaseSection';
import TestimonialsSection from '../components/home/TestimonialsSection';
import PricingSection from '../components/home/PricingSection';
import DocsSeoSection from '../components/home/DocsSeoSection';
import { useScrollReveal } from '../hooks/useScrollReveal';
import { useLanguage } from '../contexts/LanguageContext';
export default function HomePage() {
const { t } = useLanguage();
useScrollReveal();
return (
<>
<SEOHead
title="TexPixel - AI Math Formula Recognition | LaTeX, MathML OCR Tool"
description={t.marketing.hero.subtitle}
path="/"
/>
<HeroSection />
<div className="section-divider" />
<ProductSuiteSection />
<FeaturesSection />
<ShowcaseSection />
<div className="section-divider" />
<TestimonialsSection />
<div className="section-divider" />
<PricingSection />
<div className="section-divider" />
<DocsSeoSection />
</>
);
}
- Step 2: Remove dead
contactkeys from translations.ts
In src/lib/translations.ts:
-
Remove
contact: 'Contact',from theen.marketing.navblock (around line 115) -
Remove
contact: '联系我们',from thezh.marketing.navblock (around line 297) -
Remove the entire
en.marketing.contactblock (the object starting withcontact: { title:around line 165–175) -
Remove the entire
zh.marketing.contactblock (equivalent block in the zh section) -
Step 3: Delete old components
rm src/components/home/HowItWorksSection.tsx
rm src/components/home/ContactSection.tsx
- Step 4: Verify typecheck and build
npm run typecheck && npm run build
Expected: no TypeScript errors, build succeeds
- Step 5: Commit
git add src/pages/HomePage.tsx src/lib/translations.ts
git rm src/components/home/HowItWorksSection.tsx src/components/home/ContactSection.tsx
git commit -m "feat: wire all landing sections in HomePage, remove obsolete components"
Task 15: Verify CSS Scoping Does Not Break Workspace
Files:
-
No code changes — verification only
-
Step 1: Run the dev server and navigate to /app
npm run dev
Open http://localhost:5173/app — verify:
-
The workspace
AppLayoutandWorkspacePagerender correctly -
No
.marketing-pageCSS rules leak (no warm beige background, no grid overlay on the workspace) -
Step 2: Navigate to / (home)
Open http://localhost:5173/ — verify:
-
Background matches reference (
#FFFBF7warm cream with grid pattern) -
All three glow blobs visible as ambient background
-
Navbar, Hero, all sections, and Footer match the reference HTML visually
-
Step 3: Run full build
npm run build
Expected: success with no errors
- Step 4: Final commit
git add -A
git commit -m "chore: final verification — landing refactor complete"