scrolly 0.2.3__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.
Files changed (124) hide show
  1. {scrolly-0.2.3 → scrolly-0.2.4}/.gitignore +3 -0
  2. scrolly-0.2.4/PKG-INFO +118 -0
  3. scrolly-0.2.4/README.md +85 -0
  4. {scrolly-0.2.3 → scrolly-0.2.4}/pyproject.toml +3 -1
  5. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/_cli/_cli.py +2 -46
  6. scrolly-0.2.4/scrolly/_cli/_schema.py +167 -0
  7. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/catalog/E003.md +2 -2
  8. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/catalog/E299.md +2 -2
  9. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/catalog/E601.md +3 -2
  10. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/render/assets/canvas.css +1 -1
  11. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/render/assets/canvas.js +15 -1
  12. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/slide/__init__.py +2 -1
  13. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/slide/ir/__init__.py +2 -0
  14. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/slide/ir/_framework/element.py +49 -1
  15. scrolly-0.2.3/PKG-INFO +0 -81
  16. scrolly-0.2.3/README.md +0 -50
  17. {scrolly-0.2.3 → scrolly-0.2.4}/LICENSE +0 -0
  18. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/__init__.py +0 -0
  19. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/_cli/__init__.py +0 -0
  20. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/_cli/_errors.py +0 -0
  21. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/_cli/_introspect/__init__.py +0 -0
  22. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/_cli/_introspect/_assets.py +0 -0
  23. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/_cli/_introspect/_common.py +0 -0
  24. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/_cli/_introspect/_dom.py +0 -0
  25. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/_cli/_introspect/_elements.py +0 -0
  26. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/_cli/_introspect/_slides.py +0 -0
  27. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/_cli/_introspect/_snaps.py +0 -0
  28. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/_cli/_introspect/_snapshot.py +0 -0
  29. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/_cli/_introspect/_timeline.py +0 -0
  30. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/deck/__init__.py +0 -0
  31. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/deck/inference.py +0 -0
  32. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/deck/introspect.py +0 -0
  33. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/deck/model.py +0 -0
  34. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/deck/parser.py +0 -0
  35. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/deck/schema.py +0 -0
  36. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/deck/validator.py +0 -0
  37. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/__init__.py +0 -0
  38. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/_catalog.py +0 -0
  39. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/_codes.py +0 -0
  40. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/_report.py +0 -0
  41. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/_validation_error.py +0 -0
  42. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/catalog/E001.md +0 -0
  43. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/catalog/E002.md +0 -0
  44. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/catalog/E004.md +0 -0
  45. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/catalog/E005.md +0 -0
  46. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/catalog/E006.md +0 -0
  47. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/catalog/E007.md +0 -0
  48. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/catalog/E008.md +0 -0
  49. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/catalog/E009.md +0 -0
  50. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/catalog/E010.md +0 -0
  51. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/catalog/E011.md +0 -0
  52. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/catalog/E012.md +0 -0
  53. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/catalog/E101.md +0 -0
  54. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/catalog/E102.md +0 -0
  55. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/catalog/E103.md +0 -0
  56. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/catalog/E201.md +0 -0
  57. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/catalog/E202.md +0 -0
  58. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/catalog/E203.md +0 -0
  59. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/catalog/E204.md +0 -0
  60. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/catalog/E205.md +0 -0
  61. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/catalog/E206.md +0 -0
  62. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/catalog/E207.md +0 -0
  63. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/catalog/E301.md +0 -0
  64. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/catalog/E302.md +0 -0
  65. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/catalog/E303.md +0 -0
  66. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/catalog/E304.md +0 -0
  67. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/catalog/E305.md +0 -0
  68. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/catalog/E306.md +0 -0
  69. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/catalog/E307.md +0 -0
  70. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/catalog/E308.md +0 -0
  71. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/catalog/E401.md +0 -0
  72. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/catalog/E402.md +0 -0
  73. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/catalog/E403.md +0 -0
  74. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/catalog/E501.md +0 -0
  75. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/catalog/E502.md +0 -0
  76. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/catalog/E503.md +0 -0
  77. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/catalog/E504.md +0 -0
  78. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/catalog/E505.md +0 -0
  79. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/catalog/E602.md +0 -0
  80. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/catalog/E603.md +0 -0
  81. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/catalog/E701.md +0 -0
  82. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/catalog/E702.md +0 -0
  83. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/errors/catalog/__init__.py +0 -0
  84. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/pipeline/__init__.py +0 -0
  85. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/pipeline/_bundler.py +0 -0
  86. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/pipeline/assets.py +0 -0
  87. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/pipeline/introspect.py +0 -0
  88. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/pipeline/lint.py +0 -0
  89. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/pipeline/loader.py +0 -0
  90. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/pipeline/orchestrator.py +0 -0
  91. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/pipeline/writer.py +0 -0
  92. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/render/__init__.py +0 -0
  93. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/render/assembler.py +0 -0
  94. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/render/assets/mermaid-LICENSE +0 -0
  95. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/render/assets/mermaid.min.js +0 -0
  96. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/render/bundled_assets.py +0 -0
  97. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/render/color.py +0 -0
  98. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/render/fan.py +0 -0
  99. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/render/nav_data.py +0 -0
  100. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/render/templates/index.html.j2 +0 -0
  101. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/render/zoom_control.py +0 -0
  102. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/slide/element_ir/__init__.py +0 -0
  103. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/slide/element_ir/ir.py +0 -0
  104. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/slide/element_ir/processor.py +0 -0
  105. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/slide/element_ir/registry.py +0 -0
  106. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/slide/element_ir/rendered.py +0 -0
  107. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/slide/element_ir/renderers/__init__.py +0 -0
  108. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/slide/element_ir/renderers/_shared.py +0 -0
  109. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/slide/element_ir/renderers/html.py +0 -0
  110. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/slide/element_ir/renderers/iframe.py +0 -0
  111. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/slide/element_ir/renderers/image.py +0 -0
  112. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/slide/element_ir/renderers/image_sequence.py +0 -0
  113. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/slide/element_ir/renderers/markdown.py +0 -0
  114. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/slide/element_ir/renderers/mermaid.py +0 -0
  115. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/slide/html.py +0 -0
  116. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/slide/introspect.py +0 -0
  117. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/slide/ir/_framework/__init__.py +0 -0
  118. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/slide/ir/_framework/animated_values.py +0 -0
  119. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/slide/ir/_framework/utils.py +0 -0
  120. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/slide/ir/slide.py +0 -0
  121. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/slide/processor.py +0 -0
  122. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/slide/registry.py +0 -0
  123. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/slide/renderers/__init__.py +0 -0
  124. {scrolly-0.2.3 → scrolly-0.2.4}/scrolly/slide/renderers/slide.py +0 -0
