scrolly 0.2.2__tar.gz → 0.2.4__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.
- {scrolly-0.2.2 → scrolly-0.2.4}/.gitignore +3 -0
- scrolly-0.2.4/PKG-INFO +118 -0
- scrolly-0.2.4/README.md +85 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/pyproject.toml +4 -2
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/_cli/_cli.py +2 -46
- scrolly-0.2.4/scrolly/_cli/_schema.py +167 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/deck/introspect.py +2 -1
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/deck/model.py +1 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/deck/parser.py +34 -11
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/deck/schema.py +11 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/catalog/E003.md +2 -2
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/catalog/E299.md +2 -2
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/catalog/E601.md +3 -2
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/render/assets/canvas.css +49 -5
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/render/assets/canvas.js +59 -8
- scrolly-0.2.4/scrolly/render/color.py +66 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/render/nav_data.py +15 -4
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/render/templates/index.html.j2 +12 -2
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/slide/__init__.py +2 -1
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/slide/ir/__init__.py +2 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/slide/ir/_framework/element.py +49 -1
- scrolly-0.2.2/PKG-INFO +0 -81
- scrolly-0.2.2/README.md +0 -50
- {scrolly-0.2.2 → scrolly-0.2.4}/LICENSE +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/__init__.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/_cli/__init__.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/_cli/_errors.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/_cli/_introspect/__init__.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/_cli/_introspect/_assets.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/_cli/_introspect/_common.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/_cli/_introspect/_dom.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/_cli/_introspect/_elements.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/_cli/_introspect/_slides.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/_cli/_introspect/_snaps.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/_cli/_introspect/_snapshot.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/_cli/_introspect/_timeline.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/deck/__init__.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/deck/inference.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/deck/validator.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/__init__.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/_catalog.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/_codes.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/_report.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/_validation_error.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/catalog/E001.md +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/catalog/E002.md +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/catalog/E004.md +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/catalog/E005.md +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/catalog/E006.md +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/catalog/E007.md +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/catalog/E008.md +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/catalog/E009.md +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/catalog/E010.md +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/catalog/E011.md +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/catalog/E012.md +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/catalog/E101.md +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/catalog/E102.md +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/catalog/E103.md +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/catalog/E201.md +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/catalog/E202.md +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/catalog/E203.md +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/catalog/E204.md +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/catalog/E205.md +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/catalog/E206.md +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/catalog/E207.md +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/catalog/E301.md +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/catalog/E302.md +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/catalog/E303.md +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/catalog/E304.md +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/catalog/E305.md +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/catalog/E306.md +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/catalog/E307.md +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/catalog/E308.md +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/catalog/E401.md +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/catalog/E402.md +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/catalog/E403.md +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/catalog/E501.md +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/catalog/E502.md +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/catalog/E503.md +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/catalog/E504.md +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/catalog/E505.md +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/catalog/E602.md +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/catalog/E603.md +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/catalog/E701.md +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/catalog/E702.md +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/errors/catalog/__init__.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/pipeline/__init__.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/pipeline/_bundler.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/pipeline/assets.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/pipeline/introspect.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/pipeline/lint.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/pipeline/loader.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/pipeline/orchestrator.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/pipeline/writer.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/render/__init__.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/render/assembler.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/render/assets/mermaid-LICENSE +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/render/assets/mermaid.min.js +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/render/bundled_assets.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/render/fan.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/render/zoom_control.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/slide/element_ir/__init__.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/slide/element_ir/ir.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/slide/element_ir/processor.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/slide/element_ir/registry.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/slide/element_ir/rendered.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/slide/element_ir/renderers/__init__.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/slide/element_ir/renderers/_shared.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/slide/element_ir/renderers/html.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/slide/element_ir/renderers/iframe.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/slide/element_ir/renderers/image.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/slide/element_ir/renderers/image_sequence.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/slide/element_ir/renderers/markdown.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/slide/element_ir/renderers/mermaid.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/slide/html.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/slide/introspect.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/slide/ir/_framework/__init__.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/slide/ir/_framework/animated_values.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/slide/ir/_framework/utils.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/slide/ir/slide.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/slide/processor.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/slide/registry.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/slide/renderers/__init__.py +0 -0
- {scrolly-0.2.2 → scrolly-0.2.4}/scrolly/slide/renderers/slide.py +0 -0
scrolly-0.2.4/PKG-INFO
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: scrolly
|
|
3
|
+
Version: 0.2.4
|
|
4
|
+
Summary: CLI that compiles a JSON5 deck + slide files into a self-contained 2D-canvas HTML presentation.
|
|
5
|
+
Project-URL: Homepage, https://scrolly.readthedocs.io/en/stable/
|
|
6
|
+
Project-URL: Documentation, https://scrolly.readthedocs.io/en/stable/
|
|
7
|
+
Project-URL: Source, https://github.com/bertpl/scrolly
|
|
8
|
+
Project-URL: Changelog, https://github.com/bertpl/scrolly/blob/main/CHANGELOG.md
|
|
9
|
+
Project-URL: Issues, https://github.com/bertpl/scrolly/issues
|
|
10
|
+
Author-email: Bert Pluymers <bert.pluymers@gmail.com>
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: cli,html,presentation,scrollytelling,slides
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Topic :: Multimedia :: Graphics :: Presentation
|
|
23
|
+
Classifier: Topic :: Text Processing :: Markup :: HTML
|
|
24
|
+
Requires-Python: >=3.11
|
|
25
|
+
Requires-Dist: click>=8.2.0
|
|
26
|
+
Requires-Dist: jinja2>=3.1
|
|
27
|
+
Requires-Dist: json5>=0.13.0
|
|
28
|
+
Requires-Dist: markdown>=3.4
|
|
29
|
+
Requires-Dist: pydantic>=2.0
|
|
30
|
+
Requires-Dist: pyyaml>=6.0
|
|
31
|
+
Requires-Dist: rich>=13.0.0
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# scrolly
|
|
35
|
+
|
|
36
|
+
Compile a JSON5 deck into a single, self-contained, scrollable 2D-canvas HTML presentation.
|
|
37
|
+
|
|
38
|
+
*Liked by humans; understood by agents.*
|
|
39
|
+
|
|
40
|
+
[](https://scrolly.readthedocs.io/en/stable/)
|
|
41
|
+
|
|
42
|
+
[](https://github.com/bertpl/scrolly/actions/workflows/push_to_main.yml)
|
|
43
|
+
[](https://pypi.org/project/scrolly/)
|
|
44
|
+
[](https://pypi.org/project/scrolly/)
|
|
45
|
+
[](https://github.com/bertpl/scrolly/blob/main/LICENSE)
|
|
46
|
+
|
|
47
|
+
## Why it's different
|
|
48
|
+
|
|
49
|
+
- **A 2D canvas you fly around**, not a linear reel. Slides sit on an
|
|
50
|
+
integer grid connected by edges; readers zoom out to a deck map and
|
|
51
|
+
zoom into any slide to scroll through it.
|
|
52
|
+
- **Scroll-driven keyframe animation.** Element properties (position,
|
|
53
|
+
size, opacity, scale, angle) interpolate as the reader scrolls — no
|
|
54
|
+
timeline, no autoplay.
|
|
55
|
+
- **One self-contained HTML file.** A default build inlines every
|
|
56
|
+
asset into a single `index.html` with zero external loads — open it
|
|
57
|
+
by double-clicking, host it anywhere, email it.
|
|
58
|
+
- **Agent-friendly.** The CLI exposes full help, input schemas, deck
|
|
59
|
+
introspection, and numbered error codes, so agentic coding tools
|
|
60
|
+
immediately understand how to author and debug scrolly decks.
|
|
61
|
+
|
|
62
|
+
## Installation
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pip install scrolly # or: uv tool install scrolly
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Quickstart
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
scrolly init my-deck # scaffold a starter deck
|
|
72
|
+
scrolly build my-deck/deck.deck.json --out out --force # compile to one HTML file
|
|
73
|
+
open out/index.html # double-click anywhere
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
A deck is a `.deck.json` manifest plus one `.slide.json` per slide (both
|
|
77
|
+
parsed as JSON5):
|
|
78
|
+
|
|
79
|
+
```json5
|
|
80
|
+
// deck.deck.json
|
|
81
|
+
{
|
|
82
|
+
title: "My Deck",
|
|
83
|
+
slides: [
|
|
84
|
+
{ id: "intro", position: [0, 0], source: "slides/intro.slide.json" },
|
|
85
|
+
],
|
|
86
|
+
edges: [],
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
```json5
|
|
91
|
+
// slides/intro.slide.json
|
|
92
|
+
{
|
|
93
|
+
title: "My Deck",
|
|
94
|
+
elements: [
|
|
95
|
+
{ markdown: "# My Deck\n\nWelcome to your new presentation." },
|
|
96
|
+
],
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Elements
|
|
101
|
+
|
|
102
|
+
Slides hold elements of six types — `markdown`, `image`, `image_sequence`,
|
|
103
|
+
`mermaid`, `html`, and `iframe` — each animatable via scroll-driven
|
|
104
|
+
keyframes. See the [element reference](https://scrolly.readthedocs.io/en/stable/reference/elements/)
|
|
105
|
+
for fields and examples.
|
|
106
|
+
|
|
107
|
+
## Learn more
|
|
108
|
+
|
|
109
|
+
- **[Documentation](https://scrolly.readthedocs.io/en/stable/)** — guided
|
|
110
|
+
walkthrough, concepts, authoring, and the full reference, with a live
|
|
111
|
+
interactive deck right on the homepage.
|
|
112
|
+
- **[Examples](examples/)** — including the
|
|
113
|
+
[stacked-diffs](examples/stacked-diffs/) hero deck.
|
|
114
|
+
- **[Changelog](CHANGELOG.md)**.
|
|
115
|
+
|
|
116
|
+
## License
|
|
117
|
+
|
|
118
|
+
[MIT](https://github.com/bertpl/scrolly/blob/main/LICENSE).
|
scrolly-0.2.4/README.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# scrolly
|
|
2
|
+
|
|
3
|
+
Compile a JSON5 deck into a single, self-contained, scrollable 2D-canvas HTML presentation.
|
|
4
|
+
|
|
5
|
+
*Liked by humans; understood by agents.*
|
|
6
|
+
|
|
7
|
+
[](https://scrolly.readthedocs.io/en/stable/)
|
|
8
|
+
|
|
9
|
+
[](https://github.com/bertpl/scrolly/actions/workflows/push_to_main.yml)
|
|
10
|
+
[](https://pypi.org/project/scrolly/)
|
|
11
|
+
[](https://pypi.org/project/scrolly/)
|
|
12
|
+
[](https://github.com/bertpl/scrolly/blob/main/LICENSE)
|
|
13
|
+
|
|
14
|
+
## Why it's different
|
|
15
|
+
|
|
16
|
+
- **A 2D canvas you fly around**, not a linear reel. Slides sit on an
|
|
17
|
+
integer grid connected by edges; readers zoom out to a deck map and
|
|
18
|
+
zoom into any slide to scroll through it.
|
|
19
|
+
- **Scroll-driven keyframe animation.** Element properties (position,
|
|
20
|
+
size, opacity, scale, angle) interpolate as the reader scrolls — no
|
|
21
|
+
timeline, no autoplay.
|
|
22
|
+
- **One self-contained HTML file.** A default build inlines every
|
|
23
|
+
asset into a single `index.html` with zero external loads — open it
|
|
24
|
+
by double-clicking, host it anywhere, email it.
|
|
25
|
+
- **Agent-friendly.** The CLI exposes full help, input schemas, deck
|
|
26
|
+
introspection, and numbered error codes, so agentic coding tools
|
|
27
|
+
immediately understand how to author and debug scrolly decks.
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install scrolly # or: uv tool install scrolly
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Quickstart
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
scrolly init my-deck # scaffold a starter deck
|
|
39
|
+
scrolly build my-deck/deck.deck.json --out out --force # compile to one HTML file
|
|
40
|
+
open out/index.html # double-click anywhere
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
A deck is a `.deck.json` manifest plus one `.slide.json` per slide (both
|
|
44
|
+
parsed as JSON5):
|
|
45
|
+
|
|
46
|
+
```json5
|
|
47
|
+
// deck.deck.json
|
|
48
|
+
{
|
|
49
|
+
title: "My Deck",
|
|
50
|
+
slides: [
|
|
51
|
+
{ id: "intro", position: [0, 0], source: "slides/intro.slide.json" },
|
|
52
|
+
],
|
|
53
|
+
edges: [],
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
```json5
|
|
58
|
+
// slides/intro.slide.json
|
|
59
|
+
{
|
|
60
|
+
title: "My Deck",
|
|
61
|
+
elements: [
|
|
62
|
+
{ markdown: "# My Deck\n\nWelcome to your new presentation." },
|
|
63
|
+
],
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Elements
|
|
68
|
+
|
|
69
|
+
Slides hold elements of six types — `markdown`, `image`, `image_sequence`,
|
|
70
|
+
`mermaid`, `html`, and `iframe` — each animatable via scroll-driven
|
|
71
|
+
keyframes. See the [element reference](https://scrolly.readthedocs.io/en/stable/reference/elements/)
|
|
72
|
+
for fields and examples.
|
|
73
|
+
|
|
74
|
+
## Learn more
|
|
75
|
+
|
|
76
|
+
- **[Documentation](https://scrolly.readthedocs.io/en/stable/)** — guided
|
|
77
|
+
walkthrough, concepts, authoring, and the full reference, with a live
|
|
78
|
+
interactive deck right on the homepage.
|
|
79
|
+
- **[Examples](examples/)** — including the
|
|
80
|
+
[stacked-diffs](examples/stacked-diffs/) hero deck.
|
|
81
|
+
- **[Changelog](CHANGELOG.md)**.
|
|
82
|
+
|
|
83
|
+
## License
|
|
84
|
+
|
|
85
|
+
[MIT](https://github.com/bertpl/scrolly/blob/main/LICENSE).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "scrolly"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.4"
|
|
4
4
|
description = "CLI that compiles a JSON5 deck + slide files into a self-contained 2D-canvas HTML presentation."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -40,7 +40,7 @@ dev = [
|
|
|
40
40
|
# Exact pin: gitsvg is pre-1.0 and its output schema can shift
|
|
41
41
|
# between releases. Used to regenerate the stacked-diffs example's
|
|
42
42
|
# gitsvg frames.
|
|
43
|
-
"gitsvg==0.2.
|
|
43
|
+
"gitsvg==0.2.3",
|
|
44
44
|
]
|
|
45
45
|
# Optional group for the hero-animation capture pipeline (docs/_gen).
|
|
46
46
|
# Heavy (Playwright ships browser binaries via `make capture-setup`),
|
|
@@ -54,6 +54,8 @@ capture = [
|
|
|
54
54
|
scrolly = "scrolly._cli._cli:cli"
|
|
55
55
|
|
|
56
56
|
[project.urls]
|
|
57
|
+
Homepage = "https://scrolly.readthedocs.io/en/stable/"
|
|
58
|
+
Documentation = "https://scrolly.readthedocs.io/en/stable/"
|
|
57
59
|
Source = "https://github.com/bertpl/scrolly"
|
|
58
60
|
Changelog = "https://github.com/bertpl/scrolly/blob/main/CHANGELOG.md"
|
|
59
61
|
Issues = "https://github.com/bertpl/scrolly/issues"
|
|
@@ -8,6 +8,7 @@ from rich.console import Console
|
|
|
8
8
|
from scrolly import __version__
|
|
9
9
|
from scrolly._cli._errors import errors_command
|
|
10
10
|
from scrolly._cli._introspect import introspect
|
|
11
|
+
from scrolly._cli._schema import schema
|
|
11
12
|
from scrolly.errors import ScrollyError, ValidationError
|
|
12
13
|
from scrolly.pipeline import build_deck, load_deck
|
|
13
14
|
from scrolly.pipeline.lint import lint_deck
|
|
@@ -82,52 +83,6 @@ def build(
|
|
|
82
83
|
click.echo(f"Built '{deck.title or '(untitled)'}': {len(deck.slides)} slides, {len(deck.edges)} edges → {out_dir}")
|
|
83
84
|
|
|
84
85
|
|
|
85
|
-
@cli.command()
|
|
86
|
-
@click.argument("type_name", required=False)
|
|
87
|
-
@click.option(
|
|
88
|
-
"--list-types",
|
|
89
|
-
"list_types",
|
|
90
|
-
is_flag=True,
|
|
91
|
-
help="Print bare type names one per line (no descriptions) for scripting use.",
|
|
92
|
-
)
|
|
93
|
-
def schema(type_name: str | None, list_types: bool) -> None:
|
|
94
|
-
"""Show source file schemas.
|
|
95
|
-
|
|
96
|
-
\b
|
|
97
|
-
scrolly schema → formatted index of available types
|
|
98
|
-
scrolly schema <type> → JSON Schema for <type>
|
|
99
|
-
scrolly schema --list-types → bare type names, one per line (agent / scripting)
|
|
100
|
-
"""
|
|
101
|
-
from scrolly.deck import deck_source_schema
|
|
102
|
-
from scrolly.slide import registered_ir_types
|
|
103
|
-
|
|
104
|
-
ir_types = registered_ir_types()
|
|
105
|
-
all_type_names = sorted(["deck", *ir_types])
|
|
106
|
-
|
|
107
|
-
if list_types:
|
|
108
|
-
for name in all_type_names:
|
|
109
|
-
click.echo(name)
|
|
110
|
-
return
|
|
111
|
-
|
|
112
|
-
if type_name is None:
|
|
113
|
-
click.echo("Available schemas:\n")
|
|
114
|
-
click.echo(f" {'deck':<17}{'.deck.json':<24}Deck structure (slides + edges)")
|
|
115
|
-
for name in sorted(ir_types):
|
|
116
|
-
cls = ir_types[name]
|
|
117
|
-
click.echo(f" {name:<17}{cls.SUFFIX:<24}{cls.DESCRIPTION}")
|
|
118
|
-
return
|
|
119
|
-
|
|
120
|
-
if type_name == "deck":
|
|
121
|
-
click.echo(json.dumps(deck_source_schema(), indent=2))
|
|
122
|
-
return
|
|
123
|
-
|
|
124
|
-
if type_name not in ir_types:
|
|
125
|
-
_err_console.print(f"[red]error:[/red] unknown type '{type_name}' (known: {', '.join(all_type_names)})")
|
|
126
|
-
sys.exit(1)
|
|
127
|
-
|
|
128
|
-
click.echo(json.dumps(ir_types[type_name].source_schema(), indent=2))
|
|
129
|
-
|
|
130
|
-
|
|
131
86
|
@cli.command()
|
|
132
87
|
@click.argument("deck_path", type=click.Path(exists=True, dir_okay=False, path_type=Path))
|
|
133
88
|
@click.option("--strict", is_flag=True, help="Enable additional lint checks (e.g. out-of-range keyframes).")
|
|
@@ -217,3 +172,4 @@ def init(dir_path: Path) -> None:
|
|
|
217
172
|
|
|
218
173
|
cli.add_command(errors_command)
|
|
219
174
|
cli.add_command(introspect)
|
|
175
|
+
cli.add_command(schema)
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""``scrolly schema`` — show source-file and slide-element schemas from the CLI.
|
|
2
|
+
|
|
3
|
+
A Click group with two parallel subcommands, mirroring the
|
|
4
|
+
subcommand-group structure of ``scrolly introspect``:
|
|
5
|
+
|
|
6
|
+
* ``scrolly schema`` — combined index of file and element schemas.
|
|
7
|
+
* ``scrolly schema file`` — index of source-file schemas (deck, slide).
|
|
8
|
+
* ``scrolly schema file <type>`` — JSON Schema for a source-file type.
|
|
9
|
+
* ``scrolly schema file --list-types`` — bare file-type names, one per line.
|
|
10
|
+
* ``scrolly schema element`` — index of element schemas.
|
|
11
|
+
* ``scrolly schema element <type>`` — JSON Schema for an element type.
|
|
12
|
+
* ``scrolly schema element --list-types`` — bare element keys, one per line.
|
|
13
|
+
|
|
14
|
+
``schema`` shows static *type* definitions; ``introspect`` inspects a
|
|
15
|
+
specific *resolved deck instance* — different surfaces, kept distinct.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
import sys
|
|
22
|
+
|
|
23
|
+
import click
|
|
24
|
+
from rich.console import Console
|
|
25
|
+
|
|
26
|
+
_err_console = Console(stderr=True, highlight=False)
|
|
27
|
+
|
|
28
|
+
# Width of the name / suffix columns in the human-readable index, matching
|
|
29
|
+
# the alignment used by ``scrolly errors``.
|
|
30
|
+
_NAME_COL = 17
|
|
31
|
+
_SUFFIX_COL = 24
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# ==================================================================================================
|
|
35
|
+
# schema group
|
|
36
|
+
# ==================================================================================================
|
|
37
|
+
@click.group(name="schema", invoke_without_command=True)
|
|
38
|
+
@click.pass_context
|
|
39
|
+
def schema(ctx: click.Context) -> None:
|
|
40
|
+
"""Show source-file and slide-element schemas.
|
|
41
|
+
|
|
42
|
+
\b
|
|
43
|
+
scrolly schema → combined index (file + element schemas)
|
|
44
|
+
scrolly schema file [<type>] → source-file schemas (deck, slide)
|
|
45
|
+
scrolly schema element [<type>] → slide-element schemas (markdown, image, …)
|
|
46
|
+
|
|
47
|
+
Append --list-types to either subcommand for bare names (agent / scripting).
|
|
48
|
+
Shows static type definitions; use `scrolly introspect` for a resolved deck.
|
|
49
|
+
"""
|
|
50
|
+
if ctx.invoked_subcommand is None:
|
|
51
|
+
_print_file_index()
|
|
52
|
+
click.echo()
|
|
53
|
+
_print_element_index()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# --------------------------------------------------------------------------
|
|
57
|
+
# Subcommands
|
|
58
|
+
# --------------------------------------------------------------------------
|
|
59
|
+
@schema.command(name="file")
|
|
60
|
+
@click.argument("type_name", required=False)
|
|
61
|
+
@click.option(
|
|
62
|
+
"--list-types",
|
|
63
|
+
"list_types",
|
|
64
|
+
is_flag=True,
|
|
65
|
+
help="Print bare file-type names one per line (no descriptions) for scripting use.",
|
|
66
|
+
)
|
|
67
|
+
def schema_file(type_name: str | None, list_types: bool) -> None:
|
|
68
|
+
"""Source-file schemas (deck, slide).
|
|
69
|
+
|
|
70
|
+
\b
|
|
71
|
+
scrolly schema file → formatted index of file types
|
|
72
|
+
scrolly schema file <type> → JSON Schema for <type>
|
|
73
|
+
scrolly schema file --list-types → bare type names, one per line (agent / scripting)
|
|
74
|
+
"""
|
|
75
|
+
names = _file_type_names()
|
|
76
|
+
|
|
77
|
+
if list_types:
|
|
78
|
+
for name in names:
|
|
79
|
+
click.echo(name)
|
|
80
|
+
return
|
|
81
|
+
|
|
82
|
+
if type_name is None:
|
|
83
|
+
_print_file_index()
|
|
84
|
+
return
|
|
85
|
+
|
|
86
|
+
schema_dict = _file_schema(type_name)
|
|
87
|
+
if schema_dict is None:
|
|
88
|
+
_err_console.print(f"[red]error:[/red] unknown file type '{type_name}' (known: {', '.join(names)})")
|
|
89
|
+
sys.exit(1)
|
|
90
|
+
click.echo(json.dumps(schema_dict, indent=2))
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@schema.command(name="element")
|
|
94
|
+
@click.argument("type_name", required=False)
|
|
95
|
+
@click.option(
|
|
96
|
+
"--list-types",
|
|
97
|
+
"list_types",
|
|
98
|
+
is_flag=True,
|
|
99
|
+
help="Print bare element keys one per line (no descriptions) for scripting use.",
|
|
100
|
+
)
|
|
101
|
+
def schema_element(type_name: str | None, list_types: bool) -> None:
|
|
102
|
+
"""Slide-element schemas (markdown, image, …).
|
|
103
|
+
|
|
104
|
+
\b
|
|
105
|
+
scrolly schema element → formatted index of element types
|
|
106
|
+
scrolly schema element <type> → JSON Schema for <type>
|
|
107
|
+
scrolly schema element --list-types → bare element keys, one per line (agent / scripting)
|
|
108
|
+
"""
|
|
109
|
+
from scrolly.slide import element_source_types
|
|
110
|
+
|
|
111
|
+
elements = element_source_types()
|
|
112
|
+
|
|
113
|
+
if list_types:
|
|
114
|
+
for key in elements:
|
|
115
|
+
click.echo(key)
|
|
116
|
+
return
|
|
117
|
+
|
|
118
|
+
if type_name is None:
|
|
119
|
+
_print_element_index()
|
|
120
|
+
return
|
|
121
|
+
|
|
122
|
+
if type_name not in elements:
|
|
123
|
+
_err_console.print(f"[red]error:[/red] unknown element type '{type_name}' (known: {', '.join(elements)})")
|
|
124
|
+
sys.exit(1)
|
|
125
|
+
click.echo(json.dumps(elements[type_name].source_schema(), indent=2))
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
# --------------------------------------------------------------------------
|
|
129
|
+
# Schema lookup + index rendering
|
|
130
|
+
# --------------------------------------------------------------------------
|
|
131
|
+
def _file_type_names() -> list[str]:
|
|
132
|
+
"""Return the sorted source-file type names (deck + registered slide types)."""
|
|
133
|
+
from scrolly.slide import registered_ir_types
|
|
134
|
+
|
|
135
|
+
return sorted(["deck", *registered_ir_types()])
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _file_schema(type_name: str) -> dict | None:
|
|
139
|
+
"""Return the JSON Schema for a source-file type, or ``None`` if unknown."""
|
|
140
|
+
from scrolly.deck import deck_source_schema
|
|
141
|
+
from scrolly.slide import registered_ir_types
|
|
142
|
+
|
|
143
|
+
if type_name == "deck":
|
|
144
|
+
return deck_source_schema()
|
|
145
|
+
ir_types = registered_ir_types()
|
|
146
|
+
if type_name in ir_types:
|
|
147
|
+
return ir_types[type_name].source_schema()
|
|
148
|
+
return None
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _print_file_index() -> None:
|
|
152
|
+
"""Print the human-readable index of source-file schemas."""
|
|
153
|
+
from scrolly.slide import registered_ir_types
|
|
154
|
+
|
|
155
|
+
click.echo("File schemas (source files):\n")
|
|
156
|
+
click.echo(f" {'deck':<{_NAME_COL}}{'.deck.json':<{_SUFFIX_COL}}Deck structure (slides + edges)")
|
|
157
|
+
for name, cls in sorted(registered_ir_types().items()):
|
|
158
|
+
click.echo(f" {name:<{_NAME_COL}}{cls.SUFFIX:<{_SUFFIX_COL}}{cls.DESCRIPTION}")
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _print_element_index() -> None:
|
|
162
|
+
"""Print the human-readable index of slide-element schemas."""
|
|
163
|
+
from scrolly.slide import element_source_types
|
|
164
|
+
|
|
165
|
+
click.echo("Element schemas (slide elements):\n")
|
|
166
|
+
for key, cls in element_source_types().items():
|
|
167
|
+
click.echo(f" {key:<{_NAME_COL}}{cls.DESCRIPTION}")
|
|
@@ -35,7 +35,7 @@ def slides_to_json(
|
|
|
35
35
|
``slides``: map of slide id → {position, title, scroll_range,
|
|
36
36
|
element_count, snap_position_count}.
|
|
37
37
|
``edges``: list of {a: {slide, side}, b: {slide, side}}.
|
|
38
|
-
``groups``: list of {label, color, slide_ids}.
|
|
38
|
+
``groups``: list of {label, color, label_color, slide_ids}.
|
|
39
39
|
"""
|
|
40
40
|
del slide_ids # always deck-wide; param exists for signature uniformity
|
|
41
41
|
|
|
@@ -63,6 +63,7 @@ def slides_to_json(
|
|
|
63
63
|
{
|
|
64
64
|
"label": group.label,
|
|
65
65
|
"color": group.color,
|
|
66
|
+
"label_color": group.label_color,
|
|
66
67
|
"slide_ids": list(group.slide_ids),
|
|
67
68
|
}
|
|
68
69
|
for group in deck.groups
|
|
@@ -79,8 +79,16 @@ def _parse_slides_and_groups(slides_raw: list, deck_path: Path) -> tuple[tuple[S
|
|
|
79
79
|
group_slide_ids.append(slide.id)
|
|
80
80
|
flat_idx += 1
|
|
81
81
|
|
|
82
|
-
color =
|
|
83
|
-
|
|
82
|
+
color = _parse_group_hex_color(item, ctx, "color")
|
|
83
|
+
label_color = _parse_group_hex_color(item, ctx, "label_color")
|
|
84
|
+
groups.append(
|
|
85
|
+
SlideGroup(
|
|
86
|
+
label=label,
|
|
87
|
+
slide_ids=tuple(group_slide_ids),
|
|
88
|
+
color=color,
|
|
89
|
+
label_color=label_color,
|
|
90
|
+
)
|
|
91
|
+
)
|
|
84
92
|
else:
|
|
85
93
|
slide = _parse_slide(item, deck_dir, flat_idx, ctx)
|
|
86
94
|
slides.append(slide)
|
|
@@ -92,16 +100,31 @@ def _parse_slides_and_groups(slides_raw: list, deck_path: Path) -> tuple[tuple[S
|
|
|
92
100
|
_HEX_COLOR_RE = re.compile(r"^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$")
|
|
93
101
|
|
|
94
102
|
|
|
95
|
-
def
|
|
96
|
-
"""Parse and validate an optional hex color from a group object.
|
|
97
|
-
|
|
103
|
+
def _parse_group_hex_color(raw: dict, ctx: str, key: str) -> str | None:
|
|
104
|
+
"""Parse and validate an optional hex color field from a group object.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
raw: The raw group object.
|
|
108
|
+
ctx: Error-context prefix (e.g. ``slides[2]``).
|
|
109
|
+
key: Which field to read — ``color`` (background) or ``label_color``
|
|
110
|
+
(label override).
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
The validated ``#RGB`` / ``#RRGGBB`` string, or ``None`` if the field
|
|
114
|
+
is absent.
|
|
115
|
+
|
|
116
|
+
Raises:
|
|
117
|
+
DeckParseError: If present but not a string (E004) or not a valid hex
|
|
118
|
+
color (E009).
|
|
119
|
+
"""
|
|
120
|
+
if key not in raw:
|
|
98
121
|
return None
|
|
99
|
-
|
|
100
|
-
if not isinstance(
|
|
101
|
-
raise DeckParseError(code="E004", message=f"{ctx}: '
|
|
102
|
-
if not _HEX_COLOR_RE.match(
|
|
103
|
-
raise DeckParseError(code="E009", message=f"{ctx}: '
|
|
104
|
-
return
|
|
122
|
+
value = raw[key]
|
|
123
|
+
if not isinstance(value, str):
|
|
124
|
+
raise DeckParseError(code="E004", message=f"{ctx}: '{key}' must be a string, got {type(value).__name__}")
|
|
125
|
+
if not _HEX_COLOR_RE.match(value):
|
|
126
|
+
raise DeckParseError(code="E009", message=f"{ctx}: '{key}' must be #RGB or #RRGGBB, got '{value}'")
|
|
127
|
+
return value
|
|
105
128
|
|
|
106
129
|
|
|
107
130
|
def _require_list(d: dict, key: str, deck_path: Path) -> list:
|
|
@@ -55,6 +55,17 @@ def deck_source_schema() -> dict:
|
|
|
55
55
|
"type": "string",
|
|
56
56
|
"description": "Group label, displayed above the group rectangle in deck view.",
|
|
57
57
|
},
|
|
58
|
+
"color": {
|
|
59
|
+
"type": "string",
|
|
60
|
+
"description": "Group background fill as #RGB or #RRGGBB. Defaults to a neutral gray.",
|
|
61
|
+
},
|
|
62
|
+
"label_color": {
|
|
63
|
+
"type": "string",
|
|
64
|
+
"description": (
|
|
65
|
+
"Group label color as #RGB or #RRGGBB. Defaults to a "
|
|
66
|
+
"black-or-white auto-contrast pick against the background."
|
|
67
|
+
),
|
|
68
|
+
},
|
|
58
69
|
"slides": {
|
|
59
70
|
"type": "array",
|
|
60
71
|
"description": "Slides in this group.",
|
|
@@ -14,5 +14,5 @@ require `title` and `elements`; per-slide entries in a deck require
|
|
|
14
14
|
|
|
15
15
|
## How to fix
|
|
16
16
|
|
|
17
|
-
Add the missing field. Run `scrolly schema deck` or `scrolly schema
|
|
18
|
-
slide` to see the full required
|
|
17
|
+
Add the missing field. Run `scrolly schema file deck` or `scrolly schema
|
|
18
|
+
file slide` to see the full required schema for each file type.
|
|
@@ -17,5 +17,5 @@ path embedded in the error message.
|
|
|
17
17
|
## How to fix
|
|
18
18
|
|
|
19
19
|
Read the embedded Pydantic message — it names the field path and the
|
|
20
|
-
constraint that failed. Cross-reference with `scrolly schema slide`
|
|
21
|
-
for the expected
|
|
20
|
+
constraint that failed. Cross-reference with `scrolly schema file slide`
|
|
21
|
+
for the expected schema of the slide source.
|
|
@@ -13,5 +13,6 @@ An element object with no recognised type field — no `markdown:`,
|
|
|
13
13
|
|
|
14
14
|
## How to fix
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
Run `scrolly schema element --list-types` to see the valid element
|
|
17
|
+
types, then `scrolly schema element <type>` for one's fields. Each
|
|
18
|
+
element must include the field that names its type.
|