feat: asteroids

This commit is contained in:
Oleg Proskurin 2026-04-09 19:31:46 +07:00
parent cb183d687c
commit b6579f29a8
37 changed files with 151 additions and 30 deletions

View File

@ -179,12 +179,56 @@ Puppeteer settings for A4 worksheets:
- Margins: zero (CSS handles margins)
- `preferCSSPageSize: true`
## 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
```
- **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
### How it works
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. Add a link to the new document in `output/index.html` (card with title and path)
6. Run `pnpm pdf -- output/html/<file>.html` to create PDF
7. Preview with `pnpm preview` at localhost:3000
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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 706 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1010 KiB

After

Width:  |  Height:  |  Size: 620 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 701 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 893 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 974 KiB

After

Width:  |  Height:  |  Size: 580 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 737 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 927 KiB

After

Width:  |  Height:  |  Size: 527 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 817 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 999 KiB

After

Width:  |  Height:  |  Size: 665 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 851 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 857 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 668 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 720 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 608 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 611 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 654 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 604 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 938 KiB

After

Width:  |  Height:  |  Size: 416 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 423 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 740 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 556 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 624 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 785 KiB

After

Width:  |  Height:  |  Size: 343 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 581 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 852 KiB

After

Width:  |  Height:  |  Size: 427 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 835 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 983 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 808 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

@ -10,7 +10,7 @@ module.exports = {
"output/html/**/*.html",
"assets/**/*"
],
port: 3000,
port: 3300,
open: false,
notify: false,
ui: false

View File

@ -1,7 +1,7 @@
{
"id": "collecting-asteroids-1",
"title": "Собери Астероиды",
"description": "Match asteroids to cargo ships by weight sum — 3 pages with increasing difficulty",
"description": "9-page asteroid-matching worksheet: match asteroids to cargo ships by weight sum, increasing difficulty",
"labels": {
"title": "Собери Астероиды",
"subtitle": "Загрузи трюмы кораблей!",
@ -14,41 +14,118 @@
},
"layout": {
"type": "asteroid-matching",
"shipsPerPage": 3
"shipsPerPage": 3,
"asteroidTypesPerPage": 2,
"asteroidTypeRatio": "70:30"
},
"pages": [
{
"heroImage": "assets/hero-images/spaceship2.jpeg",
"footerImage": "assets/footers/planet3.jpeg",
"heroDirection": "row-reverse",
"overlapLevel": 30,
"ships": [
{ "capacity": 9, "asteroids": [3, 2, 4, 5] },
{ "capacity": 11, "asteroids": [5, 4, 2, 6] },
{ "capacity": 8, "asteroids": [1, 3, 4, 2] }
]
{ "capacity": 5, "fillCount": 2 },
{ "capacity": 5, "fillCount": 2 },
{ "capacity": 7, "fillCount": 3 }
],
"heroImage": "assets/hero-images/splitters/splitter1.png",
"footerImage": "assets/footers/cabin1.jpeg",
"heroDirection": "row-reverse",
"cargoBays": ["cargo-bay1", "cargo-bay2", "cargo-bay3"],
"asteroidTypes": ["asteroid1", "asteroid6"]
},
{
"heroImage": "assets/hero-images/spaceship5.jpeg",
"footerImage": "assets/footers/planet5.jpeg",
"ships": [
{ "capacity": 7, "fillCount": 3 },
{ "capacity": 7, "fillCount": 3 },
{ "capacity": 10, "fillCount": 4 }
],
"heroImage": "assets/hero-images/splitters/splitter2.png",
"footerImage": "assets/footers/cabin2.jpeg",
"heroDirection": "row",
"overlapLevel": 50,
"ships": [
{ "capacity": 10, "asteroids": [6, 1, 3, 4] },
{ "capacity": 12, "asteroids": [5, 4, 3, 6] },
{ "capacity": 14, "asteroids": [6, 5, 2, 1, 3] }
]
"cargoBays": ["cargo-bay4", "cargo-bay5", "cargo-bay6"],
"asteroidTypes": ["asteroid3", "asteroid9"]
},
{
"heroImage": "assets/hero-images/spaceship7.jpeg",
"footerImage": "assets/footers/planet8.jpeg",
"heroDirection": "row-reverse",
"overlapLevel": 70,
"ships": [
{ "capacity": 13, "asteroids": [5, 4, 3, 1, 6] },
{ "capacity": 10, "asteroids": [4, 4, 2, 3] },
{ "capacity": 15, "asteroids": [6, 5, 3, 1, 4] }
]
{ "capacity": 5, "fillCount": 2 },
{ "capacity": 5, "fillCount": 3 },
{ "capacity": 6, "fillCount": 3 }
],
"heroImage": "assets/hero-images/splitters/splitter3.png",
"footerImage": "assets/footers/cabin3.jpeg",
"heroDirection": "row-reverse",
"cargoBays": ["cargo-bay7", "cargo-bay8", "cargo-bay9"],
"asteroidTypes": ["asteroid5", "asteroid11"]
},
{
"ships": [
{ "capacity": 8, "fillCount": 2 },
{ "capacity": 8, "fillCount": 3 },
{ "capacity": 6, "fillCount": 2 }
],
"heroImage": "assets/hero-images/splitters/splitter4.png",
"footerImage": "assets/footers/cabin4.jpeg",
"heroDirection": "row",
"cargoBays": ["cargo-bay1", "cargo-bay2", "cargo-bay3"],
"asteroidTypes": ["asteroid2", "asteroid8"]
},
{
"ships": [
{ "capacity": 4, "fillCount": 1 },
{ "capacity": 4, "fillCount": 2 },
{ "capacity": 4, "fillCount": 3 }
],
"heroImage": "assets/hero-images/splitters/splitter5.png",
"footerImage": "assets/footers/cabin5.jpeg",
"heroDirection": "row-reverse",
"cargoBays": ["cargo-bay4", "cargo-bay5", "cargo-bay6"],
"asteroidTypes": ["asteroid4", "asteroid10"]
},
{
"ships": [
{ "capacity": 10, "fillCount": 4 },
{ "capacity": 10, "fillCount": 3 },
{ "capacity": 8, "fillCount": 3 }
],
"heroImage": "assets/hero-images/splitters/splitter6.png",
"footerImage": "assets/footers/cabin6.jpeg",
"heroDirection": "row",
"cargoBays": ["cargo-bay7", "cargo-bay8", "cargo-bay9"],
"asteroidTypes": ["asteroid7", "asteroid13"]
},
{
"ships": [
{ "capacity": 6, "fillCount": 2 },
{ "capacity": 9, "fillCount": 3 },
{ "capacity": 12, "fillCount": 4 }
],
"heroImage": "assets/hero-images/splitters/splitter7.png",
"footerImage": "assets/footers/cabin7.jpeg",
"heroDirection": "row-reverse",
"cargoBays": ["cargo-bay1", "cargo-bay2", "cargo-bay3"],
"asteroidTypes": ["asteroid12", "asteroid16"]
},
{
"ships": [
{ "capacity": 14, "fillCount": 3 },
{ "capacity": 10, "fillCount": 2 },
{ "capacity": 16, "fillCount": 4 }
],
"heroImage": "assets/hero-images/splitters/splitter8.png",
"footerImage": "assets/footers/cabin8.jpeg",
"heroDirection": "row",
"cargoBays": ["cargo-bay4", "cargo-bay5", "cargo-bay6"],
"asteroidTypes": ["asteroid14", "asteroid15"]
},
{
"ships": [
{ "capacity": 5, "fillCount": 2 },
{ "capacity": 10, "fillCount": 4 },
{ "capacity": 15, "fillCount": 5 }
],
"heroImage": "assets/hero-images/splitters/splitter9.png",
"footerImage": "assets/footers/cabin9.jpeg",
"heroDirection": "row-reverse",
"cargoBays": ["cargo-bay7", "cargo-bay8", "cargo-bay9"],
"asteroidTypes": ["asteroid1", "asteroid9"]
}
]
}