@@ -5,6 +5,9 @@ dist/
5
5
  .pytest_cache/
6
6
  .ruff_cache/
7
7
 
8
+ # Docs site build output (mkdocs build)
9
+ /site/
10
+
8
11
  # Golden-test debug output (written next to the fixture on mismatch)
9
12
  *.actual.html
10
13
 
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
+ [![scrolly — scroll-driven 2D-canvas presentations](https://raw.githubusercontent.com/bertpl/scrolly/main/docs/assets/hero-v4.webp)](https://scrolly.readthedocs.io/en/stable/)
41
+
42
+ [![CI](https://img.shields.io/github/actions/workflow/status/bertpl/scrolly/push_to_main.yml?branch=main&label=CI)](https://github.com/bertpl/scrolly/actions/workflows/push_to_main.yml)
43
+ [![PyPI](https://img.shields.io/pypi/v/scrolly.svg)](https://pypi.org/project/scrolly/)
44
+ [![Python](https://img.shields.io/pypi/pyversions/scrolly.svg)](https://pypi.org/project/scrolly/)
45
+ [![License](https://img.shields.io/badge/license-MIT-blue)](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).
@@ -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
+ [![scrolly — scroll-driven 2D-canvas presentations](https://raw.githubusercontent.com/bertpl/scrolly/main/docs/assets/hero-v4.webp)](https://scrolly.readthedocs.io/en/stable/)
8
+
9
+ [![CI](https://img.shields.io/github/actions/workflow/status/bertpl/scrolly/push_to_main.yml?branch=main&label=CI)](https://github.com/bertpl/scrolly/actions/workflows/push_to_main.yml)
10
+ [![PyPI](https://img.shields.io/pypi/v/scrolly.svg)](https://pypi.org/project/scrolly/)
11
+ [![Python](https://img.shields.io/pypi/pyversions/scrolly.svg)](https://pypi.org/project/scrolly/)
12
+ [![License](https://img.shields.io/badge/license-MIT-blue)](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"
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"
@@ -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}")
@@ -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 shape for each file type.
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 shape of the slide source.
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
- Check the element JSON against `scrolly schema slide`. Each element
17
- must include the field that names its type.
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.
@@ -1023,7 +1023,7 @@ body.pan-transitioning .canvas {
1023
1023
  left: 0;
1024
1024
  width: 100%;
1025
1025
  height: 100%;
1026
- background: rgba(60, 60, 60, 0.5);
1026
+ background: rgba(200, 200, 200, 0.5);
1027
1027
  backdrop-filter: blur(16px);
1028
1028
  -webkit-backdrop-filter: blur(16px);
1029
1029
  }
@@ -1651,6 +1651,18 @@
1651
1651
  };
1652
1652
  }
1653
1653
 
1654
+ // ---- resolveVersionLabel (pure — help-screen version display) ------------
1655
+ // The " vX.Y.Z" fragment shown after "scrolly" in the help About panel, or
1656
+ // "" when there is nothing to show. A `scrolly-version` URL override (null
1657
+ // when the param is absent) takes precedence over the built-in
1658
+ // `meta.version`; an empty override hides the version entirely — used by the
1659
+ // capture pipeline so rendered help screens don't bake in a pre-release
1660
+ // version number.
1661
+ function resolveVersionLabel(override, metaVersion) {
1662
+ const version = override !== null ? override : metaVersion;
1663
+ return version ? " v" + version : "";
1664
+ }
1665
+
1654
1666
  // ---- Exports (for Node.js / Vitest testing) -------------------------------
1655
1667
 
1656
1668
  if (typeof exports !== "undefined") {
@@ -1668,6 +1680,7 @@
1668
1680
  exports.resolveTarget = resolveTarget;
1669
1681
  exports.decompressBundle = decompressBundle;
1670
1682
  exports.buildAutomationHook = buildAutomationHook;
1683
+ exports.resolveVersionLabel = resolveVersionLabel;
1671
1684
  }
1672
1685
 
1673
1686
  // ---- DOM code (skipped in Node.js) ----------------------------------------
@@ -2054,11 +2067,12 @@
2054
2067
  const uniqueLine = formatCounts(p.unique);
2055
2068
  const compressedLine = p.compressed ? "yes" : "no";
2056
2069
  const savedLine = p.bytes_saved > 0 ? formatBytes(p.bytes_saved) : "0 B";
2070
+ const versionParam = new URLSearchParams(window.location.search).get("scrolly-version");
2057
2071
 
2058
2072
  body.innerHTML =
2059
2073
  '<h2>About</h2>' +
2060
2074
  '<div class="help-about">' +
2061
- '<p class="help-about-title"><strong>scrolly</strong> v' + meta.version + '</p>' +
2075
+ '<p class="help-about-title"><strong>scrolly</strong>' + resolveVersionLabel(versionParam, meta.version) + '</p>' +
2062
2076
  '<p><a href="https://opensource.org/licenses/MIT" target="_blank" rel="noopener">MIT License</a></p>' +
2063
2077
  '<p>' + meta.author + '</p>' +
2064
2078
  '<p><a href="' + meta.pypi_url + '" target="_blank" rel="noopener">' + meta.pypi_url + '</a></p>' +
@@ -7,7 +7,7 @@ renderers with the element-IR registry.
7
7
 
8
8
  from scrolly.slide.element_ir import renderers as _element_renderers # noqa: F401 — register element renderers
9
9
  from scrolly.slide.html import SlideHTML
10
- from scrolly.slide.ir import SlideIR
10
+ from scrolly.slide.ir import SlideIR, element_source_types
11
11
  from scrolly.slide.processor import Renderer
12
12
  from scrolly.slide.registry import (
13
13
  find_renderer,
@@ -26,6 +26,7 @@ __all__ = [
26
26
  "Renderer",
27
27
  "SlideHTML",
28
28
  "SlideIR",
29
+ "element_source_types",
29
30
  "find_renderer",
30
31
  "get_ir_class_for_path",
31
32
  "register_ir",
@@ -15,6 +15,7 @@ from scrolly.slide.ir._framework.element import (
15
15
  MarkdownElement,
16
16
  MermaidElement,
17
17
  SlideElement,
18
+ element_source_types,
18
19
  )
19
20
  from scrolly.slide.ir._framework.utils import parse_json5_ir, resolve_asset_paths
20
21
  from scrolly.slide.ir.slide import SlideIR
@@ -33,6 +34,7 @@ __all__ = [
33
34
  "SlideElement",
34
35
  "SlideIR",
35
36
  "Vec2Keyframes",
37
+ "element_source_types",
36
38
  "parse_json5_ir",
37
39
  "resolve_asset_paths",
38
40
  ]