update readme

This commit is contained in:
2026-09-21 00:05:49 +07:00
parent 13a9bfbfea
commit 61966dc40b
+230 -22
View File
@@ -1,35 +1,243 @@
# FunPen
FunPen — themed, skill-targeted printable worksheets for children aged 6-10.
Print-ready A4 math worksheets for children aged 6–10, built as HTML and rendered to PDF.
## Форматы
Every worksheet is a themed scene — spaceships, asteroids, cargo bays — where solving a
problem does something in the story: you fill a cargo hold, you charge a battery, you plot
a route. The math is ordinary; the wrapping is what makes a child pick the page up.
- **PDF** — A4 листы, готовые к распечатке
- **HTML** — промежуточный формат для просмотра всех страниц
- **JSON** — исходный файл с описанием задания
This repository is the authoring system behind them: a template pipeline, a set of
in-browser visual editors, and a deterministic HTML → PDF renderer.
## Оформление
<!-- GALLERY -->
Задания оформлены с лаконичной но элегантной графикой. Каждая строка с примером сопровождается небольшой картинкой — ребёнок "собирает" ресурсы, решая примеры. Помимо мелких картинок используются крупные фоновые изображения.
---
Для генерации изображений используется сервис **Banatie**.
## How it works
## JSON-формат задания
```
.template.html + .data.json → generate.mjs → .output.html → generate-pdf.mjs → PDF
(authored) (editor) (per type) (+ page PNGs) (Puppeteer)
```
В JSON-файле указываются:
Two inputs, one output, no hidden state:
- **Формат задания (промпт)** — шаблон вида `A * X + B = `, где A, X, B — переменные с диапазонами значений (например A от 1 до 5, X от 1 до 3, B от 4 до 8)
- **Папка с мелкими картинками** — иконки для каждого примера
- **Пути к крупным картинкам** — фоновые изображения для страниц
- **`.template.html`** is the document — near-final static markup, A4-sized, authored by hand
(with an agent's help) and readable on its own.
- **`.data.json`** is a thin overlay produced by the visual editor: positions, rotations,
scales, values. **It may not exist** — and when it doesn't, the template *is* the output.
- **`generate.mjs`** applies the overlay to the template. It is per task type, deterministic,
and the only thing that writes `.output.html`.
- **`generate-pdf.mjs`** renders that HTML with Puppeteer: A4, `printBackground`, zero
margins, `preferCSSPageSize`.
## Рабочий процесс
The same pipeline serves both paths — a human dragging things around in the editor, and an
agent regenerating the document from source. There is no second code path for "manual" edits.
Взаимодействие через Claude Code:
### Why the output is static HTML
1. Генерация крупных фоновых картинок для задания через Banatie
2. Генерация набора мелких картинок для примеров через Banatie
3. Описание идеи задания → генерация JSON-файла
4. Ревью JSON-файла
5. Генерация HTML с готовым заданием (с указанием количества страниц)
6. Ревью HTML
7. Генерация PDF для печати
Templates contain **no runtime scripting**. Nothing computes, generates or lays out content
when the page loads. A worksheet must look identical in the browser, in the screenshot and
in the PDF, so everything is resolved before render time — otherwise the print artifact and
the preview quietly drift apart.
---
## Document anatomy
Each document is up to four files in `tasks/{type}/docs/`:
| File | Purpose | Written by |
|---|---|---|
| `{docId}.md` | Problem patterns, page notes, non-obvious details | authoring (human + agent) |
| `{docId}.template.html` | The document itself — static A4 markup | authoring (human + agent) |
| `{docId}.data.json` | Editor overlay: positions, values, transforms | the editor's save endpoint |
| `{docId}.output.html` | Template with overlay applied | `generate.mjs` (not committed) |
The `.md` file never drives generation. It is context — *why* this page looks the way it
does, which patterns repeat, what must not be broken next time.
---
## Task types
Seven types, fourteen documents. Each type owns its folder, its rules, its generator and —
where the layout is hand-tuned — its editor.
| Type | What the child does | Editor |
|---|---|---|
| `space-exploration` | Solves problem cards decorated with icons, over a hero scene | — |
| `collecting-asteroids` | Matches asteroids to cargo ships so weights sum to capacity | ✓ |
| `asteroid-splitting` | Splits an asteroid of weight N into two unequal parts | ✓ |
| `cargo-filling` | Splits an asteroid to fill a hold to exactly 10 — addition across ten | ✓ |
| `space-route` | Follows a hex-graph route under arithmetic constraints between nodes | ✓ |
| `crystal-battery` | Two characters take turns filling a 10-cell battery — complements of 10 | — |
| `space-coloring` | Colours a full-page line-art scene | — |
Every type folder carries its own `CLAUDE.md` with layout rules, asset conventions and
generation constraints. **Type-specific rules live there, never in the root file** — that
separation is what keeps one type's quirks from leaking into another.
```
tasks/{type}/
CLAUDE.md — rules for this type
editor.html — visual editor (if the type needs one)
scripts/ — generate.mjs + validators
docs/ — the documents
temp/ — diffs and page screenshots (not committed)
```
---
## The editor
Four of the seven types have a visual editor at
`http://localhost:3300/tasks/{type}/editor.html?file={docId}`.
They exist because some things cannot be authored blind: whether an asteroid overlaps a
ship's nose, whether a row breathes, whether the page is balanced. Those are seen, not
specified.
All four share `src/editor/editor-core.js` — worksheet loading, page navigation, selection,
toolbar, toast, save, export. Type-specific logic (what is draggable, what a keypress means,
how state serialises) stays in each `editor.html`.
### Keyboard
Precision work is done from the keyboard, not the mouse. In `collecting-asteroids`:
| Key | Effect |
|---|---|
| `←` `→` `↑` `↓` | Move by 1 |
| `Shift` + arrows | Move by 5 |
| `[` `]` | Rotate (5°, or 1° with `Shift`) |
| `-` `+` | Change the asteroid's value — the sprite rescales to match |
| `,` `.` | Change the base scale for this asteroid type on this page |
The last one matters: it edits a *class* of objects, not one object. Tuning one asteroid is
fiddling; tuning every asteroid of that kind on the page is design.
### Millimetres, not pixels
The worksheet is a physical object. The editor establishes the scale from the rendered page:
```js
_mmToPx = pages[0].getBoundingClientRect().width / 210; // 210mm = A4 width
```
Positions are reasoned about in millimetres and rendered in pixels, so what is nudged on
screen is what comes out of the printer.
### Save
1. Editor `POST`s `/api/save-edits` with `{ taskType, docId, data }`
2. Server writes `docs/{docId}.data.json`
3. Server diffs it against the previous state → `temp/{docId}.diff.json`
4. Server runs `generate.mjs` → fresh `.output.html` + per-page PNGs
The diff is the point. After a session of dragging, the question "what actually changed?"
has a file for an answer instead of a guess — reviewable by a human, readable by an agent.
Two details that stop the editor from fighting its own tooling: live-reload is disconnected
inside editor pages (the page holds unsaved DOM state, a reload would discard it), and the
save endpoint de-duplicates rapid repeat POSTs.
---
## Creating a new document of an existing type
No script — this is deliberate. Documents differ in ways a generator cannot guess.
1. Copy the previous document's `.template.html` and `.md`, incrementing the number
2. Update the `.md` with the new problem patterns and page notes
3. Edit the template: new problems, different hero images, different footers
4. Add a card to the type's `index.html`
5. Run `generate.mjs`, then `generate-pdf.mjs`
`.data.json` starts absent and is created only if the document is opened in the editor.
---
## Creating a new task type
1. `tasks/{type-name}/`
2. Pick a theme, record it in the type's `CLAUDE.md` as `**Theme:** {name}`
3. Write `CLAUDE.md` — layout rules, asset conventions, generation constraints
4. Write `scripts/generate.mjs` — template + data → output. **It must call `postGenerate()`**,
which produces the per-page screenshots the rest of the workflow depends on
5. Write `editor.html` against `src/editor/editor-core.js` if the layout needs hand-tuning.
`tasks/asteroid-splitting/editor.html` is the reference implementation — the smallest one
6. Add a card to `tasks/index.html` and create the type's own `index.html`
---
## Verification
Worksheets fail visually — an overlap, a clipped footer, a number sitting off its asteroid —
and no assertion catches that. So every generation ends in a picture:
- `postGenerate()` runs `take-screenshots.mjs`, writing one PNG per page to `temp/`
- Stale screenshots are deleted first, so what is on disk always matches the current output
- Those PNGs are read back and checked; Chrome DevTools MCP covers interactive checks
- Editors are verified end to end — load, edit, save, regenerate — not just eyeballed
---
## Scripts are tools, not actors
Every script in this repository reads input, computes, and either prints JSON to stdout or
writes one specific file. Scripts do not chain, do not modify documents, and do not decide
anything.
```
run → node tasks/collecting-asteroids/scripts/verify.mjs --ships 10,10,14 --asteroids 3,2,1,4,5
stdout ← { "valid": false, "errors": ["ship 2 overflow by 3"] }
then → read the output, decide, edit the template
```
Four writers are allowed, and the list is closed: `generate.mjs` (template + data → output),
`generate-pdf.mjs` (the final artifact), `take-screenshots.mjs` (page PNGs), and the editor's
save endpoint. Everything else is read-only.
This is the boundary the project is built on. Generation, layout and PDF rendering are fully
deterministic and reproducible; judgement — what a worksheet should teach, how a page should
feel, whether an image is right — stays outside that line, where it can be reviewed.
---
## Themes and assets
Assets live in `assets/themes/{name}/`, each theme with its own `CLAUDE.md` listing available
images, palettes and usage rules. The space theme (`assets/themes/nms/`) carries the current
task types: spaceships, asteroids, planets, cargo bays, footer panoramas.
Images are generated through the Banatie API via a `/gen-image` skill, using existing images
from the same folder as references so a new asteroid looks like it belongs beside the old
ones. Newly generated files land in `raw/` and move up a level once processed — usually
after `remove-bg.mjs` flood-fills the white background away.
---
## Running it
```bash
pnpm install
pnpm dev # Tailwind watch + live-reload server on :3300
pnpm pdf -- tasks/{type}/docs/{docId}.output.html
```
Preview index: `http://localhost:3300/tasks/index.html` → type page → document.
Generated PDFs are committed under `output/pdf/`.
```bash
pnpm build:css # build Tailwind once, minified
pnpm remove-bg -- <file|dir> # strip white background from generated PNGs
```
---
## Stack
Plain HTML and CSS for the documents, Tailwind for the utility layer, vanilla JavaScript for
the editors, Node for the scripts. Puppeteer renders the PDFs, sharp processes the images.
No framework, no build step for the worksheets themselves — a document is a file you can open.