refactor: new structure

This commit is contained in:
2026-04-21 19:49:08 +07:00
parent 99bc387244
commit ba676c2924
45 changed files with 12097 additions and 807 deletions
+157 -187
View File
@@ -1,15 +1,13 @@
# Math Tasks Generator
Printable math worksheet generator (A4 PDF) for children aged 7–9. Claude Code-driven workflow: user describes a task idea → Claude generates JSON config → creates HTML pages with Tailwind CSS → converts to PDF via Puppeteer.
**No template engine.** Claude Code generates fresh HTML pages directly from JSON task configs each time.
Printable math worksheet generator (A4 PDF) for children aged 7–9. Claude Code is the central orchestrator: user describes a task idea → Claude creates/edits HTML templates → optional visual editor fine-tuning → PDF via Puppeteer.
## Commands
```bash
pnpm build:css # Build Tailwind CSS (minified)
pnpm build:css:watch # Watch mode for CSS
pnpm preview # Serve HTML at localhost:3000 with live-reload
pnpm preview # Serve at localhost:3300 with live-reload
pnpm dev # CSS watch + preview server (concurrent)
pnpm pdf -- <file> # Convert HTML file to PDF
pnpm remove-bg -- <file|dir> # Remove white background from PNG icons
@@ -20,230 +18,202 @@ Generate images via `/gen-image` skill (uses Banatie API, reads `BANATIE_KEY` fr
## Directory Structure
```
tasks/
index.html — document index (Claude updates manually)
{task-type}/ — one folder per task type
CLAUDE.md — type-specific rules and conventions
editor.html — visual editor for this task type
scripts/ — type-specific scripts (validation, generation)
docs/ — document instances
{docId}.md — task description, problem patterns, notes (Claude reads/writes)
{docId}.template.html — near-final HTML maintained by Claude
{docId}.data.json — editor data layer (positions, values — may not exist)
{docId}.output.html — generated from template+data (gitignored)
temp/ — diffs and transient data (gitignored)
src/
styles/main.css — Tailwind source with A4/print styles
templates/space-base.html — Base template (layout/styling reference)
examples/space-worksheet2.html — Finished 3-page example (output reference)
editor/editor-core.js — shared editor framework
scripts/
generate-pdf.mjs — HTML → PDF via Puppeteer
remove-bg.mjs — Remove white background from PNGs (flood fill)
tasks/ — JSON task definition files
assets/
hero-images/ — spaceship1-6.jpeg (header hero images)
footers/ — planet1-6.jpeg (footer panorama images)
icons/pack1/ — minerals1-6 + plants1-6, 16 variants each ({name}-{row}-{col}.png)
backgrounds/ — large background images per theme (~1200x1700px)
output/
html/ — generated HTML (gitignored)
pdf/ — generated PDFs (gitignored)
css/ — built Tailwind CSS (gitignored)
generate-pdf.mjs — HTML → PDF via Puppeteer (all types)
remove-bg.mjs — Remove white background from PNGs
split-sprites.mjs — Split sprite sheets into icons
generate-problems.mjs — Problem generation utility
styles/main.css — Tailwind source with A4/print styles
templates/space-base.html — Shared base template reference
assets/ — images (hero, footer, icons, items, backgrounds)
output/pdf/ — generated PDFs (gitignored)
```
## JSON Task Format
## Task Types
Each task is a JSON file in `tasks/` defining a multi-page document. See `tasks/space-exploration-1.json` for a real example.
Each task type lives in `tasks/{type-name}/` with its own CLAUDE.md, scripts, editor, and document instances. Current types:
```json
{
"id": "space-exploration-1",
"title": "Исследуй Планету",
"description": "3-page space worksheet: addition ±, multiply by 5 ±, multiplication table ±",
"labels": {
"title": "Исследуй Планету",
"subtitle": "Собери ресурсы, решая примеры!",
"footerBubble": "Итого собрано на планете:"
},
"theme": {
"style": "space",
"template": "space-base",
"icons": "assets/icons/pack1/"
},
"layout": {
"columns": 2,
"problemsPerPage": 20
},
"pages": [
{
"task": "A + B ± C, где A от 8 до 16, B от 4 до 8, C от 1 до 3. Знак ± выбирается случайно",
"problemCount": 20,
"heroImage": "assets/hero-images/spaceship2.jpeg",
"footerImage": "assets/footers/planet3.jpeg",
"heroDirection": "row-reverse"
}
]
}
- **space-exploration** — math problem worksheets with hero images and icon-decorated cards
- **collecting-asteroids** — match asteroids to cargo ships by weight sum
- **space-route** — navigate hex graph routes with difficulty constraints
**Type-specific rules** (layout details, asset conventions, generation algorithms) live in each type's own CLAUDE.md — NOT in this file. Read `tasks/{type}/CLAUDE.md` before working on any task of that type.
## Document File Set
Each document instance consists of up to 4 files in `tasks/{type}/docs/`:
| File | Purpose | Owner |
|------|---------|-------|
| `{docId}.md` | Task description, problem patterns, page notes | Claude writes, user reviews |
| `{docId}.template.html` | Near-final static HTML, maintained by Claude | Claude |
| `{docId}.data.json` | Editor data layer (positions, values) | Editor UI via save endpoint |
| `{docId}.output.html` | Final HTML = template + data applied | generate.mjs (gitignored) |
**Key:** `.data.json` may not exist — and that's fine. When it doesn't exist, `.template.html` IS the final output.
**The `.md` file does NOT directly drive HTML generation.** It's context for Claude — problem patterns, non-obvious details (e.g., hero orientation per page), things Claude needs to know when editing the template. It does NOT contain titles, footers, or asset paths that are already in the HTML.
## Creating New Task Types
1. Create folder `tasks/{type-name}/`
2. Create `CLAUDE.md` with type-specific rules
3. Create `scripts/generate.mjs` — template + data → output pipeline
4. Create `editor.html` importing `src/editor/editor-core.js` + type-specific logic
5. Create `scripts/` for any validation/generation utilities
6. Add section to `tasks/index.html`
## Creating New Documents
When the user asks to create a new document of an existing type:
1. Copy the previous document's files with incremented number:
- `{type}-N.template.html` → `{type}-(N+1).template.html`
- `{type}-N.md` → `{type}-(N+1).md`
2. Update the `.md` file with new problem patterns / page descriptions
3. Edit `.template.html` as needed (new problems, different assets, etc.)
4. Add card to `tasks/index.html`
**No script for this — Claude does it manually.** `.data.json` starts empty (created only when the editor is used).
## Adding Pages to Existing Documents
1. Read the `.md` file to understand existing patterns
2. Edit `.template.html` directly — add new page markup
3. Update `.md` if new patterns were added
## Generation Pipeline
```
.template.html + .data.json (if exists) → scripts/generate.mjs → .output.html
```
### Fields
- Same pipeline for both Claude-initiated regeneration and editor-save flow
- If no `.data.json` exists → template.html copied as-is to output.html
- `generate.mjs` is a per-task-type script, not a global one
- **pages[]** — array of pages, each with its own task and images
- **pages[].task** — free-text description of problems to generate (Claude reads this and creates concrete expressions)
- **pages[].problemCount** — how many problems on this page
- **pages[].heroImage** — path to hero image for this page
- **pages[].footerImage** — path to footer panorama for this page
- **pages[].heroDirection** — `row` (hero left) or `row-reverse` (hero right)
- **layout.columns** — 1 or 2 column layout
- **layout.problemsPerPage** — max problems per A4 page
- **labels** — all visible text (no hardcoded language)
- **theme.style** — visual theme name (e.g. "space")
- **theme.template** — which base template to use (e.g. "space-base")
- **theme.icons** — path to icon directory for problem cards
## Editor System
## Space Base Template
Each task type has its own `editor.html` in its folder. All editors share `src/editor/editor-core.js` for common functionality (drag, selection, keyboard, toolbar, save, toast).
Base template: `src/templates/space-base.html`
Finished example: `src/examples/space-worksheet2.html` (3-page output matching `tasks/space-worksheet2.json`)
**Opening in editor:**
```
http://localhost:3300/tasks/{type}/editor.html?file={docId}
```
The base template defines the visual design for all space-themed worksheets. The example shows a complete generated document. When generating a new worksheet, **read both files first**, then produce a new HTML with these variations:
**Save flow:**
1. User clicks Save in editor
2. Editor POST `/api/save-edits` with `{ taskType, docId, data }`
3. Server writes `docs/{docId}.data.json`
4. Server computes diff → writes `temp/{docId}.diff.json`
5. Server runs `generate.mjs` to regenerate output.html
### What to vary per worksheet
**Claude reviewing editor changes:**
```bash
cat tasks/{type}/temp/{docId}.diff.json
```
1. **Hero image** — pick one from `assets/hero-images/spaceship{1-6}.jpeg`
2. **Footer image** — pick one from `assets/footers/planet{1-6}.jpeg`
3. **Hero position** — use `flex-row-reverse` class (hero right) or default flex direction (hero left) on the header div
4. **Problem icons** — pick from `assets/icons/pack1/` (minerals and plants, any variant `{name}-{row}-{col}.png`). **Every icon must be unique across the entire document** (no repeats across pages). Shuffle minerals and plants together randomly — do not sort by type
5. **Problem alignment** — for each problem card, randomly assign `justify-start`, `justify-center`, or `justify-end` within its grid column. No repeating patterns — should look chaotic/scattered
6. **Title, subtitle, footer text** — set from the task description
7. **Problems** — generate directly from the `task` text in JSON config. Claude reads the free-text description and creates concrete math expressions
## Index Page
### Layout structure (do not change)
`tasks/index.html` — Claude updates manually when creating new documents. Each document has View and Edit links. Different task types can have different card styles.
- Page: `w-[210mm] h-[297mm]` white container
- Footer: absolute bottom, `h-[80mm]`, with white-to-transparent fade on top. **No `overflow-hidden`** on footer container (causes 1px hairline artifact in PDF)
- Footer bubble: absolute `bottom-[12mm]`, pill-shaped with semi-transparent white bg
- Content area: `px-[12mm] pt-[4mm] pb-[65mm]` flex column
- Header: hero image `w-[48%]` + title block centered, use `flex-row-reverse` for hero-right
- Footer gradient: `linear-gradient(to bottom, white 0%, rgba(255,255,255,0.6) 25%, transparent 50%)` with `h-full`
- Problems: `grid grid-cols-2 gap-x-3 gap-y-[3px]` — 20 problems total
- Each problem: 58px icon outside pill + `text-[1.2rem]` expression + `w-16` answer underline inside pill
- Font: Nunito via Google Fonts
- Uses Tailwind CDN (`<script src="https://cdn.tailwindcss.com">`)
**Preview:** `http://localhost:3300/tasks/index.html`
### Color palette (do not change)
## Script Principles (Orchestrator Pattern)
| Element | Hex |
|---------|-----|
| Title text | `text-indigo-950` (#1e1b4b) |
| Subtitle | `text-indigo-400` (#6366f1) |
| Card border | `border-indigo-100` (#e0e7ff) |
| Answer underline | `border-indigo-300` (#a5b4fc) |
| Card bg gradient | `from-white to-indigo-50/40` |
| Footer bubble border | `border-indigo-200` |
Claude Code is the central orchestrator. Scripts are tools for Claude, not autonomous actors.
**Rules:**
- Scripts read input → compute → output to stdout (JSON) or write a single specific output file
- Scripts NEVER chain together
- Scripts NEVER auto-modify documents or make decisions
- Claude runs a script, reads its output, decides what to do next
**Allowed write exceptions:**
- `generate.mjs` — writes `.output.html` (template+data→output, deterministic)
- `generate-pdf.mjs` — writes PDF (final artifact)
- `/api/save-edits` — writes `.data.json` + `.diff.json` + runs `generate.mjs` (user-initiated from editor UI)
**Example of correct flow:**
```
Claude: node tasks/collecting-asteroids/scripts/verify.mjs --ships 10,10,14 --asteroids 3,2,1,4,5
Script → stdout: { "valid": false, "errors": ["ship 2 overflow by 3"] }
Claude: *reads output, decides how to fix, edits template*
```
## Proactive Rule Saving
During sessions, when the user gives feedback or instructions specific to a task type, **proactively suggest saving it as a rule**:
> "Сохранить это как правило для {type}?"
If confirmed → write to `tasks/{type}/CLAUDE.md`.
General rules that apply to all types → root CLAUDE.md (this file).
## HTML Generation Guidelines
When generating HTML worksheets:
- **OUTPUT MUST BE STATIC HTML.** Generated HTML files in `output/html/` must contain only static markup — no embedded `<script>` that computes, generates, or modifies content at runtime. If you need algorithms or calculations (graph generation, random placement, etc.), run them in a separate Node.js script in `src/scripts/`, then write the computed results as static HTML to `output/html/`. The user works with the final HTML directly and expects it to be stable across page reloads.
- **Always read** `src/templates/space-base.html` (structure) and `src/examples/space-worksheet.html` (finished output) before generating
- **OUTPUT MUST BE STATIC HTML.** Template files must contain only static markup — no embedded `<script>` that computes, generates, or modifies content at runtime. Tailwind CDN `<script>` tag is the only exception.
- **Page size:** A4 = 210mm × 297mm
- **CSS:** Uses Tailwind CDN in the HTML `<script>` tag (not the compiled CSS file) — this is the only allowed script
- **Page breaks:** Use `break-after: page` between pages
- **Icons:** 58×58px inline images from `assets/icons/` next to each problem (outside the pill card)
- **Page breaks:** `break-after: page` between pages
- **Fonts:** Nunito from Google Fonts via `<link>`
- **Images in PDF:** Use local file paths (not URLs). Puppeteer resolves `file://` protocol
- **Embed images** as base64 data URIs when possible for reliable PDF rendering
- **Images in PDF:** Use local file paths. Puppeteer resolves `file://` protocol. Embed as base64 data URIs when possible for reliable rendering.
## Image Generation
Use the `/gen-image` skill to generate images via the Banatie API. The skill has its own script and full API documentation in `.claude/skills/gen-image/`. Auth via `BANATIE_KEY` in `.env`. Rate limit: 100 requests/hour.
Use the `/gen-image` skill to generate images via the Banatie API. Auth via `BANATIE_KEY` in `.env`. Rate limit: 100 requests/hour.
### Reference Policy for This Project
### Reference Policy
When generating new assets for `assets/items/`, use existing images from the same subfolder as `--ref` to maintain visual consistency across the set. For example, when creating `asteroid2.png`, reference `asteroid1.png` from the same folder. The gen-image skill's default policy is to ask before using refs — this project overrides that: use refs automatically for items in the same asset group.
When generating new assets for `assets/items/`, use existing images from the same subfolder as `--ref` to maintain visual consistency. The gen-image skill's default policy is to ask before using refs — this project overrides that: use refs automatically for items in the same asset group.
Style reference chain for themed assets:
- `assets/items/asteroids/` — use any existing asteroid as ref for new ones
- `assets/items/crystals/` — use any existing crystal as ref for new ones
- `assets/icons/` — generate independently (icons have their own pipeline via icon packs)
- `assets/hero-images/`, `assets/footers/`, `assets/backgrounds/` — generate independently, no refs needed
Style reference chain:
- `assets/items/asteroids/` — use any existing asteroid as ref
- `assets/items/crystals/` — use any existing crystal as ref
- `assets/icons/` — generate independently (icon packs have their own pipeline)
- `assets/hero-images/`, `assets/footers/`, `assets/backgrounds/` — generate independently
## Background Removal
Script `src/scripts/remove-bg.mjs` removes white backgrounds from PNG icons using flood-fill from edges (like magic wand in Photoshop). Uses sharp. White areas inside objects are preserved.
`src/scripts/remove-bg.mjs` — flood-fill white background removal from PNGs (like magic wand). Uses sharp.
```bash
node src/scripts/remove-bg.mjs <file|dir> [--threshold N] [--fuzz N]
```
- `--threshold` — whiteness threshold for R,G,B (default: 230, lower = more aggressive)
- `--fuzz` — anti-alias radius in pixels at the boundary (default: 0)
- Default input: `assets/icons/pack2/`
This is optional — not all icons need it. After generating/splitting new icons, ask the user if they want background removal on specific files.
Optional — not all icons need it. Ask user after generating/splitting new icons.
## PDF Generation
Puppeteer settings for A4 worksheets:
- Format: A4
- `printBackground: true` (required for background images)
- Margins: zero (CSS handles margins)
- `preferCSSPageSize: true`
## Space Route Task Type
Generator script: `src/scripts/generate-space-route.mjs`
Output: `output/html/space-route-{N}.html`
### Generation rules
- **Grid:** 7×9 hex grid with jitter, nodes evenly distributed across map area
- **Start nodes:** Enemy = top-left corner, Player = bottom-left corner. Start nodes trimmed to exactly 2 edges
- **No asteroids on route nodes** — decorative objects must not be placed on any node belonging to enemy or player routes, nor on neighbors of start nodes
- **Routes use waypoints** — route goes toward an offset waypoint for ~40% of steps, then changes direction toward the final target. This prevents obvious straight-line paths
- **Route length:** 7-9 steps for enemy, player gets 10 empty cells
- **Ship orientation:** check each ship image and set `flipEnemy`/`flipPlayer` flags so all ships face right
- **Difficulty via diffRange:** `[minNodeValue, maxSpread]` — first number = minimum node value, second = max |difference| between connected neighbors
## Visual Position Editor
`output/html/editor.html` — a standalone drag-and-drop editor for repositioning elements (asteroids, icons, etc.) in generated worksheets.
### Usage
```
http://localhost:3300/html/editor.html?file=collecting-asteroids-1.html
```bash
node src/scripts/generate-pdf.mjs tasks/{type}/docs/{docId}.output.html
```
- **Mouse drag** — move elements freely
- **Arrow keys** — nudge 1mm (Shift+Arrow = 5mm)
- **Copy All JSON** — export all positions to clipboard
- **Copy Changes** — export only moved elements
- **`window.getConfig()`** — same via console
Puppeteer: A4, `printBackground: true`, zero margins, `preferCSSPageSize: true`.
### How it works
## Workflow Summary
The editor `fetch()`es the worksheet HTML and injects it into its own DOM, adding drag-and-drop behavior. The worksheet HTML stays clean — no editor code touches it. The editor identifies draggable elements via CSS selector (e.g., `img[src*="pack3-asteroids"]` for asteroids).
### Applying positions from editor
User copies JSON from editor, gives it to Claude. Claude applies positions via a Node.js one-liner:
```js
// Pattern: regex-replace positions in order per page
html.replace(
/(<div class="absolute" style="left: )-?\d+mm; top: -?\d+mm(;"><div class="relative w-\[88px\]...pack3-asteroids)/g,
(match, prefix, suffix) => { /* replace with new left/top from JSON */ }
);
```
### When to use this pattern
Use the visual editor approach whenever a worksheet requires **manual fine-tuning of element positions** — any time automated placement can't fully replace human judgment (e.g., avoiding overlaps with irregular ship shapes, achieving visual balance). The workflow is:
1. Claude generates initial positions algorithmically
2. User opens editor, adjusts positions visually
3. User exports JSON → Claude applies to HTML
4. Generate PDF
This pattern can be extended to other element types by adding new CSS selectors to the editor's identification logic.
## Workflow
1. User describes the math task idea (or runs `/new-doc`)
2. Claude creates/updates a JSON config in `tasks/` with `pages[].task` descriptions
3. Claude reads `src/templates/space-base.html` + `src/examples/space-worksheet.html` as references
4. Claude generates HTML file in `output/html/` — creates concrete problems from `task` text, assigns unique shuffled icons, builds all pages
5. **(Optional)** User fine-tunes element positions via visual editor at `editor.html?file=<name>.html`, exports JSON, Claude applies
6. Add a link to the new document in `output/index.html` (card with title and path)
7. Run `node src/scripts/generate-pdf.mjs output/html/<file>.html` to create PDF
8. Preview with `pnpm preview` at localhost:3300
1. User describes the task idea (or runs `/new-doc`)
2. Claude creates/updates `.md` file with patterns and notes
3. Claude creates/edits `.template.html` (reading type CLAUDE.md and base references)
4. Run `generate.mjs` to create `.output.html`
5. **(Optional)** User opens editor → fine-tunes positions → Save → data.json + output.html updated
6. Claude reviews diff if asked: `cat tasks/{type}/temp/{docId}.diff.json`
7. Add card to `tasks/index.html`
8. Generate PDF: `node src/scripts/generate-pdf.mjs tasks/{type}/docs/{docId}.output.html`
9. Preview at `http://localhost:3300/tasks/index.html`