comicforge 0.2.0__tar.gz → 0.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. {comicforge-0.2.0 → comicforge-0.2.1}/.claude/skills/comicforge/SKILL.md +2 -1
  2. {comicforge-0.2.0 → comicforge-0.2.1}/.claude/skills/comicforge/reference.md +3 -0
  3. comicforge-0.2.1/.github/workflows/docs.yml +69 -0
  4. {comicforge-0.2.0 → comicforge-0.2.1}/.gitignore +9 -0
  5. {comicforge-0.2.0 → comicforge-0.2.1}/CLAUDE.md +26 -1
  6. {comicforge-0.2.0 → comicforge-0.2.1}/PKG-INFO +20 -8
  7. {comicforge-0.2.0 → comicforge-0.2.1}/README.md +19 -7
  8. {comicforge-0.2.0 → comicforge-0.2.1}/comicforge/scaffold.py +2 -0
  9. comicforge-0.2.1/docs/art/characters.md +205 -0
  10. comicforge-0.2.1/docs/art/inspire.md +147 -0
  11. comicforge-0.2.1/docs/art/pixel.md +91 -0
  12. comicforge-0.2.1/docs/art/scenes.md +145 -0
  13. comicforge-0.2.1/docs/assets/favicon.svg +5 -0
  14. comicforge-0.2.1/docs/assets/logo.svg +6 -0
  15. comicforge-0.2.1/docs/claude.md +118 -0
  16. comicforge-0.2.1/docs/concepts.md +144 -0
  17. comicforge-0.2.1/docs/contributing.md +129 -0
  18. comicforge-0.2.1/docs/demos/arms.yaml +15 -0
  19. comicforge-0.2.1/docs/demos/bubble-at.yaml +21 -0
  20. comicforge-0.2.1/docs/demos/bubble-stack.yaml +15 -0
  21. comicforge-0.2.1/docs/demos/bubbles.yaml +19 -0
  22. comicforge-0.2.1/docs/demos/captions.yaml +18 -0
  23. comicforge-0.2.1/docs/demos/coords.yaml +17 -0
  24. comicforge-0.2.1/docs/demos/faces.yaml +18 -0
  25. comicforge-0.2.1/docs/demos/frames.yaml +19 -0
  26. comicforge-0.2.1/docs/demos/grid.yaml +24 -0
  27. comicforge-0.2.1/docs/demos/illustration.yaml +14 -0
  28. comicforge-0.2.1/docs/demos/images.yaml +24 -0
  29. comicforge-0.2.1/docs/demos/lettering.yaml +23 -0
  30. comicforge-0.2.1/docs/demos/pixel.yaml +28 -0
  31. comicforge-0.2.1/docs/demos/poses.yaml +12 -0
  32. comicforge-0.2.1/docs/demos/raster-art/backdrop.png +0 -0
  33. comicforge-0.2.1/docs/demos/scenes.yaml +13 -0
  34. comicforge-0.2.1/docs/demos/strip.yaml +37 -0
  35. comicforge-0.2.1/docs/demos/tutorial-art/characters/pip/base.svg +12 -0
  36. comicforge-0.2.1/docs/demos/tutorial-art/characters/pip/character.yaml +6 -0
  37. comicforge-0.2.1/docs/demos/tutorial-art/characters/pip/face-happy.svg +7 -0
  38. comicforge-0.2.1/docs/demos/tutorial-art/characters/pip/face-neutral.svg +7 -0
  39. comicforge-0.2.1/docs/demos/tutorial-art/pages/first.yaml +16 -0
  40. comicforge-0.2.1/docs/demos/tutorial-art/pixel/heart.yaml +10 -0
  41. comicforge-0.2.1/docs/gallery.md +133 -0
  42. comicforge-0.2.1/docs/guide/actors.md +157 -0
  43. comicforge-0.2.1/docs/guide/bubbles.md +201 -0
  44. comicforge-0.2.1/docs/guide/captions-frames.md +153 -0
  45. comicforge-0.2.1/docs/guide/illustrations.md +113 -0
  46. comicforge-0.2.1/docs/guide/images.md +116 -0
  47. comicforge-0.2.1/docs/guide/pages.md +159 -0
  48. comicforge-0.2.1/docs/guide/pixel-art.md +126 -0
  49. comicforge-0.2.1/docs/guide/scenes.md +91 -0
  50. comicforge-0.2.1/docs/hooks/render_demos.py +105 -0
  51. comicforge-0.2.1/docs/index.md +127 -0
  52. comicforge-0.2.1/docs/install.md +126 -0
  53. comicforge-0.2.1/docs/quickstart.md +191 -0
  54. comicforge-0.2.1/docs/reference/cli.md +331 -0
  55. comicforge-0.2.1/docs/reference/python-api.md +179 -0
  56. comicforge-0.2.1/docs/reference/spec.md +259 -0
  57. comicforge-0.2.1/docs/reference/troubleshooting.md +182 -0
  58. comicforge-0.2.1/docs/starting-a-project.md +118 -0
  59. comicforge-0.2.1/docs/stylesheets/extra.css +188 -0
  60. comicforge-0.2.1/mkdocs.yml +164 -0
  61. {comicforge-0.2.0 → comicforge-0.2.1}/poe_tasks.toml +8 -0
  62. {comicforge-0.2.0 → comicforge-0.2.1}/pyproject.toml +4 -1
  63. {comicforge-0.2.0 → comicforge-0.2.1}/skills/comicforge/SKILL.md +2 -1
  64. {comicforge-0.2.0 → comicforge-0.2.1}/skills/comicforge/reference.md +3 -0
  65. {comicforge-0.2.0 → comicforge-0.2.1}/uv.lock +423 -1
  66. comicforge-0.2.0/docs/starting-a-project.md +0 -70
  67. {comicforge-0.2.0 → comicforge-0.2.1}/.github/dependabot.yml +0 -0
  68. {comicforge-0.2.0 → comicforge-0.2.1}/.github/workflows/ci.yml +0 -0
  69. {comicforge-0.2.0 → comicforge-0.2.1}/.github/workflows/release.yml +0 -0
  70. {comicforge-0.2.0 → comicforge-0.2.1}/.python-version +0 -0
  71. {comicforge-0.2.0 → comicforge-0.2.1}/comicforge/__init__.py +0 -0
  72. {comicforge-0.2.0 → comicforge-0.2.1}/comicforge/__main__.py +0 -0
  73. {comicforge-0.2.0 → comicforge-0.2.1}/comicforge/bubbles.py +0 -0
  74. {comicforge-0.2.0 → comicforge-0.2.1}/comicforge/caption.py +0 -0
  75. {comicforge-0.2.0 → comicforge-0.2.1}/comicforge/cli.py +0 -0
  76. {comicforge-0.2.0 → comicforge-0.2.1}/comicforge/inspire.py +0 -0
  77. {comicforge-0.2.0 → comicforge-0.2.1}/comicforge/library.py +0 -0
  78. {comicforge-0.2.0 → comicforge-0.2.1}/comicforge/pixelart.py +0 -0
  79. {comicforge-0.2.0 → comicforge-0.2.1}/comicforge/raster.py +0 -0
  80. {comicforge-0.2.0 → comicforge-0.2.1}/comicforge/render.py +0 -0
  81. {comicforge-0.2.0 → comicforge-0.2.1}/comicforge/scene.py +0 -0
  82. {comicforge-0.2.0 → comicforge-0.2.1}/comicforge/validate.py +0 -0
  83. {comicforge-0.2.0 → comicforge-0.2.1}/examples/README.md +0 -0
  84. {comicforge-0.2.0 → comicforge-0.2.1}/examples/README.md.old +0 -0
  85. {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/characters/bara/character.yaml +0 -0
  86. {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/characters/bara/face-happy.svg +0 -0
  87. {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/characters/bara/face-neutral.svg +0 -0
  88. {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/characters/bara/poses/sit/base.svg +0 -0
  89. {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/characters/bara/poses/sit/pose.yaml +0 -0
  90. {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/characters/bara/poses/walk/base.svg +0 -0
  91. {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/characters/bara/poses/walk/pose.yaml +0 -0
  92. {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/characters/tom/arms-crossed.svg +0 -0
  93. {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/characters/tom/arms-down.svg +0 -0
  94. {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/characters/tom/arms-hips.svg +0 -0
  95. {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/characters/tom/arms-point.svg +0 -0
  96. {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/characters/tom/arms-thumbsup.svg +0 -0
  97. {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/characters/tom/arms-wave.svg +0 -0
  98. {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/characters/tom/base.svg +0 -0
  99. {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/characters/tom/character.yaml +0 -0
  100. {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/characters/tom/face-angry.svg +0 -0
  101. {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/characters/tom/face-happy.svg +0 -0
  102. {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/characters/tom/face-laugh.svg +0 -0
  103. {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/characters/tom/face-neutral.svg +0 -0
  104. {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/characters/tom/face-sad.svg +0 -0
  105. {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/characters/tom/face-surprised.svg +0 -0
  106. {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/characters/tom/face-wink.svg +0 -0
  107. {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/pages/dvur-scene.yaml +0 -0
  108. {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/pages/kosticka.yaml +0 -0
  109. {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/pages/slepice.yaml +0 -0
  110. {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/pixel/bone.yaml +0 -0
  111. {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/pixel/heart.yaml +0 -0
  112. {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/pixel/star.yaml +0 -0
  113. {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/pixel/sun.yaml +0 -0
  114. {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/references.yaml +0 -0
  115. {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/scenes/dvur/base.svg +0 -0
  116. {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/scenes/dvur/scene.yaml +0 -0
  117. {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/scenes/dvur/weather-clear.svg +0 -0
  118. {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/scenes/dvur/weather-rain.svg +0 -0
  119. {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/scenes/pokoj/base.svg +0 -0
  120. {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/scenes/pokoj/scene.yaml +0 -0
  121. {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/theme.yaml +0 -0
  122. {comicforge-0.2.0 → comicforge-0.2.1}/tests/__init__.py +0 -0
  123. {comicforge-0.2.0 → comicforge-0.2.1}/tests/conftest.py +0 -0
  124. {comicforge-0.2.0 → comicforge-0.2.1}/tests/test_bubbles.py +0 -0
  125. {comicforge-0.2.0 → comicforge-0.2.1}/tests/test_caption.py +0 -0
  126. {comicforge-0.2.0 → comicforge-0.2.1}/tests/test_cli.py +0 -0
  127. {comicforge-0.2.0 → comicforge-0.2.1}/tests/test_inspire.py +0 -0
  128. {comicforge-0.2.0 → comicforge-0.2.1}/tests/test_library.py +0 -0
  129. {comicforge-0.2.0 → comicforge-0.2.1}/tests/test_pixelart.py +0 -0
  130. {comicforge-0.2.0 → comicforge-0.2.1}/tests/test_raster.py +0 -0
  131. {comicforge-0.2.0 → comicforge-0.2.1}/tests/test_render.py +0 -0
  132. {comicforge-0.2.0 → comicforge-0.2.1}/tests/test_scaffold.py +0 -0
  133. {comicforge-0.2.0 → comicforge-0.2.1}/tests/test_scene.py +0 -0
  134. {comicforge-0.2.0 → comicforge-0.2.1}/tests/test_validate.py +0 -0
@@ -17,7 +17,8 @@ description: >
17
17
  You write a **comic page as a YAML spec**; the engine renders it to SVG/PNG/PDF.
18
18
  This file is the authoring contract. For deeper explanations (path resolution,
19
19
  how to add characters/scenes, pixel-art format, Python API, full CLI reference)
20
- see [`reference.md`](reference.md) in this skill directory.
20
+ see [`reference.md`](reference.md) in this skill directory, or the documentation
21
+ site at <https://mojzis.github.io/comicforge/>.
21
22
 
22
23
  ## Loop
23
24
 
@@ -4,6 +4,9 @@ This guide covers everything you need to author comics with ComicForge — for b
4
4
  humans and LLMs. The [SKILL.md](SKILL.md) file is the quick-reference contract;
5
5
  this document goes deeper on structure, paths, and how all the pieces fit together.
6
6
 
7
+ The same material, with rendered examples beside every spec, is published at
8
+ <https://mojzis.github.io/comicforge/>.
9
+
7
10
  ---
8
11
 
9
12
  ## Engine + self-contained projects
@@ -0,0 +1,69 @@
1
+ name: Docs
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ paths:
7
+ - "docs/**"
8
+ - "mkdocs.yml"
9
+ - "examples/**" # the gallery renders from the demo project
10
+ - "comicforge/**" # …through the engine, so a renderer change matters
11
+ - "pyproject.toml"
12
+ - ".github/workflows/docs.yml"
13
+ pull_request:
14
+ paths:
15
+ - "docs/**"
16
+ - "mkdocs.yml"
17
+ - "examples/**"
18
+ - "comicforge/**"
19
+ workflow_dispatch:
20
+
21
+ concurrency:
22
+ group: docs-${{ github.ref }}
23
+ cancel-in-progress: true
24
+
25
+ permissions: {}
26
+
27
+ jobs:
28
+ build:
29
+ runs-on: ubuntu-latest
30
+ steps:
31
+ - uses: actions/checkout@v7
32
+
33
+ # Every image on the site is rendered at build time by
34
+ # docs/hooks/render_demos.py, so the docs build needs cairo just like a
35
+ # normal render does.
36
+ - name: Install cairo
37
+ run: sudo apt-get update && sudo apt-get install -y libcairo2
38
+
39
+ - name: Install uv
40
+ uses: astral-sh/setup-uv@v10.0.1
41
+ with:
42
+ enable-cache: true
43
+
44
+ - name: Sync docs dependencies
45
+ run: uv sync --group docs
46
+
47
+ # --strict fails the build on a broken cross-reference, a dead anchor, a
48
+ # missing snippet, or a demo spec that no longer renders.
49
+ - name: Build site
50
+ run: uv run mkdocs build --strict
51
+
52
+ - uses: actions/upload-pages-artifact@v4
53
+ with:
54
+ path: site/
55
+
56
+ deploy:
57
+ # Pull requests get the build as a check; only main publishes.
58
+ if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
59
+ needs: build
60
+ runs-on: ubuntu-latest
61
+ environment:
62
+ name: github-pages
63
+ url: ${{ steps.deployment.outputs.page_url }}
64
+ permissions:
65
+ pages: write
66
+ id-token: write
67
+ steps:
68
+ - id: deployment
69
+ uses: actions/deploy-pages@v4
@@ -34,3 +34,12 @@ review.html
34
34
 
35
35
  # Source archive (redundant after flattening)
36
36
  *.zip
37
+
38
+ # Docs site: `mkdocs build` output, and the demo renders the build hook draws
39
+ # (every image on the site is regenerated from a spec — never checked in).
40
+ site/
41
+ docs/assets/renders/
42
+
43
+ # …except the one raster the `image:` demo needs as *input*, which `*.png` above
44
+ # would otherwise swallow.
45
+ !docs/demos/raster-art/*.png
@@ -21,6 +21,7 @@ comicforge/ The engine — pure code, no bundled art.
21
21
  examples/pes/ A self-contained demo project (characters, scenes, pixel, pages).
22
22
  Develop against the engine the same way a downstream project would.
23
23
  skills/comicforge/ The portable authoring skill (SKILL.md + reference.md).
24
+ docs/ The documentation site (MkDocs Material) — see below.
24
25
  tests/ pytest suite.
25
26
  ```
26
27
 
@@ -82,6 +83,29 @@ cmf scene examples/pes/pages/dvur-scene.yaml -o dvur.png
82
83
  cmf panel examples/pes/pages/slepice.yaml -o panels/ --all
83
84
  ```
84
85
 
86
+ ## Docs site
87
+
88
+ `docs/` is a MkDocs Material site published to GitHub Pages by
89
+ `.github/workflows/docs.yml`.
90
+
91
+ ```bash
92
+ uv sync --group docs
93
+ poe docs # serve with live reload
94
+ poe docs-build # mkdocs build --strict — what CI runs
95
+ ```
96
+
97
+ **Every image on the site is rendered at build time** by
98
+ `docs/hooks/render_demos.py`, from a spec in `docs/demos/` or from
99
+ `examples/pes/`. Nothing is a checked-in screenshot, so a renderer change that
100
+ breaks a demo breaks the docs build. To add an illustrated example: write
101
+ `docs/demos/<name>.yaml` (small custom `page:`, `library:` pointing at
102
+ `../../examples/pes/characters`), `cmf validate` it, then reference
103
+ `assets/renders/<name>.png` and include the spec with a `pymdownx.snippets`
104
+ include — see `docs/guide/bubbles.md` for the pattern.
105
+
106
+ `--strict` plus the `validation:` block in `mkdocs.yml` fails the build on a
107
+ broken cross-reference, a dead anchor, a missing snippet or a missing render.
108
+
85
109
  ## Conventions / invariants
86
110
 
87
111
  - **Content-free engine:** never hardcode an asset name, path, or default project
@@ -91,7 +115,8 @@ cmf panel examples/pes/pages/slepice.yaml -o panels/ --all
91
115
  - The CLI subcommands (`render`/`scene`/`panel`/`characters`/`scenes`) are the
92
116
  stable surface; `characters`/`scenes` emit JSON manifests other tools depend on.
93
117
  - When you change the spec grammar or CLI, update the authoring skill in
94
- `skills/comicforge/` (both `SKILL.md` and `reference.md`) in the same change.
118
+ `skills/comicforge/` (both `SKILL.md` and `reference.md`) **and** the affected
119
+ pages under `docs/` in the same change.
95
120
  - Demo art is hand/LLM-authored crisp SVG, edited directly in `examples/pes/`
96
121
  (there is no procedural generator). Use `comicforge inspire` for *reference*
97
122
  images only — never auto-vectorize them; it breaks overlay/anchor registration.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: comicforge
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: A tiny, scriptable comic-page engine — author comics as YAML, render to SVG / PNG / PDF
5
5
  Requires-Python: >=3.13
6
6
  Requires-Dist: cairosvg>=2.7
@@ -13,12 +13,18 @@ Description-Content-Type: text/markdown
13
13
 
14
14
  # ComicForge
15
15
 
16
+ **📖 Documentation: <https://mojzis.github.io/comicforge/>**
17
+
16
18
  A tiny, scriptable comic-page engine. Characters are a **base SVG + stackable
17
19
  variant overlays** (faces, arms, …); a comic page is a **declarative YAML spec**;
18
20
  output is **SVG / PNG / PDF**. Built so an LLM (or you) can author pages as plain
19
- text — see the authoring skill [`skills/comicforge/SKILL.md`](skills/comicforge/SKILL.md)
20
- for the full authoring contract and [`skills/comicforge/reference.md`](skills/comicforge/reference.md)
21
- for a deeper reference. Working **on the engine** instead? See [`CLAUDE.md`](CLAUDE.md).
21
+ text.
22
+
23
+ - [Your first comic](https://mojzis.github.io/comicforge/quickstart/) — a project, a character, a strip
24
+ - [Spec reference](https://mojzis.github.io/comicforge/reference/spec/) · [CLI reference](https://mojzis.github.io/comicforge/reference/cli/) · [Gallery](https://mojzis.github.io/comicforge/gallery/)
25
+ - [`skills/comicforge/SKILL.md`](skills/comicforge/SKILL.md) — the authoring contract an LLM reads
26
+ - Working **on the engine**? See [`CLAUDE.md`](CLAUDE.md) and
27
+ [Contributing](https://mojzis.github.io/comicforge/contributing/).
22
28
 
23
29
  ## Start a project
24
30
 
@@ -32,13 +38,17 @@ cmf init my-comic # scaffold characters/ scenes/ pixel/ pages/
32
38
  cd my-comic && cmf render pages/hello.yaml
33
39
  ```
34
40
 
35
- See [`docs/starting-a-project.md`](docs/starting-a-project.md) for the data-only
36
- setup, version pinning, and when to make it a real Python project.
41
+ See [Starting a project](https://mojzis.github.io/comicforge/starting-a-project/)
42
+ for the data-only setup, version pinning, and when to make it a real Python
43
+ project.
37
44
 
38
45
  ## Working on the engine
39
46
 
40
47
  ```bash
41
48
  uv sync # create .venv and install deps
49
+ uv run poe test # the tight loop
50
+ uv run poe check # everything, before opening a PR
51
+ uv run poe docs # serve the documentation site locally
42
52
  ```
43
53
 
44
54
  ## Use
@@ -114,8 +124,10 @@ pixel_dir: "../pixel" # pixel-art sprites
114
124
  CLI flags (`--library`, `--scenes`, `--pixel-dir`) override spec keys and are
115
125
  treated as relative to the current working directory.
116
126
 
117
- See [`skills/comicforge/reference.md`](skills/comicforge/reference.md) for the full path
118
- resolution rule, how to add characters/scenes/sprites, and the complete CLI reference.
127
+ The [documentation site](https://mojzis.github.io/comicforge/) covers the full
128
+ path-resolution rule, how to add characters / scenes / sprites, and the complete
129
+ CLI and spec references — every picture on it is rendered from a spec at build
130
+ time.
119
131
 
120
132
  ## Design choices
121
133
 
@@ -1,11 +1,17 @@
1
1
  # ComicForge
2
2
 
3
+ **📖 Documentation: <https://mojzis.github.io/comicforge/>**
4
+
3
5
  A tiny, scriptable comic-page engine. Characters are a **base SVG + stackable
4
6
  variant overlays** (faces, arms, …); a comic page is a **declarative YAML spec**;
5
7
  output is **SVG / PNG / PDF**. Built so an LLM (or you) can author pages as plain
6
- text — see the authoring skill [`skills/comicforge/SKILL.md`](skills/comicforge/SKILL.md)
7
- for the full authoring contract and [`skills/comicforge/reference.md`](skills/comicforge/reference.md)
8
- for a deeper reference. Working **on the engine** instead? See [`CLAUDE.md`](CLAUDE.md).
8
+ text.
9
+
10
+ - [Your first comic](https://mojzis.github.io/comicforge/quickstart/) — a project, a character, a strip
11
+ - [Spec reference](https://mojzis.github.io/comicforge/reference/spec/) · [CLI reference](https://mojzis.github.io/comicforge/reference/cli/) · [Gallery](https://mojzis.github.io/comicforge/gallery/)
12
+ - [`skills/comicforge/SKILL.md`](skills/comicforge/SKILL.md) — the authoring contract an LLM reads
13
+ - Working **on the engine**? See [`CLAUDE.md`](CLAUDE.md) and
14
+ [Contributing](https://mojzis.github.io/comicforge/contributing/).
9
15
 
10
16
  ## Start a project
11
17
 
@@ -19,13 +25,17 @@ cmf init my-comic # scaffold characters/ scenes/ pixel/ pages/
19
25
  cd my-comic && cmf render pages/hello.yaml
20
26
  ```
21
27
 
22
- See [`docs/starting-a-project.md`](docs/starting-a-project.md) for the data-only
23
- setup, version pinning, and when to make it a real Python project.
28
+ See [Starting a project](https://mojzis.github.io/comicforge/starting-a-project/)
29
+ for the data-only setup, version pinning, and when to make it a real Python
30
+ project.
24
31
 
25
32
  ## Working on the engine
26
33
 
27
34
  ```bash
28
35
  uv sync # create .venv and install deps
36
+ uv run poe test # the tight loop
37
+ uv run poe check # everything, before opening a PR
38
+ uv run poe docs # serve the documentation site locally
29
39
  ```
30
40
 
31
41
  ## Use
@@ -101,8 +111,10 @@ pixel_dir: "../pixel" # pixel-art sprites
101
111
  CLI flags (`--library`, `--scenes`, `--pixel-dir`) override spec keys and are
102
112
  treated as relative to the current working directory.
103
113
 
104
- See [`skills/comicforge/reference.md`](skills/comicforge/reference.md) for the full path
105
- resolution rule, how to add characters/scenes/sprites, and the complete CLI reference.
114
+ The [documentation site](https://mojzis.github.io/comicforge/) covers the full
115
+ path-resolution rule, how to add characters / scenes / sprites, and the complete
116
+ CLI and spec references — every picture on it is rendered from a spec at build
117
+ time.
106
118
 
107
119
  ## Design choices
108
120
 
@@ -82,6 +82,8 @@ A **data-only** ComicForge project: YAML specs + SVG / pixel art, no Python.
82
82
  Paths in a spec resolve relative to the spec file, so `library: ../characters`
83
83
  works from anywhere. See the skill for the full spec grammar, and add your own
84
84
  characters under `characters/` to start placing actors.
85
+
86
+ Documentation: https://mojzis.github.io/comicforge/
85
87
  """
86
88
 
87
89
  _GITIGNORE = "output/\n"
@@ -0,0 +1,205 @@
1
+ # Making a character
2
+
3
+ A character is a directory of SVG files and one manifest. No code, no build
4
+ step, no registration anywhere in the engine — drop the directory into
5
+ `characters/` and it exists.
6
+
7
+ There are two on-disk shapes. Use the simple one until you need more than one
8
+ body.
9
+
10
+ ## Single-pose (flat)
11
+
12
+ Everything drawn in one `viewBox`:
13
+
14
+ ```
15
+ characters/<name>/
16
+ base.svg the body
17
+ <slot>-<variant>.svg overlays, in the SAME viewBox
18
+ character.yaml the manifest
19
+ ```
20
+
21
+ ```yaml title="character.yaml"
22
+ name: tom # internal id — must match the directory name
23
+ label: Tom # display name, used in the manifest
24
+ viewbox: [200, 320] # the shared local canvas [width, height]
25
+ default:
26
+ face: neutral # used whenever a spec omits the slot
27
+ arms: down
28
+ slots:
29
+ arms: [down, wave, point, crossed, hips, thumbsup]
30
+ face: [neutral, happy, surprised, sad, angry, laugh, wink]
31
+ ```
32
+
33
+ Every file — base and overlays alike — declares the same `viewBox`. That is the
34
+ whole registration mechanism: the engine strips each SVG to its inner markup and
35
+ stacks them in one coordinate system, so a mouth drawn at `(100, 92)` lands at
36
+ `(100, 92)` on the body.
37
+
38
+ The stacking order is the slot order in `character.yaml`, so a slot listed later
39
+ draws over one listed earlier. Put `face` after `arms` if a raised hand should
40
+ never cover the mouth.
41
+
42
+ ### Drawing rules that matter
43
+
44
+ - **One `viewBox`, no exceptions.** An overlay with a different viewBox will be
45
+ scaled to the pose's and land in the wrong place.
46
+ - **Draw only the part that varies.** `face-happy.svg` contains eyes and a
47
+ mouth, not a head. The head is in `base.svg`, and drawing it twice means one
48
+ outline peeking out from behind the other.
49
+ - **Leave the head alone across expressions.** Same size, same position — the
50
+ variation is in the features.
51
+ - **Name files `<slot>-<variant>.svg`.** The file name *is* the lookup; a slot
52
+ listed in the manifest with no matching file fails at load time.
53
+
54
+ Here is a complete, minimal character — the one from
55
+ [Your first comic](../quickstart.md):
56
+
57
+ === "base.svg"
58
+
59
+ ```svg
60
+ --8<-- "demos/tutorial-art/characters/pip/base.svg"
61
+ ```
62
+
63
+ === "face-neutral.svg"
64
+
65
+ ```svg
66
+ --8<-- "demos/tutorial-art/characters/pip/face-neutral.svg"
67
+ ```
68
+
69
+ === "face-happy.svg"
70
+
71
+ ```svg
72
+ --8<-- "demos/tutorial-art/characters/pip/face-happy.svg"
73
+ ```
74
+
75
+ === "character.yaml"
76
+
77
+ ```yaml
78
+ --8<-- "demos/tutorial-art/characters/pip/character.yaml"
79
+ ```
80
+
81
+ ## Multiple poses
82
+
83
+ When the **body** changes — sitting versus walking, legs in a different place —
84
+ one viewBox is no longer enough. Give each pose its own base, and let the
85
+ expressions stay shared.
86
+
87
+ ```
88
+ characters/<name>/
89
+ character.yaml identity + SHARED slots + default + the pose list
90
+ <slot>-<variant>.svg SHARED overlays, drawn around the canonical anchor
91
+ poses/
92
+ sit/
93
+ pose.yaml viewbox, anchor, optional pose-specific slots
94
+ base.svg this pose's body
95
+ walk/
96
+ pose.yaml
97
+ base.svg
98
+ <slot>-<variant>.svg optional overlays that only exist in this pose
99
+ ```
100
+
101
+ ```yaml title="character.yaml"
102
+ name: bara
103
+ label: Bára
104
+ anchor: [120, 72] # the canonical point shared overlays draw around
105
+ slots: {face: [neutral, happy]} # SHARED across poses
106
+ default: {pose: sit, face: neutral}
107
+ poses: [sit, walk]
108
+ ```
109
+
110
+ ```yaml title="poses/walk/pose.yaml"
111
+ viewbox: [240, 180]
112
+ anchor: [132, 64] # where THIS pose's head centre lands
113
+ slots: {arms: [trot]} # optional, pose-specific
114
+ default: {arms: trot}
115
+ ```
116
+
117
+ ### The anchor
118
+
119
+ The anchor is the trick that makes one face file work on every body. It is a
120
+ reference point — the head centre is the natural choice — that you agree on
121
+ once:
122
+
123
+ - The **character's** `anchor` is where the head centre sits in the coordinate
124
+ system the shared overlays are drawn in.
125
+ - Each **pose's** `anchor` is where that same head centre sits in *that pose's*
126
+ viewBox.
127
+
128
+ When composing, the engine shifts every shared overlay by
129
+ `pose.anchor − character.anchor`. So `face-happy.svg`, authored once, lands
130
+ correctly whether Bára is sitting or walking.
131
+
132
+ <figure class="cf-demo" markdown>
133
+ ![Bára sitting and walking, with the same shared face variants](../assets/renders/poses.png)
134
+ <figcaption>Two bodies, one set of faces. The <code>happy</code> face in the middle and right panels is the same file as the one drawn for <code>sit</code>.</figcaption>
135
+ </figure>
136
+
137
+ !!! warning "Translate only"
138
+
139
+ The shift is a translation and nothing else — no scaling, no rotation. Keep
140
+ the head the **same size** across every pose and move only its position. A
141
+ pose that draws a bigger head will wear a face that is too small for it.
142
+
143
+ ### Composition order
144
+
145
+ For one actor, the engine assembles:
146
+
147
+ 1. the chosen pose's `base.svg`
148
+ 2. that pose's **pose-specific** overlays, in `pose.yaml` slot order
149
+ 3. the **shared** overlays, in `character.yaml` slot order, each wrapped in the
150
+ anchor translate
151
+
152
+ Then the whole thing is scaled to the requested height and centred at the
153
+ requested point.
154
+
155
+ ### Flat is a special case of posed
156
+
157
+ A flat character is the posed model with one implicit pose whose anchor is
158
+ `[0, 0]` and whose viewBox is the character's. Both render through exactly the
159
+ same code path, which is why adding poses to an existing flat character later is
160
+ a restructuring rather than a rewrite.
161
+
162
+ ## Adding to an existing character
163
+
164
+ | You want | Do this |
165
+ |---|---|
166
+ | A new shared expression | Draw it around the canonical anchor, save as `<slot>-<variant>.svg` at the character root, add the variant to `slots` in `character.yaml` |
167
+ | A new pose | Add `poses/<name>/` with its own `base.svg` and `pose.yaml` (set its `anchor`), and list the name under `poses:` |
168
+ | A new overlay on a flat character | Save `<slot>-<variant>.svg` in the shared viewBox, add it to `slots` |
169
+ | A whole new slot | Add the slot to `slots`, add a variant file for each value, and give it an entry in `default` |
170
+
171
+ No code changes in any case.
172
+
173
+ ## Checking your work
174
+
175
+ ```bash
176
+ cmf characters --library characters # what does the engine think exists?
177
+ cmf character bara walk happy --library characters
178
+ cmf character tom --library characters # everything at its default
179
+ ```
180
+
181
+ `cmf character` renders one character alone on a plain canvas, cropped to its
182
+ pose — the fastest way to see whether an overlay lines up. It writes the full
183
+ render plus a smaller `<name>.small.png` companion; when an agent is reading the
184
+ result back, the small one costs far fewer tokens.
185
+
186
+ Common failures and what they mean:
187
+
188
+ | Symptom | Cause |
189
+ |---|---|
190
+ | `is not a well-formed <svg>…</svg> file` | The overlay is missing its outer `<svg>` element |
191
+ | Overlay lands in the wrong place | Its `viewBox` differs from the base's, or the pose anchor is wrong |
192
+ | `slot 'face' has no variant 'grin'` | The variant is not listed in the manifest — or the file name does not match |
193
+ | A double outline around the head | The overlay redraws part of the base |
194
+ | The face is too small on one pose | That pose draws the head at a different size — anchors translate, they do not scale |
195
+
196
+ ## Where the art comes from
197
+
198
+ The demo art in `examples/pes/` is hand- and LLM-authored SVG, edited directly.
199
+ There is no procedural generator, and there is no vectorizer in the pipeline.
200
+
201
+ A visual model is genuinely useful for *inspiration* — generate a reference
202
+ image, then author the crisp SVG from it. [`cmf inspire`](inspire.md) does that
203
+ part. What you must not do is auto-vectorize the result: tracing produces
204
+ thousands of paths in one flat layer, which destroys the slot structure and the
205
+ anchor registration that make the character poseable at all.
@@ -0,0 +1,147 @@
1
+ # Reference images
2
+
3
+ `cmf inspire` blends a project-wide **theme** with per-item **descriptions** and
4
+ asks an image model to paint them. The output is **reference art to draw from** —
5
+ never a shipped asset.
6
+
7
+ ```bash
8
+ cmf inspire references.yaml --dry-run # compose the prompts, call nothing
9
+ cmf inspire references.yaml --review # generate, plus a review.html grid
10
+ ```
11
+
12
+ !!! danger "Do not auto-vectorize the result"
13
+
14
+ Tracing a generated image produces thousands of paths in one flat layer.
15
+ That destroys the slot structure and the anchor registration that make a
16
+ character poseable — you end up with a picture, not a character. Look at the
17
+ reference, then author the SVG. See
18
+ [Making a character](characters.md).
19
+
20
+ ## Two files
21
+
22
+ ### `theme.yaml` — the project's look
23
+
24
+ Applied to every image, so the whole cast comes back in one style.
25
+
26
+ ```yaml title="theme.yaml"
27
+ style: >
28
+ Hand-drawn children's-book comic illustration. Bold, clean black ink outlines
29
+ with flat cheerful fills and simple friendly shapes. Light, uncluttered
30
+ background. Centered subject filling the frame.
31
+
32
+ palette:
33
+ - "#f4d35e" # sunny yellow
34
+ - "#ee964b" # warm orange
35
+ - "#0d3b66" # deep blue
36
+ - "#faf0ca" # cream
37
+ - "#f95738" # coral
38
+
39
+ mood: warm, playful, gentle
40
+
41
+ negative: No text, no letters, no words, no watermark, no signature.
42
+
43
+ aspect_ratio: "1:1"
44
+ # model: google/imagen-3
45
+ ```
46
+
47
+ | Key | Default | Meaning |
48
+ |---|---|---|
49
+ | `style` | — | The look, in words. Prepended to every prompt |
50
+ | `palette` | — | Hex colours fed to the model as a colour scale |
51
+ | `mood` | — | One line of tone |
52
+ | `negative` | no text / watermark / signature | What to avoid |
53
+ | `aspect_ratio` | `"1:1"` | Passed to the model |
54
+ | `model` | `google/imagen-3` | Any Replicate model id |
55
+
56
+ A missing `theme.yaml` is not an error — you just get the defaults.
57
+
58
+ ### `references.yaml` — the things to depict
59
+
60
+ ```yaml title="references.yaml"
61
+ items:
62
+ - id: tom
63
+ prompt: >
64
+ A cheerful 8-year-old boy named Tom in a striped t-shirt and shorts,
65
+ full body, standing, neutral friendly expression, simple cartoon style.
66
+
67
+ - id: dvur
68
+ prompt: >
69
+ A sunny Czech village farmyard: a wooden fence, a small chicken coop,
70
+ a few hens pecking, a cobbled yard, a tree to one side.
71
+ ```
72
+
73
+ Each entry needs an id (`id` or `name`) and a description (`prompt`,
74
+ `description` or `desc`). A bare list without the `items:` key works too. The id
75
+ becomes the filename.
76
+
77
+ ## The composed prompt
78
+
79
+ `style` → `Subject: <prompt>` → palette → mood → negative, joined by blank
80
+ lines:
81
+
82
+ ```text title="references/tom.prompt.txt"
83
+ Hand-drawn children's-book comic illustration. Bold, clean black ink outlines
84
+ with flat cheerful fills and simple friendly shapes. Light, uncluttered
85
+ background. Centered subject filling the frame.
86
+
87
+ Subject: A cheerful 8-year-old boy named Tom in a striped t-shirt and shorts,
88
+ full body, standing, neutral friendly expression, simple cartoon style.
89
+
90
+ Use this color palette: #f4d35e, #ee964b, #0d3b66, #faf0ca, #f95738.
91
+
92
+ Mood: warm, playful, gentle.
93
+
94
+ No text, no letters, no words, no watermark, no signature.
95
+ ```
96
+
97
+ The sidecar is written next to every image, so you can always see exactly what
98
+ produced a picture. `--dry-run` writes only the sidecars — use it to iterate on
99
+ wording without spending a call or needing a token.
100
+
101
+ ## Output and flags
102
+
103
+ Each item writes `<out_dir>/<id>.png` and `<out_dir>/<id>.prompt.txt`.
104
+ `theme.yaml` and the output directory default to siblings of the references
105
+ spec.
106
+
107
+ ```bash
108
+ cmf inspire references.yaml -o refs/ # choose the output directory
109
+ cmf inspire references.yaml --theme t.yaml # a different theme
110
+ cmf inspire references.yaml --only tom,dvur # a subset
111
+ cmf inspire references.yaml --force # regenerate images that already exist
112
+ cmf inspire references.yaml --review # also write review.html
113
+ ```
114
+
115
+ By default an item whose `.png` already exists is skipped, so re-running after
116
+ adding one entry costs one call. `--review` writes a `review.html` grid of every
117
+ image beside its prompt — the fastest way to judge a batch and decide what to
118
+ re-word.
119
+
120
+ ## Setup for live generation
121
+
122
+ ```bash
123
+ uv tool install "comicforge[inspire]" # adds replicate + python-dotenv
124
+ export REPLICATE_API_TOKEN=... # or a .env beside the spec
125
+ ```
126
+
127
+ The token is read from the environment, or from a `.env` file next to the
128
+ references spec or in the current directory. Live calls are paced — the default
129
+ model caps at roughly six requests a minute, so a batch takes about eleven
130
+ seconds per image.
131
+
132
+ Neither the extra nor the token is needed for `--dry-run`.
133
+
134
+ ## How to actually use the output
135
+
136
+ 1. Write the description, run `--dry-run`, read the composed prompt.
137
+ 2. Generate a handful with `--review` and pick the one that reads best as a
138
+ *shape* — silhouette, proportions, the pose you want as a base.
139
+ 3. Author `base.svg` by hand from it: clean outlines, flat fills, in one
140
+ viewBox.
141
+ 4. Draw the overlays around the same head position, and write
142
+ `character.yaml`.
143
+ 5. Keep the reference in `references/` as documentation of the intent. It never
144
+ goes into `characters/`.
145
+
146
+ Step 3 is the work, and there is no shortcut through it. The reference exists to
147
+ make a design decision, not to become the asset.