feat: asteroids
50
CLAUDE.md
|
|
@ -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
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 706 KiB |
|
Before Width: | Height: | Size: 1010 KiB After Width: | Height: | Size: 620 KiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 701 KiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 893 KiB |
|
Before Width: | Height: | Size: 974 KiB After Width: | Height: | Size: 580 KiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 737 KiB |
|
Before Width: | Height: | Size: 927 KiB After Width: | Height: | Size: 527 KiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 817 KiB |
|
Before Width: | Height: | Size: 999 KiB After Width: | Height: | Size: 665 KiB |
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 851 KiB |
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 857 KiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 668 KiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 720 KiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 608 KiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 611 KiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 654 KiB |
|
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 604 KiB |
|
Before Width: | Height: | Size: 938 KiB After Width: | Height: | Size: 416 KiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 423 KiB |
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 740 KiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 556 KiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 624 KiB |
|
Before Width: | Height: | Size: 785 KiB After Width: | Height: | Size: 343 KiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 581 KiB |
|
Before Width: | Height: | Size: 852 KiB After Width: | Height: | Size: 427 KiB |
|
Before Width: | Height: | Size: 1.6 MiB After Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 1.6 MiB After Width: | Height: | Size: 835 KiB |
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.6 MiB After Width: | Height: | Size: 983 KiB |
|
Before Width: | Height: | Size: 1.6 MiB After Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 1.8 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 808 KiB |
|
Before Width: | Height: | Size: 1.8 MiB After Width: | Height: | Size: 1.2 MiB |
|
|
@ -10,7 +10,7 @@ module.exports = {
|
|||
"output/html/**/*.html",
|
||||
"assets/**/*"
|
||||
],
|
||||
port: 3000,
|
||||
port: 3300,
|
||||
open: false,
|
||||
notify: false,
|
||||
ui: false
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||