feat: optimize docs pages and add 4 new doc articles (en + zh)
- 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>
This commit is contained in:
66
content/docs/en/copy-to-word.md
Normal file
66
content/docs/en/copy-to-word.md
Normal file
@@ -0,0 +1,66 @@
|
||||
---
|
||||
title: Copy to Word
|
||||
description: Export recognized formulas directly into Microsoft Word as editable equations
|
||||
slug: copy-to-word
|
||||
date: 2026-03-25
|
||||
tags: [export, Word, DOCX]
|
||||
order: 4
|
||||
---
|
||||
|
||||
# Copy to Word
|
||||
|
||||
TexPixel can export your recognized formulas directly into Microsoft Word as native, editable equations — not images. This means you can continue editing the formula inside Word after export.
|
||||
|
||||
## How to Export to Word
|
||||
|
||||
1. Upload your formula image and wait for recognition to complete.
|
||||
2. Click the **Export** button in the result panel.
|
||||
3. Select **DOCX** from the file export options.
|
||||
4. Download the file and open it in Microsoft Word.
|
||||
|
||||
The downloaded `.docx` file contains your formula as a native Word equation (OMML format), which Word renders using its built-in equation editor.
|
||||
|
||||
## Why Use DOCX Export?
|
||||
|
||||
| Method | Editable in Word | Renders Correctly | Copy-Paste |
|
||||
|---|---|---|---|
|
||||
| Screenshot / image | No | Yes | No |
|
||||
| LaTeX string | No (without plugin) | No | Yes |
|
||||
| DOCX export | **Yes** | **Yes** | N/A |
|
||||
|
||||
The DOCX format is ideal when you need to:
|
||||
- Submit homework or reports as Word documents
|
||||
- Share formulas with colleagues who don't use LaTeX
|
||||
- Continue editing the formula after export
|
||||
|
||||
## Inserting into an Existing Document
|
||||
|
||||
If you want to insert a formula into an existing Word document rather than starting fresh:
|
||||
|
||||
1. Open the downloaded `.docx` file in Word.
|
||||
2. Select the equation and copy it (`Ctrl+C` / `Cmd+C`).
|
||||
3. Paste it into your target document (`Ctrl+V` / `Cmd+V`).
|
||||
|
||||
Word preserves the equation formatting during paste.
|
||||
|
||||
## Mixed Content (Text + Formulas)
|
||||
|
||||
If your upload contains a mix of regular text and formulas (e.g., a textbook page), use DOCX export — it's the only format that handles mixed content correctly. LaTeX and MathML export are only available for pure-formula results.
|
||||
|
||||
> **Note:** For mixed-content results, LaTeX/MathML export is disabled. Use DOCX to get a properly formatted document with both text and equations.
|
||||
|
||||
## Compatibility
|
||||
|
||||
DOCX export is compatible with:
|
||||
- Microsoft Word 2016 and later (Windows and Mac)
|
||||
- Google Docs (equations render as images when imported)
|
||||
- LibreOffice Writer (partial support)
|
||||
|
||||
## Tips
|
||||
|
||||
- After pasting into Word, double-click the equation to open the equation editor and make changes.
|
||||
- If the formula looks different from expected, try re-uploading a higher-resolution image for a more accurate recognition result.
|
||||
|
||||
---
|
||||
|
||||
[Try exporting a formula to Word →](/app)
|
||||
80
content/docs/en/image-to-latex.md
Normal file
80
content/docs/en/image-to-latex.md
Normal file
@@ -0,0 +1,80 @@
|
||||
---
|
||||
title: Image to LaTeX
|
||||
description: How to convert any formula image into clean LaTeX code with TexPixel
|
||||
slug: image-to-latex
|
||||
date: 2026-03-25
|
||||
tags: [LaTeX, tutorial]
|
||||
order: 2
|
||||
---
|
||||
|
||||
# Image to LaTeX
|
||||
|
||||
TexPixel's core feature is converting formula images — from photos, scans, or screenshots — directly into LaTeX code you can paste anywhere.
|
||||
|
||||
## How It Works
|
||||
|
||||
1. **Upload your image** — Drag and drop a JPG or PNG into the upload zone, or click to browse. You can also paste from your clipboard.
|
||||
2. **AI processes it** — Our model detects the formula region, runs OCR, and generates structured LaTeX in under a second.
|
||||
3. **Copy the result** — Click the copy button next to the LaTeX output. Paste directly into Overleaf, VS Code, Word, or any LaTeX editor.
|
||||
|
||||
## Input Requirements
|
||||
|
||||
| Requirement | Details |
|
||||
|---|---|
|
||||
| File formats | JPG, PNG |
|
||||
| Max file size | 10 MB |
|
||||
| Recommended DPI | 150 DPI or higher |
|
||||
| Background | White or light backgrounds work best |
|
||||
|
||||
## What Gets Recognized
|
||||
|
||||
TexPixel handles a wide range of mathematical content:
|
||||
|
||||
- **Algebra** — equations, inequalities, polynomials
|
||||
- **Calculus** — derivatives, integrals, limits
|
||||
- **Matrices** — 2×2 up to large arrays
|
||||
- **Greek letters** — α, β, γ, Σ, Π, and more
|
||||
- **Subscripts and superscripts** — `x_i^2`, `a_{n+1}`
|
||||
- **Fractions** — `\frac{a}{b}`, nested fractions
|
||||
- **Square roots and radicals** — `\sqrt{x}`, `\sqrt[n]{x}`
|
||||
|
||||
## Example
|
||||
|
||||
Uploading an image of the quadratic formula gives you:
|
||||
|
||||
```latex
|
||||
x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}
|
||||
```
|
||||
|
||||
An image of an integral:
|
||||
|
||||
```latex
|
||||
\int_0^\infty e^{-x^2}\, dx = \frac{\sqrt{\pi}}{2}
|
||||
```
|
||||
|
||||
## Tips for Best Results
|
||||
|
||||
- **Use clear images** — avoid blur, shadows, or low contrast
|
||||
- **Crop tightly** — the less background, the better the focus
|
||||
- **Dark ink on white paper** — ideal for handwritten formulas
|
||||
- **Avoid rotated images** — keep the formula horizontal
|
||||
- **One formula per image** — for complex multi-part work, crop each formula separately
|
||||
|
||||
## Limitations
|
||||
|
||||
- Extremely faint or pencil-written formulas may have lower accuracy
|
||||
- Hand-drawn arrows or annotation marks outside the formula may be ignored
|
||||
- Very large matrices (10×10+) may have reduced accuracy
|
||||
|
||||
## Copy Options
|
||||
|
||||
After recognition, you can copy output in multiple formats:
|
||||
|
||||
- **LaTeX** — raw LaTeX string
|
||||
- **MathML** — for web embedding
|
||||
- **Markdown** — `$...$` inline or `$$...$$` block
|
||||
- **Plain text** — Unicode approximation
|
||||
|
||||
---
|
||||
|
||||
Ready to try it? [Upload a formula image now →](/app)
|
||||
79
content/docs/en/ocr-accuracy.md
Normal file
79
content/docs/en/ocr-accuracy.md
Normal file
@@ -0,0 +1,79 @@
|
||||
---
|
||||
title: OCR Accuracy
|
||||
description: Understanding TexPixel recognition accuracy and how to get the best results
|
||||
slug: ocr-accuracy
|
||||
date: 2026-03-25
|
||||
tags: [accuracy, tips]
|
||||
order: 5
|
||||
---
|
||||
|
||||
# OCR Accuracy
|
||||
|
||||
TexPixel achieves industry-leading accuracy on mathematical formula recognition — but accuracy isn't uniform across all input types. This guide explains what affects accuracy and how to maximize it.
|
||||
|
||||
## Accuracy by Formula Type
|
||||
|
||||
| Formula Type | Typical Accuracy |
|
||||
|---|---|
|
||||
| Printed formulas (textbooks, papers) | 95–99% |
|
||||
| Clean handwritten formulas | 88–95% |
|
||||
| Scanned documents (300 DPI+) | 93–98% |
|
||||
| Photos of whiteboards | 82–92% |
|
||||
| Low-resolution images (< 72 DPI) | 60–80% |
|
||||
|
||||
These are approximate ranges. Individual results depend heavily on image quality.
|
||||
|
||||
## Factors That Affect Accuracy
|
||||
|
||||
### Image Quality
|
||||
|
||||
The single biggest factor. A blurry, low-resolution, or poorly lit image will always produce worse results than a clean scan.
|
||||
|
||||
- **Resolution** — 150 DPI or higher is recommended. 300 DPI is ideal for documents.
|
||||
- **Contrast** — dark ink on a white background gives the clearest signal to the model.
|
||||
- **Sharpness** — avoid motion blur or out-of-focus shots.
|
||||
|
||||
### Formula Complexity
|
||||
|
||||
Simple single-line equations are recognized with near-perfect accuracy. More complex structures may have occasional errors:
|
||||
|
||||
- Multi-line equation systems
|
||||
- Large matrices (6×6 or larger)
|
||||
- Heavily nested fractions (3+ levels deep)
|
||||
- Non-standard notation or custom symbols
|
||||
|
||||
### Handwriting Style
|
||||
|
||||
Printed (typed) formulas outperform handwritten ones, but TexPixel handles handwriting well when:
|
||||
|
||||
- Letters are clearly formed and not connected (print style, not cursive)
|
||||
- Variables are written in distinct sizes (clearly different x and × for example)
|
||||
- Spacing between symbols is consistent
|
||||
|
||||
### What Reduces Accuracy
|
||||
|
||||
- **Rotated images** — formulas at an angle are harder to parse
|
||||
- **Overlapping elements** — crossed-out work, annotations, or arrows near symbols
|
||||
- **Pencil on paper** — low contrast; try increasing image brightness/contrast before uploading
|
||||
- **Multiple formulas in one image** — crop to the specific formula you need
|
||||
- **Decorative fonts** — calligraphic or stylized mathematical writing
|
||||
|
||||
## Improving Results
|
||||
|
||||
If you're getting errors, try these steps in order:
|
||||
|
||||
1. **Increase image resolution** — scan at 300 DPI instead of 150 DPI
|
||||
2. **Improve contrast** — use a photo editor to increase brightness and contrast
|
||||
3. **Crop tightly** — remove surrounding text and whitespace
|
||||
4. **Straighten the image** — correct rotation before uploading
|
||||
5. **Re-photograph** — better lighting, closer distance, sharper focus
|
||||
|
||||
## Reporting Errors
|
||||
|
||||
Found a formula type that TexPixel consistently gets wrong? Let us know — accuracy feedback directly improves the model over time.
|
||||
|
||||
Contact us at: [support@texpixel.com](mailto:support@texpixel.com)
|
||||
|
||||
---
|
||||
|
||||
[Upload a formula and test accuracy →](/app)
|
||||
75
content/docs/en/pdf-extraction.md
Normal file
75
content/docs/en/pdf-extraction.md
Normal file
@@ -0,0 +1,75 @@
|
||||
---
|
||||
title: PDF Extraction
|
||||
description: Extract and convert formulas from PDF documents automatically with TexPixel
|
||||
slug: pdf-extraction
|
||||
date: 2026-03-25
|
||||
tags: [PDF, extraction]
|
||||
order: 6
|
||||
---
|
||||
|
||||
# PDF Extraction
|
||||
|
||||
TexPixel can process entire PDF documents and extract every formula from every page — automatically. This is useful for textbooks, research papers, or any multi-page document with mathematical content.
|
||||
|
||||
## How to Extract from a PDF
|
||||
|
||||
1. Click the upload zone or drag and drop your PDF file.
|
||||
2. TexPixel detects all pages and identifies formula regions.
|
||||
3. Each recognized formula is listed in the result panel.
|
||||
4. Copy individual formulas or export the entire document as DOCX.
|
||||
|
||||
## What Gets Extracted
|
||||
|
||||
TexPixel identifies formulas in PDFs regardless of whether they were:
|
||||
- Typeset in LaTeX (rendered as vector math)
|
||||
- Embedded as images (scanned pages)
|
||||
- A mix of both
|
||||
|
||||
For vector PDFs (generated from LaTeX or Word), recognition accuracy is typically 95%+. For scanned/image PDFs, accuracy follows the same image quality guidelines as regular image uploads.
|
||||
|
||||
## Supported PDF Types
|
||||
|
||||
| Type | Description | Accuracy |
|
||||
|---|---|---|
|
||||
| Vector PDF | Created from LaTeX, Word, or typesetting tools | 95–99% |
|
||||
| Scanned PDF (high quality) | 300 DPI scan of printed text | 90–97% |
|
||||
| Scanned PDF (low quality) | < 150 DPI or poor contrast | 60–80% |
|
||||
| Photo PDF | Photographed pages embedded as images | 75–90% |
|
||||
|
||||
## File Limits
|
||||
|
||||
- **Max file size:** 20 MB
|
||||
- **Max pages:** 50 pages per upload (Pro plan: unlimited)
|
||||
- **Processing time:** ~2–5 seconds per page
|
||||
|
||||
For documents exceeding these limits, split the PDF into smaller chunks before uploading.
|
||||
|
||||
## Exporting PDF Results
|
||||
|
||||
After extraction, you can export in several ways:
|
||||
|
||||
- **Copy individual formula** — click any recognized formula to copy its LaTeX
|
||||
- **DOCX export** — download the full document with formulas as native Word equations
|
||||
- **Batch copy** — copy all formulas as a list (Pro feature)
|
||||
|
||||
## Tips for Better PDF Results
|
||||
|
||||
- **Use the original PDF**, not a re-scanned copy — vector PDFs give the best results
|
||||
- **Avoid password-protected PDFs** — these cannot be processed
|
||||
- **Crop pages** if a PDF has wide margins with no content — smaller pages process faster
|
||||
- **Split by chapter** for very large documents to stay within page limits
|
||||
|
||||
## Common Issues
|
||||
|
||||
**"No formulas found"**
|
||||
The PDF may be encrypted, have formulas stored as complex vector paths, or use non-standard encoding. Try converting the page to a PNG image and uploading that instead.
|
||||
|
||||
**Formulas recognized but garbled**
|
||||
This often happens with very low DPI scans. Try using a PDF scanner app to rescan at 300 DPI before uploading.
|
||||
|
||||
**Processing is slow**
|
||||
Large PDFs with many pages can take 30–60 seconds. This is normal. The result will appear when processing is complete.
|
||||
|
||||
---
|
||||
|
||||
[Upload a PDF and extract formulas →](/app)
|
||||
66
content/docs/zh/copy-to-word.md
Normal file
66
content/docs/zh/copy-to-word.md
Normal file
@@ -0,0 +1,66 @@
|
||||
---
|
||||
title: 导出到 Word
|
||||
description: 将识别的公式直接导出到 Microsoft Word 中作为可编辑方程
|
||||
slug: copy-to-word
|
||||
date: 2026-03-25
|
||||
tags: [导出, Word, DOCX]
|
||||
order: 4
|
||||
---
|
||||
|
||||
# 导出到 Word
|
||||
|
||||
TexPixel 可以将识别的公式直接导出到 Microsoft Word 中作为原生可编辑方程——而不是图片。这意味着导出后你可以在 Word 中继续编辑公式。
|
||||
|
||||
## 如何导出到 Word
|
||||
|
||||
1. 上传公式图片并等待识别完成。
|
||||
2. 点击结果面板中的**导出**按钮。
|
||||
3. 从文件导出选项中选择 **DOCX**。
|
||||
4. 下载文件并在 Microsoft Word 中打开。
|
||||
|
||||
下载的 `.docx` 文件包含以原生 Word 方程(OMML 格式)表示的公式,Word 使用内置方程编辑器渲染。
|
||||
|
||||
## 为什么使用 DOCX 导出?
|
||||
|
||||
| 方式 | Word 中可编辑 | 正确渲染 | 复制粘贴 |
|
||||
|---|---|---|---|
|
||||
| 截图/图片 | 否 | 是 | 否 |
|
||||
| LaTeX 字符串 | 否(无插件) | 否 | 是 |
|
||||
| DOCX 导出 | **是** | **是** | N/A |
|
||||
|
||||
DOCX 格式非常适合以下情况:
|
||||
- 提交 Word 格式的作业或报告
|
||||
- 与不使用 LaTeX 的同事共享公式
|
||||
- 导出后继续编辑公式
|
||||
|
||||
## 插入到现有文档
|
||||
|
||||
如果你想将公式插入现有 Word 文档而不是新建文档:
|
||||
|
||||
1. 在 Word 中打开下载的 `.docx` 文件。
|
||||
2. 选中方程并复制(`Ctrl+C` / `Cmd+C`)。
|
||||
3. 粘贴到目标文档(`Ctrl+V` / `Cmd+V`)。
|
||||
|
||||
Word 在粘贴时保留方程格式。
|
||||
|
||||
## 混合内容(文字 + 公式)
|
||||
|
||||
如果上传内容包含普通文字和公式的混合(例如教材页面),请使用 DOCX 导出——这是唯一能正确处理混合内容的格式。LaTeX 和 MathML 导出仅适用于纯公式结果。
|
||||
|
||||
> **注意:** 对于混合内容结果,LaTeX/MathML 导出不可用。请使用 DOCX 获取包含文字和方程的格式正确文档。
|
||||
|
||||
## 兼容性
|
||||
|
||||
DOCX 导出与以下软件兼容:
|
||||
- Microsoft Word 2016 及更高版本(Windows 和 Mac)
|
||||
- Google 文档(导入时方程渲染为图片)
|
||||
- LibreOffice Writer(部分支持)
|
||||
|
||||
## 提示
|
||||
|
||||
- 粘贴到 Word 后,双击方程打开方程编辑器进行修改。
|
||||
- 如果公式与预期不同,请尝试上传更高分辨率的图片以获得更准确的识别结果。
|
||||
|
||||
---
|
||||
|
||||
[尝试将公式导出到 Word →](/app)
|
||||
80
content/docs/zh/image-to-latex.md
Normal file
80
content/docs/zh/image-to-latex.md
Normal file
@@ -0,0 +1,80 @@
|
||||
---
|
||||
title: 图片转 LaTeX
|
||||
description: 如何使用 TexPixel 将任意公式图片转换为干净的 LaTeX 代码
|
||||
slug: image-to-latex
|
||||
date: 2026-03-25
|
||||
tags: [LaTeX, 教程]
|
||||
order: 2
|
||||
---
|
||||
|
||||
# 图片转 LaTeX
|
||||
|
||||
TexPixel 的核心功能是将公式图片——来自照片、扫描件或截图——直接转换为可以粘贴到任何地方的 LaTeX 代码。
|
||||
|
||||
## 使用方法
|
||||
|
||||
1. **上传图片** — 将 JPG 或 PNG 拖拽到上传区域,或点击浏览文件。也可以直接从剪贴板粘贴。
|
||||
2. **AI 处理** — 模型检测公式区域,运行 OCR,在不到一秒内生成结构化 LaTeX。
|
||||
3. **复制结果** — 点击 LaTeX 输出旁的复制按钮,直接粘贴到 Overleaf、VS Code、Word 或任意 LaTeX 编辑器。
|
||||
|
||||
## 输入要求
|
||||
|
||||
| 要求 | 详情 |
|
||||
|---|---|
|
||||
| 文件格式 | JPG、PNG |
|
||||
| 最大文件大小 | 10 MB |
|
||||
| 推荐分辨率 | 150 DPI 或更高 |
|
||||
| 背景 | 白色或浅色背景效果最佳 |
|
||||
|
||||
## 支持识别的内容
|
||||
|
||||
TexPixel 可处理多种数学内容:
|
||||
|
||||
- **代数** — 方程、不等式、多项式
|
||||
- **微积分** — 导数、积分、极限
|
||||
- **矩阵** — 2×2 到大型数组
|
||||
- **希腊字母** — α、β、γ、Σ、Π 等
|
||||
- **上下标** — `x_i^2`、`a_{n+1}`
|
||||
- **分数** — `\frac{a}{b}`、嵌套分数
|
||||
- **根号** — `\sqrt{x}`、`\sqrt[n]{x}`
|
||||
|
||||
## 示例
|
||||
|
||||
上传二次公式图片,输出:
|
||||
|
||||
```latex
|
||||
x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}
|
||||
```
|
||||
|
||||
上传积分图片:
|
||||
|
||||
```latex
|
||||
\int_0^\infty e^{-x^2}\, dx = \frac{\sqrt{\pi}}{2}
|
||||
```
|
||||
|
||||
## 获得最佳结果的技巧
|
||||
|
||||
- **使用清晰图片** — 避免模糊、阴影或低对比度
|
||||
- **紧密裁剪** — 背景越少,焦点越准确
|
||||
- **白纸深色墨水** — 手写公式的理想条件
|
||||
- **避免旋转图片** — 保持公式水平
|
||||
- **每张图片一个公式** — 对于复杂的多部分作业,分别裁剪每个公式
|
||||
|
||||
## 局限性
|
||||
|
||||
- 非常淡或铅笔书写的公式准确率可能较低
|
||||
- 公式外的手绘箭头或注释标记可能被忽略
|
||||
- 非常大的矩阵(10×10 以上)可能准确率降低
|
||||
|
||||
## 复制选项
|
||||
|
||||
识别完成后,可以多种格式复制输出:
|
||||
|
||||
- **LaTeX** — 原始 LaTeX 字符串
|
||||
- **MathML** — 用于网页嵌入
|
||||
- **Markdown** — 行内 `$...$` 或块级 `$$...$$`
|
||||
- **纯文本** — Unicode 近似表示
|
||||
|
||||
---
|
||||
|
||||
准备好了吗?[立即上传公式图片 →](/app)
|
||||
79
content/docs/zh/ocr-accuracy.md
Normal file
79
content/docs/zh/ocr-accuracy.md
Normal file
@@ -0,0 +1,79 @@
|
||||
---
|
||||
title: 识别准确率
|
||||
description: 了解 TexPixel 识别准确率及如何获得最佳效果
|
||||
slug: ocr-accuracy
|
||||
date: 2026-03-25
|
||||
tags: [准确率, 技巧]
|
||||
order: 5
|
||||
---
|
||||
|
||||
# 识别准确率
|
||||
|
||||
TexPixel 在数学公式识别方面达到行业领先的准确率——但准确率在不同输入类型之间并不统一。本指南解释影响准确率的因素以及如何最大化识别效果。
|
||||
|
||||
## 按公式类型的准确率
|
||||
|
||||
| 公式类型 | 典型准确率 |
|
||||
|---|---|
|
||||
| 印刷体公式(教材、论文) | 95–99% |
|
||||
| 清晰手写公式 | 88–95% |
|
||||
| 扫描文档(300 DPI+) | 93–98% |
|
||||
| 白板照片 | 82–92% |
|
||||
| 低分辨率图片(< 72 DPI) | 60–80% |
|
||||
|
||||
这些是大致范围,实际结果在很大程度上取决于图片质量。
|
||||
|
||||
## 影响准确率的因素
|
||||
|
||||
### 图片质量
|
||||
|
||||
这是最重要的单一因素。模糊、低分辨率或光线不佳的图片效果始终不如清晰扫描件。
|
||||
|
||||
- **分辨率** — 建议 150 DPI 或更高,文档理想为 300 DPI
|
||||
- **对比度** — 白色背景上的深色墨水为模型提供最清晰的信号
|
||||
- **清晰度** — 避免运动模糊或对焦不准
|
||||
|
||||
### 公式复杂度
|
||||
|
||||
简单的单行方程识别准确率接近完美。更复杂的结构可能偶有错误:
|
||||
|
||||
- 多行方程组
|
||||
- 大矩阵(6×6 或更大)
|
||||
- 深度嵌套分数(3 层以上)
|
||||
- 非标准符号或自定义符号
|
||||
|
||||
### 手写风格
|
||||
|
||||
印刷体(打字)公式优于手写体,但当以下条件满足时,TexPixel 能很好地处理手写:
|
||||
|
||||
- 字母清晰成形且不连笔(印刷体,而非草书)
|
||||
- 变量写成明显不同的大小(例如 x 和 × 清晰区分)
|
||||
- 符号间距一致
|
||||
|
||||
### 降低准确率的因素
|
||||
|
||||
- **旋转图片** — 倾斜的公式更难解析
|
||||
- **重叠元素** — 划掉的内容、注释或符号附近的箭头
|
||||
- **纸上铅笔** — 对比度低;上传前可尝试增加图片亮度/对比度
|
||||
- **一张图片多个公式** — 裁剪到你需要的具体公式
|
||||
- **装饰字体** — 花体或风格化数学书写
|
||||
|
||||
## 提高识别效果
|
||||
|
||||
如果识别出错,按以下顺序尝试:
|
||||
|
||||
1. **提高图片分辨率** — 用 300 DPI 扫描代替 150 DPI
|
||||
2. **改善对比度** — 使用图片编辑器提高亮度和对比度
|
||||
3. **紧密裁剪** — 去除周围文字和空白
|
||||
4. **矫正图片** — 上传前纠正旋转
|
||||
5. **重新拍摄** — 更好的光线、更近的距离、更清晰的对焦
|
||||
|
||||
## 反馈错误
|
||||
|
||||
发现 TexPixel 持续识别错误的公式类型?请告知我们——准确率反馈直接改进模型。
|
||||
|
||||
联系我们:[support@texpixel.com](mailto:support@texpixel.com)
|
||||
|
||||
---
|
||||
|
||||
[上传公式测试识别准确率 →](/app)
|
||||
75
content/docs/zh/pdf-extraction.md
Normal file
75
content/docs/zh/pdf-extraction.md
Normal file
@@ -0,0 +1,75 @@
|
||||
---
|
||||
title: PDF 公式提取
|
||||
description: 使用 TexPixel 自动从 PDF 文档中提取并转换公式
|
||||
slug: pdf-extraction
|
||||
date: 2026-03-25
|
||||
tags: [PDF, 提取]
|
||||
order: 6
|
||||
---
|
||||
|
||||
# PDF 公式提取
|
||||
|
||||
TexPixel 可以处理完整的 PDF 文档,自动从每一页提取所有公式。这对教材、研究论文或任何包含数学内容的多页文档非常有用。
|
||||
|
||||
## 如何从 PDF 提取
|
||||
|
||||
1. 点击上传区域或将 PDF 文件拖拽到其中。
|
||||
2. TexPixel 检测所有页面并识别公式区域。
|
||||
3. 每个识别的公式列在结果面板中。
|
||||
4. 复制单个公式或将整个文档导出为 DOCX。
|
||||
|
||||
## 提取内容
|
||||
|
||||
无论 PDF 中的公式是如何生成的,TexPixel 都能识别:
|
||||
- 用 LaTeX 排版(渲染为矢量数学)
|
||||
- 嵌入为图片(扫描页面)
|
||||
- 两种混合
|
||||
|
||||
对于矢量 PDF(由 LaTeX 或 Word 生成),识别准确率通常为 95% 以上。对于扫描/图片 PDF,准确率遵循与普通图片上传相同的图片质量准则。
|
||||
|
||||
## 支持的 PDF 类型
|
||||
|
||||
| 类型 | 描述 | 准确率 |
|
||||
|---|---|---|
|
||||
| 矢量 PDF | 由 LaTeX、Word 或排版工具创建 | 95–99% |
|
||||
| 扫描 PDF(高质量) | 印刷文字的 300 DPI 扫描 | 90–97% |
|
||||
| 扫描 PDF(低质量) | < 150 DPI 或对比度差 | 60–80% |
|
||||
| 照片 PDF | 嵌入为图片的拍照页面 | 75–90% |
|
||||
|
||||
## 文件限制
|
||||
|
||||
- **最大文件大小:** 20 MB
|
||||
- **最大页数:** 每次上传 50 页(专业版:无限制)
|
||||
- **处理时间:** 每页约 2–5 秒
|
||||
|
||||
对于超出限制的文档,上传前将 PDF 分割成较小的部分。
|
||||
|
||||
## 导出 PDF 识别结果
|
||||
|
||||
提取后,可以多种方式导出:
|
||||
|
||||
- **复制单个公式** — 点击任意识别的公式复制其 LaTeX
|
||||
- **DOCX 导出** — 下载包含原生 Word 方程的完整文档
|
||||
- **批量复制** — 将所有公式复制为列表(专业版功能)
|
||||
|
||||
## 提高 PDF 识别效果的技巧
|
||||
|
||||
- **使用原始 PDF**,而非重新扫描的副本——矢量 PDF 效果最佳
|
||||
- **避免密码保护的 PDF**——这类文件无法处理
|
||||
- 如果 PDF 有很宽的空白边距,**裁剪页面**——较小的页面处理更快
|
||||
- 对于非常大的文档,**按章节分割**以保持在页数限制内
|
||||
|
||||
## 常见问题
|
||||
|
||||
**"未找到公式"**
|
||||
PDF 可能已加密,公式可能以复杂矢量路径存储,或使用了非标准编码。尝试将页面转换为 PNG 图片后再上传。
|
||||
|
||||
**公式已识别但内容乱码**
|
||||
这通常发生在非常低 DPI 的扫描件上。尝试在上传前使用 PDF 扫描应用以 300 DPI 重新扫描。
|
||||
|
||||
**处理速度慢**
|
||||
包含多页的大型 PDF 可能需要 30–60 秒。这是正常的,处理完成后结果会显示。
|
||||
|
||||
---
|
||||
|
||||
[上传 PDF 提取公式 →](/app)
|
||||
1523
docs/superpowers/plans/2026-03-26-landing-refactor.md
Normal file
1523
docs/superpowers/plans/2026-03-26-landing-refactor.md
Normal file
File diff suppressed because it is too large
Load Diff
129
docs/superpowers/specs/2026-03-26-landing-refactor-design.md
Normal file
129
docs/superpowers/specs/2026-03-26-landing-refactor-design.md
Normal file
@@ -0,0 +1,129 @@
|
||||
# Landing Page Refactor — Design Spec
|
||||
**Date:** 2026-03-26
|
||||
**Status:** Approved
|
||||
|
||||
## Goal
|
||||
Replace all existing marketing home components with content and styles from `texpixel-landing.html`. The UI/UX must exactly match the reference file. The home page is a marketing/broadcast page; all CTAs navigate to `/app`.
|
||||
|
||||
---
|
||||
|
||||
## CSS Strategy
|
||||
- Extract the full `<style>` block (lines 10–1593) from `texpixel-landing.html` into `src/styles/landing.css`.
|
||||
- **Scope all rules** under a `.marketing-page` wrapper class to prevent bleed into the `/app` workspace.
|
||||
- Body-level rules (`body { background }`, `body::before` grid overlay) are converted to `.marketing-page` and `.marketing-page::before` respectively.
|
||||
- `:root` CSS variable declarations are kept as-is since landing variables use different names (`--primary`, `--bg`, etc.) from existing workspace variables (`--color-primary`, `--color-bg`). No conflict — they coexist.
|
||||
- **Do NOT import in `main.tsx`** — import directly in `MarketingLayout.tsx` via `import '../styles/landing.css'` so it only applies to marketing routes.
|
||||
- `MarketingLayout.tsx` wrapper div gets `className="marketing-page"`.
|
||||
- `index.css` Tailwind layer remains untouched.
|
||||
- Add Google Fonts to `index.html` `<head>`: Lora (serif, weights 400/600/700) and JetBrains Mono (monospace, weights 400/500). DM Sans already present.
|
||||
|
||||
---
|
||||
|
||||
## Component Mapping
|
||||
|
||||
| Reference section | Target file | Action |
|
||||
|---|---|---|
|
||||
| `<nav>` | `src/components/layout/MarketingNavbar.tsx` | Replace |
|
||||
| `.hero` | `src/components/home/HeroSection.tsx` | Replace |
|
||||
| `.product-suite` | `src/components/home/ProductSuiteSection.tsx` | New |
|
||||
| `.core-features` | `src/components/home/FeaturesSection.tsx` | Replace |
|
||||
| `.showcase` | `src/components/home/ShowcaseSection.tsx` | New |
|
||||
| `.user-love` | `src/components/home/TestimonialsSection.tsx` | New |
|
||||
| `.pricing` | `src/components/home/PricingSection.tsx` | Replace |
|
||||
| `.docs-seo` | `src/components/home/DocsSeoSection.tsx` | New |
|
||||
| `<footer>` | `src/components/layout/Footer.tsx` | Replace |
|
||||
|
||||
### Delete (no reference equivalent)
|
||||
- `src/components/home/HowItWorksSection.tsx`
|
||||
- `src/components/home/ContactSection.tsx`
|
||||
|
||||
---
|
||||
|
||||
## MarketingLayout.tsx
|
||||
- Wrap outlet in `<div className="marketing-page">` — applies scoped landing CSS
|
||||
- Render three `.glow-blob` divs (`.glow-blob-1`, `.glow-blob-2`, `.glow-blob-3`) as direct children of the `.marketing-page` wrapper — these are `position: fixed` ambient background elements visible across all marketing pages.
|
||||
|
||||
---
|
||||
|
||||
## HomePage.tsx
|
||||
Update to render sections in order:
|
||||
```
|
||||
HeroSection
|
||||
<div className="section-divider" />
|
||||
ProductSuiteSection
|
||||
FeaturesSection
|
||||
ShowcaseSection
|
||||
<div className="section-divider" />
|
||||
TestimonialsSection
|
||||
<div className="section-divider" />
|
||||
PricingSection
|
||||
<div className="section-divider" />
|
||||
DocsSeoSection
|
||||
```
|
||||
Section dividers are plain `<div className="section-divider" />` JSX inlined in `HomePage.tsx` — no abstraction needed.
|
||||
|
||||
---
|
||||
|
||||
## Navbar (MarketingNavbar.tsx)
|
||||
- Sticky, height 72px, backdrop blur on scroll (existing scroll state logic kept)
|
||||
- Logo: SVG icon (lines symbol) + "TexPixel" text — **remove `font-display` Tailwind class** from logo `<span>`, replace with `style={{ fontFamily: "'Lora', serif" }}` to avoid Plus Jakarta Sans conflict
|
||||
- Nav links: Home `/`, Docs `/docs`, Blog `/blog`, Pricing `#pricing` (anchor on home only), **no Contact link** — also remove the existing `anchorLinks` `#contact` entry
|
||||
- Right side:
|
||||
- Lang switch button (existing `useLanguage` toggle)
|
||||
- User avatar/menu using `const { user, signOut } = useAuth()` — show avatar dropdown when `user !== null`; show "登录/Login" CTA button when `user === null`
|
||||
- Avatar dropdown items: "启动应用" → `/app`, then logout (calls `signOut()`). **No profile settings link** (route does not exist — omitted)
|
||||
- "Try Free" CTA button → `/app`
|
||||
- i18n: `useLanguage` for all labels
|
||||
- Remove unused `t.marketing.nav.contact` references from this component
|
||||
|
||||
---
|
||||
|
||||
## JS Behaviors → React hooks/useEffect
|
||||
|
||||
### Scroll Reveal Hook
|
||||
- **Create `src/hooks/` directory** (does not exist yet)
|
||||
- Create `src/hooks/useScrollReveal.ts` — sets up a single `IntersectionObserver` targeting all `.reveal` elements, adds `.visible` class on intersection
|
||||
- Called once in `HomePage.tsx` via `useScrollReveal()`
|
||||
|
||||
### Nav Active on Scroll
|
||||
- `useEffect` in `MarketingNavbar.tsx` — watches `window.scroll`, adds `.active` class to nav link matching current section `id`
|
||||
|
||||
### Testimonial Carousel (TestimonialsSection.tsx)
|
||||
- React state: `currentPage` (0-indexed), 6 cards, 3 visible, 4 pages
|
||||
- `useEffect` auto-advances every 5s, resets on manual navigation
|
||||
- Prev/Next buttons + rendered dots
|
||||
- Window resize recalcs slide offset
|
||||
|
||||
### Typing Effect (HeroSection.tsx)
|
||||
- `useRef` on `.output-code` element
|
||||
- `useEffect` cycles through 3 LaTeX strings every 3500ms via `innerHTML` + cursor span
|
||||
|
||||
---
|
||||
|
||||
## CTA Links
|
||||
- "Try TexPixel", "Try Free", "Get Started" (Free/Monthly/Quarterly plans) → `<Link to="/app">`
|
||||
- "Buy Desktop" → `<Link to="/app">` (placeholder, no separate purchase flow)
|
||||
- Doc card links → `/docs`
|
||||
- Footer blog link → `/blog`
|
||||
- Pricing anchor `#pricing` → `<a href="#pricing">`
|
||||
|
||||
---
|
||||
|
||||
## Content / i18n
|
||||
- All text from the reference HTML is hardcoded in components (Chinese/English bilingual where reference already has it)
|
||||
- Existing `useLanguage` / `t` translations are used where keys already exist
|
||||
- New section text is **hardcoded** (not added to `translations.ts`) — the reference HTML content is the source of truth; full i18n for new sections is out of scope for this refactor
|
||||
|
||||
---
|
||||
|
||||
## Cleanup
|
||||
- Remove `contact` key from `marketing.nav` in `src/lib/translations.ts` (both `en` and `zh` blocks) — becomes dead code after ContactSection and `#contact` link are removed.
|
||||
|
||||
---
|
||||
|
||||
## What Does NOT Change
|
||||
- `src/App.tsx`, routing (`AppRouter.tsx`), auth system, workspace (`WorkspacePage`)
|
||||
- `index.css` Tailwind layer
|
||||
- Docs/Blog pages
|
||||
- `SEOHead` component usage in `HomePage.tsx`
|
||||
- `tailwind.config.js`
|
||||
@@ -3,6 +3,7 @@ import { useLanguage } from '../../contexts/LanguageContext';
|
||||
|
||||
const GUIDES = [
|
||||
{
|
||||
slug: 'image-to-latex',
|
||||
svgPaths: (
|
||||
<>
|
||||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
|
||||
@@ -15,6 +16,7 @@ const GUIDES = [
|
||||
metaZh: '5 分钟 · 最受欢迎',
|
||||
},
|
||||
{
|
||||
slug: 'copy-to-word',
|
||||
svgPaths: (
|
||||
<>
|
||||
<rect x="4" y="4" width="16" height="16" rx="2"/>
|
||||
@@ -27,6 +29,7 @@ const GUIDES = [
|
||||
metaZh: '4 分钟 · 扩展用户',
|
||||
},
|
||||
{
|
||||
slug: 'ocr-accuracy',
|
||||
svgPaths: (
|
||||
<>
|
||||
<circle cx="11" cy="11" r="8"/>
|
||||
@@ -39,6 +42,7 @@ const GUIDES = [
|
||||
metaZh: '6 分钟 · 进阶用户',
|
||||
},
|
||||
{
|
||||
slug: 'pdf-extraction',
|
||||
svgPaths: (
|
||||
<>
|
||||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
|
||||
@@ -71,7 +75,7 @@ export default function DocsSeoSection() {
|
||||
|
||||
<div className="doc-cards reveal">
|
||||
{GUIDES.map((g, i) => (
|
||||
<Link key={i} to="/docs" className="doc-card">
|
||||
<Link key={i} to={`/docs/${g.slug}`} 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">
|
||||
|
||||
@@ -1,35 +1,78 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useParams, Link } from 'react-router-dom';
|
||||
import { ArrowLeft } from 'lucide-react';
|
||||
import SEOHead from '../components/seo/SEOHead';
|
||||
import { useLanguage } from '../contexts/LanguageContext';
|
||||
import { loadContent, type ContentItem } from '../lib/content';
|
||||
|
||||
function estimateReadTime(html: string): number {
|
||||
const text = html.replace(/<[^>]+>/g, '');
|
||||
const words = text.split(/\s+/).filter(Boolean).length;
|
||||
return Math.max(2, Math.round(words / 200));
|
||||
}
|
||||
|
||||
function formatDate(dateStr: string, lang: string): string {
|
||||
const d = new Date(dateStr);
|
||||
if (isNaN(d.getTime())) return dateStr;
|
||||
return d.toLocaleDateString(lang === 'zh' ? 'zh-CN' : 'en-US', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
});
|
||||
}
|
||||
|
||||
export default function DocDetailPage() {
|
||||
const { slug } = useParams<{ slug: string }>();
|
||||
const { language } = useLanguage();
|
||||
const [content, setContent] = useState<ContentItem | null>(null);
|
||||
const [notFound, setNotFound] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setContent(null);
|
||||
setNotFound(false);
|
||||
if (slug) {
|
||||
loadContent('docs', language, slug)
|
||||
.then(setContent)
|
||||
.catch(() => setContent(null));
|
||||
.catch(() => setNotFound(true));
|
||||
}
|
||||
}, [slug, language]);
|
||||
|
||||
if (!content) {
|
||||
const zh = language === 'zh';
|
||||
|
||||
if (notFound) {
|
||||
return (
|
||||
<div className="max-w-3xl mx-auto py-20 px-6">
|
||||
<div className="animate-pulse space-y-4">
|
||||
<div className="h-4 bg-cream-200 rounded w-24" />
|
||||
<div className="h-8 bg-cream-200 rounded w-3/4" />
|
||||
<div className="h-4 bg-cream-200 rounded w-full" />
|
||||
<div className="docs-detail">
|
||||
<Link to="/docs" className="docs-back-link">
|
||||
<svg viewBox="0 0 24 24"><path d="M15 18l-6-6 6-6" /></svg>
|
||||
{zh ? '所有文档' : 'All docs'}
|
||||
</Link>
|
||||
<div style={{ textAlign: 'center', padding: '64px 0', color: 'var(--text-muted)' }}>
|
||||
<p style={{ fontSize: '18px', marginBottom: '8px' }}>
|
||||
{zh ? '文档未找到' : 'Doc not found'}
|
||||
</p>
|
||||
<Link to="/docs" style={{ color: 'var(--primary)', fontSize: '14px' }}>
|
||||
{zh ? '返回文档中心 →' : 'Back to docs →'}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!content) {
|
||||
return (
|
||||
<div className="docs-skeleton-wrap">
|
||||
<div className="skeleton-line" style={{ width: '80px', height: '14px', marginBottom: '44px' }} />
|
||||
<div className="skeleton-line" style={{ width: '60%', height: '44px', marginBottom: '16px' }} />
|
||||
<div className="skeleton-line" style={{ width: '40%', height: '16px', marginBottom: '48px' }} />
|
||||
<div className="skeleton-line" style={{ width: '100%', height: '16px' }} />
|
||||
<div className="skeleton-line" style={{ width: '92%', height: '16px' }} />
|
||||
<div className="skeleton-line" style={{ width: '96%', height: '16px' }} />
|
||||
<div className="skeleton-line" style={{ width: '80%', height: '16px' }} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const readTime = estimateReadTime(content.html);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SEOHead
|
||||
@@ -37,29 +80,54 @@ export default function DocDetailPage() {
|
||||
description={content.meta.description}
|
||||
path={`/docs/${slug}`}
|
||||
/>
|
||||
<div className="max-w-3xl mx-auto py-16 lg:py-20 px-6">
|
||||
{/* Back link */}
|
||||
<Link
|
||||
to="/docs"
|
||||
className="inline-flex items-center gap-1.5 text-sm text-ink-muted hover:text-ink transition-colors mb-8"
|
||||
>
|
||||
<ArrowLeft size={14} />
|
||||
{language === 'en' ? 'All docs' : '所有文档'}
|
||||
|
||||
<div className="docs-detail">
|
||||
{/* Back */}
|
||||
<Link to="/docs" className="docs-back-link">
|
||||
<svg viewBox="0 0 24 24"><path d="M15 18l-6-6 6-6" /></svg>
|
||||
{zh ? '所有文档' : 'All docs'}
|
||||
</Link>
|
||||
|
||||
{/* Doc body */}
|
||||
<article
|
||||
className="prose prose-lg prose-warm max-w-none
|
||||
prose-headings:font-display prose-headings:tracking-tight
|
||||
prose-h1:text-3xl prose-h1:font-bold
|
||||
prose-h2:text-2xl prose-h2:font-semibold prose-h2:mt-10
|
||||
prose-a:text-sage-700 prose-a:no-underline hover:prose-a:underline
|
||||
prose-code:text-sage-700 prose-code:bg-sage-50 prose-code:px-1.5 prose-code:py-0.5 prose-code:rounded-md prose-code:text-sm
|
||||
prose-pre:bg-ink prose-pre:text-cream-100 prose-pre:rounded-xl
|
||||
prose-img:rounded-xl
|
||||
prose-blockquote:border-sage-300 prose-blockquote:bg-sage-50/30 prose-blockquote:rounded-r-xl prose-blockquote:py-1"
|
||||
{/* Article header */}
|
||||
<div className="docs-article-header">
|
||||
{content.meta.tags.length > 0 && (
|
||||
<div className="docs-article-tags">
|
||||
{content.meta.tags.map(tag => (
|
||||
<span key={tag} className="docs-tag">{tag}</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<h1 className="docs-article-h1">{content.meta.title}</h1>
|
||||
<div className="docs-meta-row">
|
||||
<span>{formatDate(content.meta.date, language)}</span>
|
||||
<span className="docs-meta-sep">·</span>
|
||||
<span>{readTime} {zh ? '分钟阅读' : 'min read'}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Article body */}
|
||||
<div
|
||||
className="docs-prose"
|
||||
dangerouslySetInnerHTML={{ __html: content.html }}
|
||||
/>
|
||||
|
||||
{/* CTA */}
|
||||
<div className="docs-cta-box">
|
||||
<div className="docs-cta-title">
|
||||
{zh ? '准备好试试了吗?' : 'Ready to try it yourself?'}
|
||||
</div>
|
||||
<p className="docs-cta-desc">
|
||||
{zh
|
||||
? '上传一张公式图片,秒级获得 LaTeX 输出——无需注册。'
|
||||
: 'Upload a formula image and get LaTeX output in under a second — no sign-up needed.'}
|
||||
</p>
|
||||
<Link to="/app" className="btn btn-primary" style={{ display: 'inline-flex' }}>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2">
|
||||
<path d="M5 3l14 9-14 9V3z" />
|
||||
</svg>
|
||||
{zh ? '免费试用 TexPixel' : 'Try TexPixel Free'}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,10 +1,69 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { ArrowRight, BookOpen, FileText } from 'lucide-react';
|
||||
import SEOHead from '../components/seo/SEOHead';
|
||||
import { useLanguage } from '../contexts/LanguageContext';
|
||||
import { loadManifest, type ContentMeta } from '../lib/content';
|
||||
|
||||
function estimateReadTime(desc: string): number {
|
||||
return Math.max(2, Math.round(desc.split(/\s+/).length / 3));
|
||||
}
|
||||
|
||||
const DOC_ICONS: Record<string, JSX.Element> = {
|
||||
'getting-started': (
|
||||
<>
|
||||
<path d="M5 3l14 9-14 9V3z" />
|
||||
</>
|
||||
),
|
||||
'image-to-latex': (
|
||||
<>
|
||||
<rect x="3" y="3" width="18" height="14" rx="2" />
|
||||
<path d="M3 9h18" />
|
||||
<path d="M9 21l3-3 3 3" />
|
||||
</>
|
||||
),
|
||||
'supported-formats': (
|
||||
<>
|
||||
<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" />
|
||||
</>
|
||||
),
|
||||
'copy-to-word': (
|
||||
<>
|
||||
<rect x="4" y="4" width="16" height="16" rx="2" />
|
||||
<path d="M8 9h8M8 12h6M8 15h4" />
|
||||
</>
|
||||
),
|
||||
'ocr-accuracy': (
|
||||
<>
|
||||
<circle cx="11" cy="11" r="8" />
|
||||
<path d="m21 21-4.35-4.35" />
|
||||
</>
|
||||
),
|
||||
'pdf-extraction': (
|
||||
<>
|
||||
<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" />
|
||||
</>
|
||||
),
|
||||
faq: (
|
||||
<>
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3" />
|
||||
<line x1="12" y1="17" x2="12.01" y2="17" />
|
||||
</>
|
||||
),
|
||||
};
|
||||
|
||||
function DocIcon({ slug }: { slug: string }) {
|
||||
const paths = DOC_ICONS[slug] ?? DOC_ICONS['supported-formats'];
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8">
|
||||
{paths}
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default function DocsListPage() {
|
||||
const { language } = useLanguage();
|
||||
const [docs, setDocs] = useState<ContentMeta[]>([]);
|
||||
@@ -15,57 +74,63 @@ export default function DocsListPage() {
|
||||
});
|
||||
}, [language]);
|
||||
|
||||
const zh = language === 'zh';
|
||||
|
||||
return (
|
||||
<>
|
||||
<SEOHead title="Documentation" description="TexPixel documentation and guides" path="/docs" />
|
||||
<SEOHead
|
||||
title={zh ? 'TexPixel 文档中心' : 'TexPixel Documentation'}
|
||||
description={zh ? '公式识别入门指南、格式说明与常见问题。' : 'Guides, format references, and answers for getting the most out of TexPixel.'}
|
||||
path="/docs"
|
||||
/>
|
||||
|
||||
<div className="max-w-5xl mx-auto py-16 lg:py-20 px-6">
|
||||
<div className="docs-page">
|
||||
{/* Header */}
|
||||
<div className="mb-12">
|
||||
<div className="inline-flex items-center gap-2 px-3 py-1.5 bg-sage-50 border border-sage-100 rounded-full text-xs font-medium text-sage-700 mb-6">
|
||||
<BookOpen size={13} />
|
||||
{language === 'en' ? 'Documentation' : '文档中心'}
|
||||
</div>
|
||||
<h1 className="font-display text-4xl lg:text-5xl font-bold text-ink tracking-tight mb-4">
|
||||
{language === 'en' ? 'Learn TexPixel' : '了解 TexPixel'}
|
||||
<div className="docs-page-header">
|
||||
<div className="eyebrow">{zh ? '文档中心' : 'Documentation'}</div>
|
||||
<h1 className="docs-page-title">
|
||||
{zh ? '学习 TexPixel' : 'Learn TexPixel'}
|
||||
</h1>
|
||||
<p className="text-ink-secondary text-lg max-w-xl">
|
||||
{language === 'en'
|
||||
? 'Everything you need to get started with formula recognition.'
|
||||
: '公式识别入门所需的一切。'}
|
||||
<p className="docs-page-subtitle">
|
||||
{zh
|
||||
? '公式识别入门所需的一切——上传技巧、格式说明与常见问题。'
|
||||
: 'Everything you need to get the most out of formula recognition — upload tips, format guides, and FAQs.'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Docs grid */}
|
||||
<div className="space-y-4">
|
||||
{/* List */}
|
||||
<div className="docs-list">
|
||||
{docs.map((doc) => (
|
||||
<Link
|
||||
key={doc.slug}
|
||||
to={`/docs/${doc.slug}`}
|
||||
className="card p-6 flex items-start gap-5 group hover:border-sage-200 block"
|
||||
>
|
||||
<div className="w-11 h-11 bg-sage-50 rounded-xl flex items-center justify-center flex-shrink-0 group-hover:scale-110 transition-transform">
|
||||
<FileText size={20} className="text-sage-600" />
|
||||
<Link key={doc.slug} to={`/docs/${doc.slug}`} className="docs-article-card">
|
||||
<div className="docs-article-card-inner">
|
||||
<div className="docs-article-icon">
|
||||
<DocIcon slug={doc.slug} />
|
||||
</div>
|
||||
<div className="docs-article-body">
|
||||
<div className="docs-article-title">{doc.title}</div>
|
||||
<div className="docs-article-desc">{doc.description}</div>
|
||||
<div className="docs-article-meta">
|
||||
{doc.tags.slice(0, 2).map(tag => (
|
||||
<span key={tag} className="docs-tag">{tag}</span>
|
||||
))}
|
||||
<span className="docs-read-time">
|
||||
{estimateReadTime(doc.description)} {zh ? '分钟阅读' : 'min read'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<svg className="docs-article-arrow" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<path d="M9 18l6-6-6-6" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h2 className="font-display text-lg font-semibold text-ink mb-1 group-hover:text-sage-700 transition-colors">
|
||||
{doc.title}
|
||||
</h2>
|
||||
<p className="text-ink-secondary text-sm leading-relaxed">{doc.description}</p>
|
||||
</div>
|
||||
<ArrowRight size={16} className="text-ink-muted group-hover:text-sage-600 transition-colors mt-1 flex-shrink-0" />
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Empty state */}
|
||||
{docs.length === 0 && (
|
||||
<div className="card p-12 text-center">
|
||||
<p className="text-ink-muted text-sm">
|
||||
{language === 'en' ? 'Documentation coming soon.' : '文档即将发布。'}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{docs.length === 0 && (
|
||||
<div className="docs-empty">
|
||||
{zh ? '文档即将发布。' : 'Documentation coming soon.'}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1571,3 +1571,402 @@
|
||||
.reveal-delay-3 { transition-delay: 0.30s; }
|
||||
|
||||
@keyframes blink { 0%,100%{opacity:1} 50%{opacity:0} }
|
||||
|
||||
/* ═══════════════════════════════════════════════════════
|
||||
DOCS PAGES
|
||||
═══════════════════════════════════════════════════════ */
|
||||
|
||||
/* ── Docs list page ── */
|
||||
.docs-page {
|
||||
max-width: 820px;
|
||||
margin: 0 auto;
|
||||
padding: 64px 24px 96px;
|
||||
}
|
||||
|
||||
.docs-page-header {
|
||||
margin-bottom: 52px;
|
||||
}
|
||||
|
||||
.docs-page-header .eyebrow {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.docs-page-title {
|
||||
font-family: 'Lora', serif;
|
||||
font-size: 44px;
|
||||
font-weight: 700;
|
||||
line-height: 1.1;
|
||||
letter-spacing: -0.02em;
|
||||
color: var(--text-strong);
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.docs-page-subtitle {
|
||||
font-size: 17px;
|
||||
color: var(--text-body);
|
||||
line-height: 1.65;
|
||||
max-width: 480px;
|
||||
}
|
||||
|
||||
.docs-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.docs-article-card {
|
||||
background: var(--elevated);
|
||||
border: 1.5px solid var(--border);
|
||||
border-radius: var(--r-l);
|
||||
padding: 28px 32px;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: var(--shadow-soft);
|
||||
}
|
||||
|
||||
.docs-article-card:hover {
|
||||
transform: translateY(-2px);
|
||||
border-color: var(--primary-light);
|
||||
box-shadow: var(--shadow-float);
|
||||
}
|
||||
|
||||
.docs-article-card-inner {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.docs-article-icon {
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
background: var(--warm-wash);
|
||||
border-radius: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.docs-article-icon svg {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
stroke: var(--primary);
|
||||
fill: none;
|
||||
stroke-width: 1.8;
|
||||
}
|
||||
|
||||
.docs-article-body {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.docs-article-title {
|
||||
font-family: 'Lora', serif;
|
||||
font-size: 19px;
|
||||
font-weight: 700;
|
||||
color: var(--text-strong);
|
||||
margin-bottom: 6px;
|
||||
letter-spacing: -0.01em;
|
||||
transition: color 0.15s;
|
||||
}
|
||||
|
||||
.docs-article-card:hover .docs-article-title {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.docs-article-desc {
|
||||
font-size: 14px;
|
||||
color: var(--text-body);
|
||||
line-height: 1.6;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.docs-article-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.docs-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: 22px;
|
||||
padding: 0 10px;
|
||||
border-radius: 6px;
|
||||
background: var(--warm-wash);
|
||||
color: var(--primary);
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
text-transform: lowercase;
|
||||
}
|
||||
|
||||
.docs-read-time {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.docs-article-arrow {
|
||||
color: var(--text-muted);
|
||||
flex-shrink: 0;
|
||||
margin-top: 2px;
|
||||
transition: color 0.15s, transform 0.15s;
|
||||
}
|
||||
|
||||
.docs-article-card:hover .docs-article-arrow {
|
||||
color: var(--primary);
|
||||
transform: translateX(3px);
|
||||
}
|
||||
|
||||
.docs-empty {
|
||||
background: var(--elevated);
|
||||
border: 1.5px solid var(--border);
|
||||
border-radius: var(--r-l);
|
||||
padding: 48px;
|
||||
text-align: center;
|
||||
color: var(--text-muted);
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
/* ── Docs detail page ── */
|
||||
.docs-detail {
|
||||
max-width: 760px;
|
||||
margin: 0 auto;
|
||||
padding: 48px 24px 96px;
|
||||
}
|
||||
|
||||
.docs-back-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 14px;
|
||||
color: var(--text-muted);
|
||||
text-decoration: none;
|
||||
margin-bottom: 44px;
|
||||
transition: color 0.15s;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.docs-back-link:hover { color: var(--primary); }
|
||||
|
||||
.docs-back-link svg {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
stroke: currentColor;
|
||||
fill: none;
|
||||
stroke-width: 2;
|
||||
}
|
||||
|
||||
.docs-article-header {
|
||||
margin-bottom: 44px;
|
||||
padding-bottom: 36px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.docs-article-tags {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.docs-article-h1 {
|
||||
font-family: 'Lora', serif;
|
||||
font-size: 40px;
|
||||
font-weight: 700;
|
||||
line-height: 1.15;
|
||||
letter-spacing: -0.02em;
|
||||
color: var(--text-strong);
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.docs-meta-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
font-size: 13px;
|
||||
color: var(--text-muted);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.docs-meta-sep { opacity: 0.4; }
|
||||
|
||||
/* ── Docs prose body ── */
|
||||
.docs-prose {
|
||||
font-family: 'DM Sans', sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.8;
|
||||
color: var(--text-body);
|
||||
}
|
||||
|
||||
.docs-prose > h1:first-child { display: none; }
|
||||
|
||||
.docs-prose h2 {
|
||||
font-family: 'Lora', serif;
|
||||
font-size: 26px;
|
||||
font-weight: 700;
|
||||
color: var(--text-strong);
|
||||
margin: 52px 0 16px;
|
||||
letter-spacing: -0.01em;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.docs-prose h3 {
|
||||
font-family: 'Lora', serif;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: var(--text-strong);
|
||||
margin: 32px 0 10px;
|
||||
}
|
||||
|
||||
.docs-prose h4 {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: var(--text-strong);
|
||||
margin: 24px 0 8px;
|
||||
}
|
||||
|
||||
.docs-prose p { margin-bottom: 20px; }
|
||||
|
||||
.docs-prose ul,
|
||||
.docs-prose ol {
|
||||
padding-left: 24px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.docs-prose li { margin-bottom: 8px; }
|
||||
|
||||
.docs-prose strong {
|
||||
color: var(--text-strong);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.docs-prose a {
|
||||
color: var(--primary);
|
||||
text-decoration: underline;
|
||||
text-decoration-color: var(--primary-light);
|
||||
text-underline-offset: 3px;
|
||||
}
|
||||
|
||||
.docs-prose a:hover { text-decoration-color: var(--primary); }
|
||||
|
||||
.docs-prose code {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 13px;
|
||||
background: var(--warm-wash);
|
||||
color: var(--primary);
|
||||
padding: 2px 7px;
|
||||
border-radius: 6px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.docs-prose pre {
|
||||
background: var(--text-strong);
|
||||
color: #f1e6d8;
|
||||
border-radius: var(--r-m);
|
||||
padding: 20px 24px;
|
||||
overflow-x: auto;
|
||||
margin: 28px 0;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.docs-prose pre code {
|
||||
background: none;
|
||||
color: inherit;
|
||||
padding: 0;
|
||||
font-size: inherit;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
.docs-prose blockquote {
|
||||
border-left: 3px solid var(--primary-light);
|
||||
background: var(--warm-wash);
|
||||
padding: 16px 20px;
|
||||
border-radius: 0 var(--r-s) var(--r-s) 0;
|
||||
margin: 28px 0;
|
||||
color: var(--text-body);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.docs-prose table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 28px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.docs-prose th {
|
||||
background: var(--bg);
|
||||
color: var(--text-strong);
|
||||
font-weight: 700;
|
||||
padding: 10px 16px;
|
||||
text-align: left;
|
||||
border: 1px solid var(--border);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.docs-prose td {
|
||||
padding: 10px 16px;
|
||||
border: 1px solid var(--border);
|
||||
color: var(--text-body);
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.docs-prose tr:nth-child(even) td { background: var(--bg); }
|
||||
|
||||
.docs-prose .katex-display {
|
||||
margin: 32px 0;
|
||||
overflow-x: auto;
|
||||
padding: 20px;
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--r-m);
|
||||
}
|
||||
|
||||
/* ── Docs CTA box ── */
|
||||
.docs-cta-box {
|
||||
margin-top: 64px;
|
||||
padding: 44px 48px;
|
||||
background: linear-gradient(135deg, var(--warm-wash) 0%, var(--bg) 100%);
|
||||
border: 1.5px solid var(--border);
|
||||
border-radius: var(--r-xl);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.docs-cta-title {
|
||||
font-family: 'Lora', serif;
|
||||
font-size: 26px;
|
||||
font-weight: 700;
|
||||
color: var(--text-strong);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.docs-cta-desc {
|
||||
font-size: 15px;
|
||||
color: var(--text-body);
|
||||
margin-bottom: 28px;
|
||||
line-height: 1.65;
|
||||
}
|
||||
|
||||
/* ── Skeleton loader ── */
|
||||
.docs-skeleton-wrap {
|
||||
max-width: 760px;
|
||||
margin: 0 auto;
|
||||
padding: 48px 24px;
|
||||
}
|
||||
|
||||
.skeleton-line {
|
||||
background: var(--border);
|
||||
border-radius: 6px;
|
||||
margin-bottom: 14px;
|
||||
animation: skeleton-pulse 1.6s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes skeleton-pulse {
|
||||
0%, 100% { opacity: 0.7; }
|
||||
50% { opacity: 0.35; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user