storyink 0.2.1 → 0.3.1

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,45 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.1
4
+
5
+ - **Fix: Play did nothing under reduced motion.** With Reduce Motion on (OS or `#motion=reduced`),
6
+ Play now walks the story step by step ("Play steps"): each step's settled state at once (reveals,
7
+ landed pulses, counters, the whole caption), held for its reading time (caption read time or
8
+ 1.5 s), then the next, ending on the final frame. No pulses, trails, glows or tweens. Pause, ←/→,
9
+ Shift+←/→, R (restart from step 1, no rewind) and the scrubber work on settled steps. The page
10
+ loads on the final frame with a static play button; `autoplay` is ignored.
11
+ - **`story.motion`: `"full"` (default) | `"reduced"` | `"system"`.** Full motion is the default and
12
+ **ignores the reader's OS reduced-motion setting**; `"system"` restores the 0.3.0 behaviour
13
+ (follow `prefers-reduced-motion`), `"reduced"` always steps. Auto stories:
14
+ `{ "steps": "auto", "motion": … }`, `render --motion …`, tool `storyink_render` `motion`.
15
+ Precedence: `#motion=` > the reader's toolbar choice > `story.motion` > OS (only for `"system"`).
16
+ - Viewer toolbar: **Motion: full / reduced** toggle (`aria-pressed`, shortcut `M`), saved in
17
+ `localStorage`; `#motion=` still wins. Switching mid-playback continues from the current step.
18
+ - Core: `steppedSchedule`, `steppedTime`, `steppedIndex`, `steppedStop`, `STEP_BEAT` and
19
+ `storyState(…, { stepped: true })`.
20
+ - Snapshot: `--motion reduced` (tool `motion`) captures stepped `at` frames; new gate `reduced=stepped`.
21
+ - Fix: live counter reels were placed at the node-local position (top-left of the diagram) and
22
+ shown before their node was revealed; they now sit on their node and follow its reveal.
23
+ `bun run verify:viewer` checks this in headless Chrome (full play, reduced → full, reload,
24
+ resize, theme toggle) along with stepped playback.
25
+
26
+ ## 0.3.0
27
+
28
+ - **Animated SVG (SMIL):** the story plays inside a plain `<img>`, so in GitHub READMEs and PR
29
+ comments (camo), docs and chat previews, where no script runs.
30
+ - `renderAnimatedSvg(spec, { theme, once, font, hold, reset, fps })` / `animatedSvg()` in
31
+ `storyink/core`; `writeAnimatedSvg()` and `pictureSnippet()` in `storyink/node`.
32
+ - CLI: `storyink render x.json --animated-svg x.svg [--theme light|dark|both] [--once] [--font system|embed]`;
33
+ `both` writes `.light.svg` + `.dark.svg` and prints the `<picture>` snippet. Specs without a
34
+ story use the auto story (with a warning).
35
+ - Plugin: `storyink_render` takes `animatedSvg: true | "both"`, `animatedSvgPath`, `once`, `font`.
36
+ - Built by sampling `storyState(t)` (the viewer's frame function) and compressing each track
37
+ to SMIL keyframes; one shared cycle (story, 3 s hold, 0.4 s reset) or `once` (freeze).
38
+ Base values are the final frame. Theme-pinned, no custom properties, deterministic.
39
+ - `bun run gallery:animated`, `bun run verify:smil` (frame parity PSNR, `<img>` playback, font).
40
+ - `Frame` pulse trail segments carry `k`, `s0`, `s1`; captions carry their index `i`.
41
+ - `screenshotPage()` in `storyink/node`: screenshot any local page.
42
+
3
43
  ## 0.2.1
4
44
 
5
45
  - **Fix:** `storyink_snapshot` no longer inlines the full-resolution contact sheet. Tall beat
package/README.md CHANGED
@@ -6,6 +6,19 @@ Mermaid) into **one offline HTML file** (a React + Motion viewer over server-ren
6
6
 
7
7
  One package, four ways to use it: library, CLI, OpenCode plugin, and a skill for other agents.
8
8
 
9
+ <picture>
10
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/grenaad/storyink/main/docs/gallery/checkout.architecture.animated.dark.svg">
11
+ <img alt="Checkout platform, animated story" src="https://raw.githubusercontent.com/grenaad/storyink/main/docs/gallery/checkout.architecture.animated.light.svg">
12
+ </picture>
13
+
14
+ <picture>
15
+ <source media="(prefers-color-scheme: dark)" srcset="https://cdn.jsdelivr.net/gh/grenaad/storyink@main/docs/gallery/oauth.sequence.animated.dark.svg">
16
+ <img alt="OAuth sequence, animated story" src="https://cdn.jsdelivr.net/gh/grenaad/storyink@main/docs/gallery/oauth.sequence.animated.light.svg">
17
+ </picture>
18
+
19
+ *Animated SVGs (SMIL, no script): they play inside a plain `<img>`, so in a README or a PR
20
+ comment too. See [Animated SVG](#animated-svg-readmes-and-prs).*
21
+
9
22
  ```sh
10
23
  npx storyink render examples/checkout.architecture.json -o checkout.html --svg checkout.svg
11
24
  ```
@@ -35,10 +48,11 @@ both and has the OpenCode plugin as its default export. Runs on Node ≥ 20 and
35
48
 
36
49
  ```
37
50
  storyink render <in.json|in.mmd|-> [-o out.html] [--svg out.svg] [--theme light|dark] [--story auto]
51
+ [--animated-svg out.svg [--theme light|dark|both] [--once] [--font system|embed]]
38
52
  storyink mermaid <in.mmd> [-o out.json]
39
53
  storyink validate <in> [--json]
40
54
  storyink snapshot <out.html> [--theme light,dark] [--width N] [--sheet [themes|beats]|--no-sheet] [--at 0.5,1.2,end] [--scale 2] [-o dir] [--json]
41
- [--preview out.jpg [--preview-size 1024]]
55
+ [--preview out.jpg [--preview-size 1024]] [--motion reduced]
42
56
  storyink skill
43
57
  ```
44
58
 
@@ -108,7 +122,15 @@ Add a `story` to play a diagram as a sequence of beats in the HTML viewer:
108
122
 
109
123
  `"story": "auto"` (or `--story auto`) derives the beats from the graph or message order. Playback
110
124
  has a click-to-play gate, play/pause, a tape-rewind replay and a scrubber with step and chapter
111
- ticks. Space, ←/→ and R control it. When reduced motion is on, the viewer jumps to the final frame.
125
+ ticks. Space, ←/→ and R control it.
126
+
127
+ Motion is **full by default, even when the reader's system asks for reduced motion**. Authors can
128
+ set `"story": { "motion": "reduced" }` (always step by step) or `"motion": "system"` (follow
129
+ `prefers-reduced-motion`); `--motion full|reduced|system` on `render` does the same, including
130
+ for auto stories. Readers switch with the toolbar's **Motion** toggle (`M`, remembered), and
131
+ `#motion=full|reduced` overrides everything. In reduced mode Play walks the story step by step:
132
+ each step's settled state shown at once and held for its reading time, with no travelling pulses
133
+ or tweens, and the page opens on the final frame.
112
134
 
113
135
  The final frame is always the static diagram. Every frame is a pure function of time
114
136
  (`storyState(scene, timeline, t)`), so `#t=2.5` seeks exactly and
@@ -118,10 +140,41 @@ receipt gates check that the end frame and the reduced-motion page match the sta
118
140
 
119
141
  ![Checkout beats](docs/gallery/checkout.architecture.beats.light.png)
120
142
 
143
+ ## Animated SVG (READMEs and PRs)
144
+
145
+ GitHub shows images in READMEs and PR comments as a plain `<img>` through its camo proxy, so no
146
+ script runs and nothing outside the file loads. `--animated-svg` compiles the story into **SMIL**
147
+ inside one self-contained SVG that plays there:
148
+
149
+ ```sh
150
+ storyink render examples/checkout.architecture.json --animated-svg docs/checkout.svg --theme both
151
+ # wrote docs/checkout.light.svg, docs/checkout.dark.svg and prints:
152
+ ```
153
+
154
+ ```html
155
+ <picture>
156
+ <source media="(prefers-color-scheme: dark)" srcset="docs/checkout.dark.svg">
157
+ <img alt="Checkout platform" src="docs/checkout.light.svg">
158
+ </picture>
159
+ ```
160
+
161
+ - An SVG image doesn't follow the page theme, so each file is **pinned** to one theme; the
162
+ `<picture>` picks one. `--theme light|dark` writes a single file.
163
+ - It loops: story, a 3 s hold on the final frame, a 0.4 s reset. `--once` plays once and freezes.
164
+ - The attributes' base values are the final frame, so viewers without SMIL show the static diagram.
165
+ - Fonts: `--font system` (default) uses the system mono stack; `--font embed` embeds Commit Mono
166
+ (+~127 KB) for the exact look. See the size table in [docs/spec.md](docs/spec.md#animated-svg).
167
+ - No story in the spec? The auto story is used (with a warning).
168
+ - Library: `renderAnimatedSvg(spec, { theme, once, font })` from `storyink/core`; plugin:
169
+ `storyink_render` with `animatedSvg: true | "both"`, `once`, `font`.
170
+ - GitHub's image proxy caches by URL: when a diagram changes, give it a new file name (or URL).
171
+
121
172
  ## Gallery
122
173
 
123
174
  `bun run gallery` renders every example and Mermaid sample and writes one light and one dark PNG per
124
- example to `docs/gallery/`.
175
+ example to `docs/gallery/`. `bun run gallery:animated` writes the animated SVGs of the story
176
+ examples (`*.animated.light.svg` / `*.animated.dark.svg`); `bun run verify:smil` checks them in
177
+ headless Chrome (frame parity with the viewer's frames, `<img>` playback, embedded font).
125
178
 
126
179
  | | |
127
180
  | --- | --- |
@@ -48,3 +48,10 @@ site are included. Serif headings use system fonts ("Iowan Old Style", Charter,
48
48
 
49
49
  storyink parses a subset of Mermaid syntax with its own hand-written parsers; no Mermaid
50
50
  or merman code is included.
51
+
52
+ ## PR Lens (patterns only)
53
+
54
+ The animated SVG export follows the constraints and SMIL patterns of PR Lens's GitHub-safe SVG
55
+ renderer (<https://github.com/coldteadotai/pr-lens>, MIT License, Copyright (c) 2026 Coldtea AI):
56
+ self-contained files, no script or custom properties, animation as SMIL so it plays inside an
57
+ `<img>` behind GitHub's image proxy. No PR Lens code is included.