From 61966dc40bfe2fa4e3ba1a44c1727bec934ffb9f Mon Sep 17 00:00:00 2001 From: Oleg Proskurin Date: Mon, 21 Sep 2026 00:05:49 +0700 Subject: [PATCH] update readme --- README.md | 252 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 230 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index bb968f2..d51738f 100644 --- a/README.md +++ b/README.md @@ -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. -## Оформление + -Задания оформлены с лаконичной но элегантной графикой. Каждая строка с примером сопровождается небольшой картинкой — ребёнок "собирает" ресурсы, решая примеры. Помимо мелких картинок используются крупные фоновые изображения. +--- -Для генерации изображений используется сервис **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 -- # 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.