scrolly 0.2.1__tar.gz → 0.2.2__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/LICENSE +28 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/PKG-INFO +7 -3
- {scrolly-0.2.1 → scrolly-0.2.2}/README.md +6 -2
- {scrolly-0.2.1 → scrolly-0.2.2}/pyproject.toml +16 -1
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/_cli/_cli.py +10 -0
- scrolly-0.2.2/scrolly/errors/catalog/E306.md +20 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/pipeline/lint.py +2 -3
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/pipeline/orchestrator.py +31 -3
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/pipeline/writer.py +18 -7
- scrolly-0.2.2/scrolly/render/__init__.py +6 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/render/assembler.py +25 -6
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/render/assets/canvas.css +42 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/render/assets/canvas.js +236 -123
- scrolly-0.2.1/LICENSE → scrolly-0.2.2/scrolly/render/assets/mermaid-LICENSE +2 -2
- scrolly-0.2.2/scrolly/render/assets/mermaid.min.js +3405 -0
- scrolly-0.2.2/scrolly/render/bundled_assets.py +147 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/slide/element_ir/rendered.py +1 -1
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/slide/element_ir/renderers/image_sequence.py +65 -51
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/slide/html.py +1 -1
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/slide/introspect.py +6 -5
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/slide/ir/_framework/element.py +41 -29
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/slide/renderers/slide.py +18 -1
- scrolly-0.2.1/scrolly/errors/catalog/E306.md +0 -20
- scrolly-0.2.1/scrolly/render/__init__.py +0 -6
- scrolly-0.2.1/scrolly/render/bundled_assets.py +0 -55
- {scrolly-0.2.1 → scrolly-0.2.2}/.gitignore +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/__init__.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/_cli/__init__.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/_cli/_errors.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/_cli/_introspect/__init__.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/_cli/_introspect/_assets.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/_cli/_introspect/_common.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/_cli/_introspect/_dom.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/_cli/_introspect/_elements.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/_cli/_introspect/_slides.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/_cli/_introspect/_snaps.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/_cli/_introspect/_snapshot.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/_cli/_introspect/_timeline.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/deck/__init__.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/deck/inference.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/deck/introspect.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/deck/model.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/deck/parser.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/deck/schema.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/deck/validator.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/__init__.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/_catalog.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/_codes.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/_report.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/_validation_error.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/catalog/E001.md +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/catalog/E002.md +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/catalog/E003.md +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/catalog/E004.md +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/catalog/E005.md +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/catalog/E006.md +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/catalog/E007.md +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/catalog/E008.md +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/catalog/E009.md +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/catalog/E010.md +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/catalog/E011.md +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/catalog/E012.md +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/catalog/E101.md +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/catalog/E102.md +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/catalog/E103.md +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/catalog/E201.md +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/catalog/E202.md +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/catalog/E203.md +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/catalog/E204.md +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/catalog/E205.md +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/catalog/E206.md +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/catalog/E207.md +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/catalog/E299.md +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/catalog/E301.md +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/catalog/E302.md +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/catalog/E303.md +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/catalog/E304.md +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/catalog/E305.md +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/catalog/E307.md +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/catalog/E308.md +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/catalog/E401.md +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/catalog/E402.md +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/catalog/E403.md +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/catalog/E501.md +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/catalog/E502.md +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/catalog/E503.md +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/catalog/E504.md +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/catalog/E505.md +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/catalog/E601.md +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/catalog/E602.md +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/catalog/E603.md +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/catalog/E701.md +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/catalog/E702.md +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/errors/catalog/__init__.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/pipeline/__init__.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/pipeline/_bundler.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/pipeline/assets.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/pipeline/introspect.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/pipeline/loader.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/render/fan.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/render/nav_data.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/render/templates/index.html.j2 +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/render/zoom_control.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/slide/__init__.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/slide/element_ir/__init__.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/slide/element_ir/ir.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/slide/element_ir/processor.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/slide/element_ir/registry.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/slide/element_ir/renderers/__init__.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/slide/element_ir/renderers/_shared.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/slide/element_ir/renderers/html.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/slide/element_ir/renderers/iframe.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/slide/element_ir/renderers/image.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/slide/element_ir/renderers/markdown.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/slide/element_ir/renderers/mermaid.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/slide/ir/__init__.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/slide/ir/_framework/__init__.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/slide/ir/_framework/animated_values.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/slide/ir/_framework/utils.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/slide/ir/slide.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/slide/processor.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/slide/registry.py +0 -0
- {scrolly-0.2.1 → scrolly-0.2.2}/scrolly/slide/renderers/__init__.py +0 -0
scrolly-0.2.2/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Bert Pluymers
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
----
|
|
24
|
+
|
|
25
|
+
scrolly bundles third-party assets that ship under their own licenses,
|
|
26
|
+
located alongside the asset in the package tree. See for example
|
|
27
|
+
`scrolly/render/assets/mermaid-LICENSE` (MIT — Copyright (c) 2014-2022
|
|
28
|
+
Knut Sveidqvist).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scrolly
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: CLI that compiles a JSON5 deck + slide files into a self-contained 2D-canvas HTML presentation.
|
|
5
5
|
Project-URL: Source, https://github.com/bertpl/scrolly
|
|
6
6
|
Project-URL: Changelog, https://github.com/bertpl/scrolly/blob/main/CHANGELOG.md
|
|
@@ -33,6 +33,10 @@ Description-Content-Type: text/markdown
|
|
|
33
33
|
|
|
34
34
|
Compile a JSON5 deck into a self-contained, scrollable 2D-canvas HTML presentation.
|
|
35
35
|
|
|
36
|
+
*Liked by humans; understood by agents.*
|
|
37
|
+
|
|
38
|
+
[](https://github.com/bertpl/scrolly)
|
|
39
|
+
|
|
36
40
|
[](https://github.com/bertpl/scrolly/actions/workflows/push_to_main.yml)
|
|
37
41
|
[](https://pypi.org/project/scrolly/)
|
|
38
42
|
[](https://pypi.org/project/scrolly/)
|
|
@@ -53,11 +57,11 @@ uv tool install scrolly
|
|
|
53
57
|
## Quickstart
|
|
54
58
|
|
|
55
59
|
```bash
|
|
56
|
-
scrolly build examples/
|
|
60
|
+
scrolly build examples/stacked-diffs/deck.deck.json --out /tmp/scrolly-out --force
|
|
57
61
|
open /tmp/scrolly-out/index.html
|
|
58
62
|
```
|
|
59
63
|
|
|
60
|
-
See [`examples/
|
|
64
|
+
See [`examples/stacked-diffs/`](examples/stacked-diffs/) for a complete example deck.
|
|
61
65
|
|
|
62
66
|
## Source format
|
|
63
67
|
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
Compile a JSON5 deck into a self-contained, scrollable 2D-canvas HTML presentation.
|
|
4
4
|
|
|
5
|
+
*Liked by humans; understood by agents.*
|
|
6
|
+
|
|
7
|
+
[](https://github.com/bertpl/scrolly)
|
|
8
|
+
|
|
5
9
|
[](https://github.com/bertpl/scrolly/actions/workflows/push_to_main.yml)
|
|
6
10
|
[](https://pypi.org/project/scrolly/)
|
|
7
11
|
[](https://pypi.org/project/scrolly/)
|
|
@@ -22,11 +26,11 @@ uv tool install scrolly
|
|
|
22
26
|
## Quickstart
|
|
23
27
|
|
|
24
28
|
```bash
|
|
25
|
-
scrolly build examples/
|
|
29
|
+
scrolly build examples/stacked-diffs/deck.deck.json --out /tmp/scrolly-out --force
|
|
26
30
|
open /tmp/scrolly-out/index.html
|
|
27
31
|
```
|
|
28
32
|
|
|
29
|
-
See [`examples/
|
|
33
|
+
See [`examples/stacked-diffs/`](examples/stacked-diffs/) for a complete example deck.
|
|
30
34
|
|
|
31
35
|
## Source format
|
|
32
36
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "scrolly"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.2"
|
|
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"
|
|
@@ -37,6 +37,17 @@ dev = [
|
|
|
37
37
|
"pytest-cov>=6.0",
|
|
38
38
|
"ruff>=0.14.0",
|
|
39
39
|
"pre-commit>=4.0",
|
|
40
|
+
# Exact pin: gitsvg is pre-1.0 and its output schema can shift
|
|
41
|
+
# between releases. Used to regenerate the stacked-diffs example's
|
|
42
|
+
# gitsvg frames.
|
|
43
|
+
"gitsvg==0.2.2",
|
|
44
|
+
]
|
|
45
|
+
# Optional group for the hero-animation capture pipeline (docs/_gen).
|
|
46
|
+
# Heavy (Playwright ships browser binaries via `make capture-setup`),
|
|
47
|
+
# so it's separate from `dev` — casual contributors don't pay the cost.
|
|
48
|
+
capture = [
|
|
49
|
+
"playwright>=1.40",
|
|
50
|
+
"pillow>=10.0",
|
|
40
51
|
]
|
|
41
52
|
|
|
42
53
|
[project.scripts]
|
|
@@ -66,6 +77,10 @@ select = ["I"]
|
|
|
66
77
|
|
|
67
78
|
[tool.pytest.ini_options]
|
|
68
79
|
testpaths = ["tests/python"]
|
|
80
|
+
# The hero-animation engine lives under docs/_gen (dev-only tooling, not
|
|
81
|
+
# shipped in the wheel); put it on the path so its pure-logic unit tests
|
|
82
|
+
# can import it without the optional `capture` group installed.
|
|
83
|
+
pythonpath = ["docs/_gen"]
|
|
69
84
|
|
|
70
85
|
[tool.coverage.run]
|
|
71
86
|
source = ["scrolly"]
|
|
@@ -43,6 +43,14 @@ def cli() -> None:
|
|
|
43
43
|
is_flag=True,
|
|
44
44
|
help="Disable gzip compression of inlined assets.",
|
|
45
45
|
)
|
|
46
|
+
@click.option(
|
|
47
|
+
"--offline",
|
|
48
|
+
is_flag=True,
|
|
49
|
+
help=(
|
|
50
|
+
"Skip the mermaid CDN download and use the wheel-bundled mermaid for "
|
|
51
|
+
"byte-reproducibility. SCROLLY_OFFLINE=1 in the environment is equivalent."
|
|
52
|
+
),
|
|
53
|
+
)
|
|
46
54
|
def build(
|
|
47
55
|
deck_path: Path,
|
|
48
56
|
out_dir: Path,
|
|
@@ -51,6 +59,7 @@ def build(
|
|
|
51
59
|
strict: bool,
|
|
52
60
|
simplified_zoom_control: bool,
|
|
53
61
|
no_compress: bool,
|
|
62
|
+
offline: bool,
|
|
54
63
|
) -> None:
|
|
55
64
|
"""Build a deck into a self-contained HTML presentation."""
|
|
56
65
|
try:
|
|
@@ -61,6 +70,7 @@ def build(
|
|
|
61
70
|
inline=not no_inline,
|
|
62
71
|
simplified_zoom_control=simplified_zoom_control,
|
|
63
72
|
compress=not no_compress,
|
|
73
|
+
offline=offline,
|
|
64
74
|
)
|
|
65
75
|
except ScrollyError as e:
|
|
66
76
|
_err_console.print(f"[red]error:[/red] {e}")
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# E306 - image_sequence timing constraint violated
|
|
2
|
+
|
|
3
|
+
## Cause
|
|
4
|
+
|
|
5
|
+
One of the timing constraints on `image_sequence` is violated:
|
|
6
|
+
`frame_distance` must be `> 0`; `hold_fraction` must be in `[0, 1)` (so the
|
|
7
|
+
crossfade `frame_distance * (1 - hold_fraction)` stays positive); `fade_in`
|
|
8
|
+
and `fade_out` must be `>= 0`.
|
|
9
|
+
|
|
10
|
+
## Example
|
|
11
|
+
|
|
12
|
+
```json5
|
|
13
|
+
{ image_sequence: ["a.png", "b.png"], frame_distance: 400, hold_fraction: 1 }
|
|
14
|
+
// hold_fraction = 1 leaves no room to crossfade
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## How to fix
|
|
18
|
+
|
|
19
|
+
Set `frame_distance > 0`, `0 <= hold_fraction < 1`, and `fade_in,
|
|
20
|
+
fade_out >= 0`. `hold_fraction` defaults to `0.2`, so it can be omitted.
|
|
@@ -145,9 +145,8 @@ def _check_image_sequence(
|
|
|
145
145
|
diagnostics: list[Diagnostic],
|
|
146
146
|
) -> None:
|
|
147
147
|
"""Check the auto-generated opacity keyframes for an image sequence element."""
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
timeline_end = el.scroll_offset + (n - 1) * el.frame_distance + el.hold + el.fade_out
|
|
148
|
+
timeline_start = el.timeline_start()
|
|
149
|
+
timeline_end = el.timeline_end()
|
|
151
150
|
if timeline_start < 0:
|
|
152
151
|
diagnostics.append(
|
|
153
152
|
Diagnostic(
|
|
@@ -12,6 +12,7 @@ from scrolly.pipeline.assets import copy_assets, rewrite_asset_refs
|
|
|
12
12
|
from scrolly.pipeline.loader import load_deck
|
|
13
13
|
from scrolly.pipeline.writer import write_output
|
|
14
14
|
from scrolly.render.assembler import assemble
|
|
15
|
+
from scrolly.render.bundled_assets import MermaidAsset, mermaid_asset
|
|
15
16
|
from scrolly.slide.html import SlideHTML
|
|
16
17
|
from scrolly.slide.ir import SlideIR
|
|
17
18
|
from scrolly.slide.registry import find_renderer
|
|
@@ -25,8 +26,28 @@ def build_deck(
|
|
|
25
26
|
inline: bool = True,
|
|
26
27
|
simplified_zoom_control: bool = False,
|
|
27
28
|
compress: bool = True,
|
|
29
|
+
offline: bool = False,
|
|
28
30
|
) -> Deck:
|
|
29
|
-
"""Build a deck from `deck_path` into `out_dir`. Returns the fully-resolved `Deck`.
|
|
31
|
+
"""Build a deck from `deck_path` into `out_dir`. Returns the fully-resolved `Deck`.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
deck_path: Path to the ``.deck.json`` source.
|
|
35
|
+
out_dir: Destination directory for ``index.html`` (and bundled
|
|
36
|
+
assets when ``inline=False``).
|
|
37
|
+
force: Allow overwriting a non-empty ``out_dir``.
|
|
38
|
+
inline: Inline CSS/JS/mermaid into ``index.html`` (default) vs.
|
|
39
|
+
emit separate files.
|
|
40
|
+
simplified_zoom_control: Use the legacy single-icon zoom-out
|
|
41
|
+
control instead of the default deck mini-map.
|
|
42
|
+
compress: Emit the combined-payload gzip+base64 bundle when the
|
|
43
|
+
≥5% savings gate passes.
|
|
44
|
+
offline: Skip the mermaid CDN download and use the
|
|
45
|
+
wheel-bundled mermaid instead. Honored together with the
|
|
46
|
+
``SCROLLY_OFFLINE`` environment variable.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
The fully-resolved ``Deck``.
|
|
50
|
+
"""
|
|
30
51
|
deck, slide_irs = load_deck(deck_path)
|
|
31
52
|
|
|
32
53
|
# Bundler is the canonical "compressible payload tracker" whenever
|
|
@@ -56,6 +77,13 @@ def build_deck(
|
|
|
56
77
|
if fallback:
|
|
57
78
|
chunks = _substitute_fallback(chunks, fallback)
|
|
58
79
|
|
|
80
|
+
# Resolve mermaid once when any chunk needs it — threaded into both
|
|
81
|
+
# assemble (for inlined content + help-screen version) and write_output
|
|
82
|
+
# (for the standalone-file emission under `inline=False`).
|
|
83
|
+
mermaid: MermaidAsset | None = None
|
|
84
|
+
if any(chunk.has_mermaid for chunk in chunks.values()):
|
|
85
|
+
mermaid = mermaid_asset(offline=offline)
|
|
86
|
+
|
|
59
87
|
html = assemble(
|
|
60
88
|
deck,
|
|
61
89
|
chunks,
|
|
@@ -63,10 +91,10 @@ def build_deck(
|
|
|
63
91
|
simplified_zoom_control=simplified_zoom_control,
|
|
64
92
|
compressed_payload_json=compressed_payload_json,
|
|
65
93
|
bundle_stats=bundle_stats,
|
|
94
|
+
mermaid=mermaid,
|
|
66
95
|
)
|
|
67
|
-
has_mermaid = any(chunk.has_mermaid for chunk in chunks.values())
|
|
68
96
|
|
|
69
|
-
write_output(out_dir, html, force=force,
|
|
97
|
+
write_output(out_dir, html, force=force, mermaid=mermaid, inline=inline)
|
|
70
98
|
if not inline:
|
|
71
99
|
copy_assets(chunks, out_dir)
|
|
72
100
|
|
|
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
|
|
7
7
|
from scrolly.errors import OutputError
|
|
8
|
-
from scrolly.render import
|
|
8
|
+
from scrolly.render import MermaidAsset, iter_assets
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
def write_output(
|
|
@@ -13,13 +13,25 @@ def write_output(
|
|
|
13
13
|
html: str,
|
|
14
14
|
*,
|
|
15
15
|
force: bool = False,
|
|
16
|
-
|
|
16
|
+
mermaid: MermaidAsset | None = None,
|
|
17
17
|
inline: bool = True,
|
|
18
18
|
) -> None:
|
|
19
19
|
"""Write `html` as `out_dir/index.html` and optionally copy bundled assets.
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
Args:
|
|
22
|
+
out_dir: Destination directory.
|
|
23
|
+
html: Assembled page HTML.
|
|
24
|
+
force: Allow overwriting a non-empty `out_dir`.
|
|
25
|
+
mermaid: Resolved mermaid asset (passed through from
|
|
26
|
+
:func:`build_deck`). When ``inline=False`` and ``mermaid``
|
|
27
|
+
is non-None, the mermaid JS file is written alongside the
|
|
28
|
+
other bundled assets.
|
|
29
|
+
inline: When ``True``, only ``index.html`` is written (CSS, JS,
|
|
30
|
+
and mermaid are embedded in the HTML).
|
|
31
|
+
|
|
32
|
+
Raises:
|
|
33
|
+
OutputError: ``out_dir`` exists but is not a directory, or is
|
|
34
|
+
non-empty without ``force=True``.
|
|
23
35
|
"""
|
|
24
36
|
if out_dir.exists():
|
|
25
37
|
if not out_dir.is_dir():
|
|
@@ -36,6 +48,5 @@ def write_output(
|
|
|
36
48
|
if not inline:
|
|
37
49
|
for name, content in iter_assets():
|
|
38
50
|
(out_dir / name).write_bytes(content)
|
|
39
|
-
if
|
|
40
|
-
|
|
41
|
-
(out_dir / name).write_bytes(content)
|
|
51
|
+
if mermaid is not None:
|
|
52
|
+
(out_dir / mermaid.name).write_bytes(mermaid.content)
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"""Final HTML assembly — canvas template + bundled static assets."""
|
|
2
|
+
|
|
3
|
+
from scrolly.render.assembler import assemble
|
|
4
|
+
from scrolly.render.bundled_assets import MermaidAsset, bundled_css, bundled_js, iter_assets, mermaid_asset
|
|
5
|
+
|
|
6
|
+
__all__ = ["MermaidAsset", "assemble", "bundled_css", "bundled_js", "iter_assets", "mermaid_asset"]
|
|
@@ -15,7 +15,7 @@ from jinja2 import Environment, PackageLoader, StrictUndefined, select_autoescap
|
|
|
15
15
|
from scrolly import __version__
|
|
16
16
|
from scrolly.deck import Deck
|
|
17
17
|
from scrolly.pipeline._bundler import BundleStats
|
|
18
|
-
from scrolly.render.bundled_assets import bundled_css, bundled_js
|
|
18
|
+
from scrolly.render.bundled_assets import MermaidAsset, bundled_css, bundled_js
|
|
19
19
|
from scrolly.render.nav_data import build_nav_data
|
|
20
20
|
from scrolly.render.zoom_control import MinimapGeometry, compute_minimap_geometry
|
|
21
21
|
from scrolly.slide import SlideHTML
|
|
@@ -43,6 +43,7 @@ def assemble(
|
|
|
43
43
|
simplified_zoom_control: bool = False,
|
|
44
44
|
compressed_payload_json: str | None = None,
|
|
45
45
|
bundle_stats: BundleStats | None = None,
|
|
46
|
+
mermaid: MermaidAsset | None = None,
|
|
46
47
|
) -> str:
|
|
47
48
|
"""Render the deck and its chunks into a single HTML string.
|
|
48
49
|
|
|
@@ -59,6 +60,10 @@ def assemble(
|
|
|
59
60
|
bundle_stats: Stats from the bundler's ``build()``, used to
|
|
60
61
|
populate the help-screen statistics. ``None`` when no bundle
|
|
61
62
|
was emitted.
|
|
63
|
+
mermaid: Resolved mermaid asset (content + version + source
|
|
64
|
+
tier), or ``None`` when the deck has no mermaid elements.
|
|
65
|
+
Inlined into the page when ``inline=True`` and present;
|
|
66
|
+
its version always lands in the help-screen meta.
|
|
62
67
|
|
|
63
68
|
Returns:
|
|
64
69
|
The rendered HTML page as a single string.
|
|
@@ -66,16 +71,16 @@ def assemble(
|
|
|
66
71
|
template = _env().get_template("index.html.j2")
|
|
67
72
|
nav_data = build_nav_data(deck, chunks)
|
|
68
73
|
scoped_css_blocks = _collect_scoped_css(deck, chunks)
|
|
69
|
-
has_mermaid =
|
|
74
|
+
has_mermaid = mermaid is not None
|
|
70
75
|
minimap: MinimapGeometry | None = None if simplified_zoom_control else compute_minimap_geometry(deck)
|
|
71
|
-
meta = _build_meta(deck, chunks, bundle_stats=bundle_stats)
|
|
76
|
+
meta = _build_meta(deck, chunks, bundle_stats=bundle_stats, mermaid=mermaid)
|
|
72
77
|
|
|
73
78
|
inline_vars = {}
|
|
74
79
|
if inline:
|
|
75
80
|
inline_vars["bundled_css"] = bundled_css()
|
|
76
81
|
inline_vars["bundled_js"] = bundled_js()
|
|
77
|
-
if
|
|
78
|
-
inline_vars["mermaid_js_content"] =
|
|
82
|
+
if mermaid is not None:
|
|
83
|
+
inline_vars["mermaid_js_content"] = mermaid.content.decode("utf-8")
|
|
79
84
|
|
|
80
85
|
html = template.render(
|
|
81
86
|
title=deck.title or "scrolly",
|
|
@@ -100,8 +105,21 @@ def _build_meta(
|
|
|
100
105
|
chunks: dict[str, SlideHTML],
|
|
101
106
|
*,
|
|
102
107
|
bundle_stats: BundleStats | None = None,
|
|
108
|
+
mermaid: MermaidAsset | None = None,
|
|
103
109
|
) -> dict[str, Any]:
|
|
104
|
-
"""Build the metadata dict injected into the HTML for the help screen.
|
|
110
|
+
"""Build the metadata dict injected into the HTML for the help screen.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
deck: The fully-resolved deck.
|
|
114
|
+
chunks: Rendered per-slide HTML chunks.
|
|
115
|
+
bundle_stats: Bundler snapshot for the payload-stats section.
|
|
116
|
+
mermaid: Resolved mermaid asset, whose version is surfaced in
|
|
117
|
+
the help-screen statistics. ``None`` for decks without
|
|
118
|
+
mermaid elements.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Help-screen metadata dict.
|
|
122
|
+
"""
|
|
105
123
|
return {
|
|
106
124
|
"version": __version__,
|
|
107
125
|
"author": "Bert Pluymers",
|
|
@@ -110,6 +128,7 @@ def _build_meta(
|
|
|
110
128
|
"slides": len(deck.slides),
|
|
111
129
|
"edges": len(deck.edges),
|
|
112
130
|
"payloads": _payload_stats(bundle_stats),
|
|
131
|
+
"mermaid_version": mermaid.version if mermaid is not None else None,
|
|
113
132
|
"file_size": "__FILE_SIZE_PLACEHOLDER__",
|
|
114
133
|
},
|
|
115
134
|
}
|
|
@@ -314,6 +314,48 @@ body {
|
|
|
314
314
|
overflow: hidden;
|
|
315
315
|
}
|
|
316
316
|
|
|
317
|
+
/* ---- Mixed-font typography inside slide content ------------------------
|
|
318
|
+
*
|
|
319
|
+
* Slides routinely mix the body sans-serif with inline `<code>`, `<kbd>`,
|
|
320
|
+
* `<samp>` and `<pre>` blocks. By default the browser pairs `system-ui`
|
|
321
|
+
* (SF Pro / Segoe UI / Roboto) with the user's preferred generic
|
|
322
|
+
* `monospace` (Menlo / Consolas / Courier) — fonts not designed to share
|
|
323
|
+
* metrics — which makes inline `<code>` glyphs sit visibly higher or
|
|
324
|
+
* lower than adjacent sans-serif text whenever a layout depends on
|
|
325
|
+
* vertical alignment between lines.
|
|
326
|
+
*
|
|
327
|
+
* Two corrections, narrow scope (slide content only — UI chrome keeps
|
|
328
|
+
* its explicit per-component fonts):
|
|
329
|
+
*
|
|
330
|
+
* 1. Pin monospace to `ui-monospace` and OS-native pairs. `ui-monospace`
|
|
331
|
+
* picks the platform partner of `system-ui` (SF Mono on macOS,
|
|
332
|
+
* Cascadia Mono on Windows), drawn by the same designer to share
|
|
333
|
+
* metrics with the sans-serif.
|
|
334
|
+
*
|
|
335
|
+
* 2. Set `font-size-adjust: from-font` on the slide-element wrapper.
|
|
336
|
+
* The browser computes the body sans-serif's x-height ratio and
|
|
337
|
+
* inherits it down; when a `<code>` descendant falls back to its
|
|
338
|
+
* monospace family, the monospace font is rescaled so its x-height
|
|
339
|
+
* matches the sans-serif's. The visible "weight" of glyphs aligns
|
|
340
|
+
* across fonts even though their natural metrics differ.
|
|
341
|
+
*
|
|
342
|
+
* Together these reduce the residual mismatch to where it's no longer
|
|
343
|
+
* visible in normal slide layouts. The structural fix — a layout
|
|
344
|
+
* primitive that owns vertical pitch independently of font metrics —
|
|
345
|
+
* would supersede this CSS defaulting.
|
|
346
|
+
*/
|
|
347
|
+
.slide-type-slide-json .slide-element {
|
|
348
|
+
font-size-adjust: from-font;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.slide-type-slide-json .slide-element code,
|
|
352
|
+
.slide-type-slide-json .slide-element kbd,
|
|
353
|
+
.slide-type-slide-json .slide-element samp,
|
|
354
|
+
.slide-type-slide-json .slide-element pre {
|
|
355
|
+
font-family: ui-monospace, SFMono-Regular, "SF Mono",
|
|
356
|
+
Menlo, Consolas, monospace;
|
|
357
|
+
}
|
|
358
|
+
|
|
317
359
|
/* Animation-driven slides (numeric scroll_range) counter-translate the
|
|
318
360
|
* chunk's scroll-driven translateY so content stays visually stationary
|
|
319
361
|
* while --scroll-position drives the calc()-based keyframe expressions
|