narraleaf-react 0.16.0 → 0.17.0

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/README.md CHANGED
@@ -1,165 +1,165 @@
1
- <picture>
2
- <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/NarraLeaf/.github/refs/heads/master/doc/banner-md-transparent.png">
3
- <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/NarraLeaf/.github/refs/heads/master/doc/banner-md-light.png">
4
- <img alt="NarraLeaf Logo" src="https://raw.githubusercontent.com/NarraLeaf/.github/refs/heads/master/doc/banner-md-light.png">
5
- </picture>
6
-
7
- <h1 align="center">NarraLeaf-React</h1>
8
-
9
- <h4 align="center">A React visual novel player framework</h4>
10
-
11
- <p align="center">English | <a href="docs/README.zh-CN.md">简体中文</a></p>
12
-
13
-
14
- ## What is NarraLeaf-React?
15
-
16
- NarraLeaf-React is a lightweight front-end visual novel player.
17
- NL focuses on visual novel playing, so the user interface can be customized very easily.
18
-
19
- It doesn't use any rendering libraries and can be used on any web platform (e.g. Electron)
20
-
21
- ## Why NarraLeaf-React?
22
-
23
- - **Lightweight**: NarraLeaf-React is a front-end framework, and it doesn't use any rendering libraries.
24
- - **Customizable**: You can customize the UI as you like, even replace the whole components.
25
- - **Easy to use**: It is easy to use and has a simple API that is built for developers. Based on OOP principles.
26
-
27
- ### Scripting
28
-
29
- NarraLeaf-React uses TypeScript for all scripting, so you don't have to learn a whole new language to use it.
30
-
31
- It also has a highly abstracted and easy-to-use API, for example:
32
-
33
- ```typescript
34
- import {Character, Menu, Scene, c, b} from "narraleaf-react";
35
- ```
36
-
37
- ```typescript
38
- const scene1 = new Scene("Scene1: Hello World", {
39
- background: "/link/to/background.jpg",
40
- });
41
-
42
- const jS = new Character("John Smith");
43
- const jD = new Character("John Doe");
44
-
45
- scene1.action([
46
- jS`Hello, world!`,
47
- jS`This is my first ${b("NarraLeaf")} story.`,
48
- jS`Start editing ${c("src/story.js", "#00f")} and enjoy the journey!`,
49
-
50
- jD`Also, don't forget to check out the ${c("documentation", "#00f")}!`,
51
-
52
- "By the way, the documentation is available on https://www.narraleaf.com/docs/narraleaf-react",
53
- "You can also visit the website for demo and more information.",
54
-
55
- Menu.prompt("Start the journey")
56
-
57
- .choose("Yes I will!", [
58
- jS`Great! Let's start the journey!`,
59
- jS`You can open issues on GitHub if you have any questions.`
60
- ])
61
-
62
- .choose("No, I'm going to check the documentation", [
63
- jS`Sure! Take your time!`
64
- ])
65
- ]);
66
- ```
67
-
68
- For more information, please visit the [NarraLeaf-React documentation](https://www.narraleaf.com/docs/narraleaf-react).
69
-
70
- ## Get Started
71
-
72
- ### Install
73
-
74
- ```bash
75
- npm install narraleaf-react
76
- ```
77
-
78
- ### Documentation
79
-
80
- - [Introduction](https://www.narraleaf.com/docs/narraleaf-react)
81
- - [Quick Start](https://www.narraleaf.com/docs/narraleaf-react/quick-start)
82
- - [Installation](https://www.narraleaf.com/docs/narraleaf-react/installation)
83
- - [Basic](https://www.narraleaf.com/docs/narraleaf-react/basic)
84
- - [Create a Scene](https://www.narraleaf.com/docs/narraleaf-react/basic/create-scene)
85
- - [Add Actions](https://www.narraleaf.com/docs/narraleaf-react/basic/add-actions)
86
- - [Show Dialog](https://www.narraleaf.com/docs/narraleaf-react/basic/show-dialog)
87
- - [Show Image](https://www.narraleaf.com/docs/narraleaf-react/basic/show-image)
88
- - [Play Story](https://www.narraleaf.com/docs/narraleaf-react/basic/play-story)
89
- - [Make Choices](https://www.narraleaf.com/docs/narraleaf-react/basic/make-choices)
90
- - [Play Sound](https://www.narraleaf.com/docs/narraleaf-react/basic/sound)
91
- - [Store Data](https://www.narraleaf.com/docs/narraleaf-react/basic/store-data)
92
- - [Conditional](https://www.narraleaf.com/docs/narraleaf-react/basic/conditional)
93
- - [Voice](https://www.narraleaf.com/docs/narraleaf-react/basic/voice)
94
- - [Manage Preferences](https://www.narraleaf.com/docs/narraleaf-react/basic/manage-preferences)
95
- - [Solutions](https://www.narraleaf.com/docs/narraleaf-react/solutions)
96
- - [Customizing the Font](https://www.narraleaf.com/docs/narraleaf-react/solutions/font)
97
- - [Migration from Ren'Py](https://www.narraleaf.com/docs/narraleaf-react/solutions/from-renpy)
98
- - [Quick Menu](https://www.narraleaf.com/docs/narraleaf-react/solutions/quick-menu)
99
- - [Dialog Avatar](https://www.narraleaf.com/docs/narraleaf-react/solutions/dialog-avatar)
100
- - [Custom Dialog](https://www.narraleaf.com/docs/narraleaf-react/solutions/custom-dialog)
101
- - [Custom NVL Dialog](https://www.narraleaf.com/docs/narraleaf-react/solutions/custom-nvl-dialog)
102
- - [Custom Menu](https://www.narraleaf.com/docs/narraleaf-react/solutions/custom-menu)
103
- - [Page Overlay Settings](https://www.narraleaf.com/docs/narraleaf-react/solutions/page-overlay-settings)
104
- - [Save System with localStorage](https://www.narraleaf.com/docs/narraleaf-react/solutions/save-system-localstorage)
105
- - [Custom Notification](https://www.narraleaf.com/docs/narraleaf-react/solutions/custom-notification)
106
- - [Gallery Service with localStorage](https://www.narraleaf.com/docs/narraleaf-react/solutions/gallery-service-localstorage)
107
- - [Core](https://www.narraleaf.com/docs/narraleaf-react/core)
108
- - [Elements](https://www.narraleaf.com/docs/narraleaf-react/core/elements)
109
- - [Scene](https://www.narraleaf.com/docs/narraleaf-react/core/elements/scene)
110
- - [Character](https://www.narraleaf.com/docs/narraleaf-react/core/elements/character)
111
- - [Sentence](https://www.narraleaf.com/docs/narraleaf-react/core/elements/character/sentence)
112
- - [Word](https://www.narraleaf.com/docs/narraleaf-react/core/elements/character/word)
113
- - [Pause](https://www.narraleaf.com/docs/narraleaf-react/core/elements/character/pause)
114
- - [Image](https://www.narraleaf.com/docs/narraleaf-react/core/elements/image)
115
- - [Sound](https://www.narraleaf.com/docs/narraleaf-react/core/elements/sound)
116
- - [Menu](https://www.narraleaf.com/docs/narraleaf-react/core/elements/menu)
117
- - [Script](https://www.narraleaf.com/docs/narraleaf-react/core/elements/script)
118
- - [Condition](https://www.narraleaf.com/docs/narraleaf-react/core/elements/condition)
119
- - [Control](https://www.narraleaf.com/docs/narraleaf-react/core/elements/control)
120
- - [Text](https://www.narraleaf.com/docs/narraleaf-react/core/elements/text)
121
- - [Persistent](https://www.narraleaf.com/docs/narraleaf-react/core/elements/persistent)
122
- - [Story](https://www.narraleaf.com/docs/narraleaf-react/core/elements/story)
123
- - [Displayable](https://www.narraleaf.com/docs/narraleaf-react/core/elements/displayable)
124
- - [Layer](https://www.narraleaf.com/docs/narraleaf-react/core/elements/layer)
125
- - [Service](https://www.narraleaf.com/docs/narraleaf-react/core/elements/service)
126
- - [Video](https://www.narraleaf.com/docs/narraleaf-react/core/elements/video)
127
- - [Built-in Gallery](https://www.narraleaf.com/docs/narraleaf-react/core/elements/built-in/gallery)
128
- - [Animation](https://www.narraleaf.com/docs/narraleaf-react/core/animation)
129
- - [Transform](https://www.narraleaf.com/docs/narraleaf-react/core/animation/transform)
130
- - [Transitions](https://www.narraleaf.com/docs/narraleaf-react/core/animation/transition)
131
- - [Game](https://www.narraleaf.com/docs/narraleaf-react/core/game)
132
- - [LiveGame](https://www.narraleaf.com/docs/narraleaf-react/core/game/live-game)
133
- - [Storable](https://www.narraleaf.com/docs/narraleaf-react/core/game/storable)
134
- - [Preference](https://www.narraleaf.com/docs/narraleaf-react/core/game/preference/preference)
135
- - [Key Map](https://www.narraleaf.com/docs/narraleaf-react/core/game/key-map)
136
- - [Hooks](https://www.narraleaf.com/docs/narraleaf-react/core/game/hooks)
137
- - [Plugin](https://www.narraleaf.com/docs/narraleaf-react/core/plugin)
138
- - [Utils](https://www.narraleaf.com/docs/narraleaf-react/core/utils)
139
- - [Player](https://www.narraleaf.com/docs/narraleaf-react/player)
140
- - [Player](https://www.narraleaf.com/docs/narraleaf-react/player/player)
141
- - [FixedAspectRatioContainer](https://www.narraleaf.com/docs/narraleaf-react/player/fixed-aspect-ratio-container)
142
- - [GameProviders](https://www.narraleaf.com/docs/narraleaf-react/player/game-providers)
143
- - [Hooks](https://www.narraleaf.com/docs/narraleaf-react/player/hooks)
144
- - [LayoutRouter](https://www.narraleaf.com/docs/narraleaf-react/player/page-router)
145
- - [Dialog](https://www.narraleaf.com/docs/narraleaf-react/player/dialog)
146
- - [Notification](https://www.narraleaf.com/docs/narraleaf-react/player/notification)
147
- - [Menu](https://www.narraleaf.com/docs/narraleaf-react/player/menu)
148
- - [NvlContainer](https://www.narraleaf.com/docs/narraleaf-react/player/nvl-container)
149
- - About
150
- - [License](https://www.narraleaf.com/docs/narraleaf-react/info/license)
151
- - [Incompatible Changes](https://www.narraleaf.com/docs/narraleaf-react/info/incompatible-changes)
152
-
153
- Read more in the [NarraLeaf-React documentation](https://www.narraleaf.com/docs/narraleaf-react).
154
-
155
- ## License
156
-
157
- > NarraLeaf-React is licensed under the MPL-2.0 License.
158
- >
159
- > We updated the license to MPL-2.0 on 2024-9-24.
160
-
161
- ## Contributing
162
-
163
- We welcome all contributions.
164
- If you have any ideas, just open an issue or a pull request.
165
-
1
+ <picture>
2
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/NarraLeaf/.github/refs/heads/master/doc/banner-md-transparent.png">
3
+ <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/NarraLeaf/.github/refs/heads/master/doc/banner-md-light.png">
4
+ <img alt="NarraLeaf Logo" src="https://raw.githubusercontent.com/NarraLeaf/.github/refs/heads/master/doc/banner-md-light.png">
5
+ </picture>
6
+
7
+ <h1 align="center">NarraLeaf-React</h1>
8
+
9
+ <h4 align="center">A React visual novel player framework</h4>
10
+
11
+ <p align="center">English | <a href="docs/README.zh-CN.md">简体中文</a></p>
12
+
13
+
14
+ ## What is NarraLeaf-React?
15
+
16
+ NarraLeaf-React is a lightweight front-end visual novel player.
17
+ NL focuses on visual novel playing, so the user interface can be customized very easily.
18
+
19
+ It doesn't use any rendering libraries and can be used on any web platform (e.g. Electron)
20
+
21
+ ## Why NarraLeaf-React?
22
+
23
+ - **Lightweight**: NarraLeaf-React is a front-end framework, and it doesn't use any rendering libraries.
24
+ - **Customizable**: You can customize the UI as you like, even replace the whole components.
25
+ - **Easy to use**: It is easy to use and has a simple API that is built for developers. Based on OOP principles.
26
+
27
+ ### Scripting
28
+
29
+ NarraLeaf-React uses TypeScript for all scripting, so you don't have to learn a whole new language to use it.
30
+
31
+ It also has a highly abstracted and easy-to-use API, for example:
32
+
33
+ ```typescript
34
+ import {Character, Menu, Scene, c, b} from "narraleaf-react";
35
+ ```
36
+
37
+ ```typescript
38
+ const scene1 = new Scene("Scene1: Hello World", {
39
+ background: "/link/to/background.jpg",
40
+ });
41
+
42
+ const jS = new Character("John Smith");
43
+ const jD = new Character("John Doe");
44
+
45
+ scene1.action([
46
+ jS`Hello, world!`,
47
+ jS`This is my first ${b("NarraLeaf")} story.`,
48
+ jS`Start editing ${c("src/story.js", "#00f")} and enjoy the journey!`,
49
+
50
+ jD`Also, don't forget to check out the ${c("documentation", "#00f")}!`,
51
+
52
+ "By the way, the documentation is available on https://www.narraleaf.com/docs/narraleaf-react",
53
+ "You can also visit the website for demo and more information.",
54
+
55
+ Menu.prompt("Start the journey")
56
+
57
+ .choose("Yes I will!", [
58
+ jS`Great! Let's start the journey!`,
59
+ jS`You can open issues on GitHub if you have any questions.`
60
+ ])
61
+
62
+ .choose("No, I'm going to check the documentation", [
63
+ jS`Sure! Take your time!`
64
+ ])
65
+ ]);
66
+ ```
67
+
68
+ For more information, please visit the [NarraLeaf-React documentation](https://www.narraleaf.com/docs/narraleaf-react).
69
+
70
+ ## Get Started
71
+
72
+ ### Install
73
+
74
+ ```bash
75
+ npm install narraleaf-react
76
+ ```
77
+
78
+ ### Documentation
79
+
80
+ - [Introduction](https://www.narraleaf.com/docs/narraleaf-react)
81
+ - [Quick Start](https://www.narraleaf.com/docs/narraleaf-react/quick-start)
82
+ - [Installation](https://www.narraleaf.com/docs/narraleaf-react/installation)
83
+ - [Basic](https://www.narraleaf.com/docs/narraleaf-react/basic)
84
+ - [Create a Scene](https://www.narraleaf.com/docs/narraleaf-react/basic/create-scene)
85
+ - [Add Actions](https://www.narraleaf.com/docs/narraleaf-react/basic/add-actions)
86
+ - [Show Dialog](https://www.narraleaf.com/docs/narraleaf-react/basic/show-dialog)
87
+ - [Show Image](https://www.narraleaf.com/docs/narraleaf-react/basic/show-image)
88
+ - [Play Story](https://www.narraleaf.com/docs/narraleaf-react/basic/play-story)
89
+ - [Make Choices](https://www.narraleaf.com/docs/narraleaf-react/basic/make-choices)
90
+ - [Play Sound](https://www.narraleaf.com/docs/narraleaf-react/basic/sound)
91
+ - [Store Data](https://www.narraleaf.com/docs/narraleaf-react/basic/store-data)
92
+ - [Conditional](https://www.narraleaf.com/docs/narraleaf-react/basic/conditional)
93
+ - [Voice](https://www.narraleaf.com/docs/narraleaf-react/basic/voice)
94
+ - [Manage Preferences](https://www.narraleaf.com/docs/narraleaf-react/basic/manage-preferences)
95
+ - [Solutions](https://www.narraleaf.com/docs/narraleaf-react/solutions)
96
+ - [Customizing the Font](https://www.narraleaf.com/docs/narraleaf-react/solutions/font)
97
+ - [Migration from Ren'Py](https://www.narraleaf.com/docs/narraleaf-react/solutions/from-renpy)
98
+ - [Quick Menu](https://www.narraleaf.com/docs/narraleaf-react/solutions/quick-menu)
99
+ - [Dialog Avatar](https://www.narraleaf.com/docs/narraleaf-react/solutions/dialog-avatar)
100
+ - [Custom Dialog](https://www.narraleaf.com/docs/narraleaf-react/solutions/custom-dialog)
101
+ - [Custom NVL Dialog](https://www.narraleaf.com/docs/narraleaf-react/solutions/custom-nvl-dialog)
102
+ - [Custom Menu](https://www.narraleaf.com/docs/narraleaf-react/solutions/custom-menu)
103
+ - [Page Overlay Settings](https://www.narraleaf.com/docs/narraleaf-react/solutions/page-overlay-settings)
104
+ - [Save System with localStorage](https://www.narraleaf.com/docs/narraleaf-react/solutions/save-system-localstorage)
105
+ - [Custom Notification](https://www.narraleaf.com/docs/narraleaf-react/solutions/custom-notification)
106
+ - [Gallery Service with localStorage](https://www.narraleaf.com/docs/narraleaf-react/solutions/gallery-service-localstorage)
107
+ - [Core](https://www.narraleaf.com/docs/narraleaf-react/core)
108
+ - [Elements](https://www.narraleaf.com/docs/narraleaf-react/core/elements)
109
+ - [Scene](https://www.narraleaf.com/docs/narraleaf-react/core/elements/scene)
110
+ - [Character](https://www.narraleaf.com/docs/narraleaf-react/core/elements/character)
111
+ - [Sentence](https://www.narraleaf.com/docs/narraleaf-react/core/elements/character/sentence)
112
+ - [Word](https://www.narraleaf.com/docs/narraleaf-react/core/elements/character/word)
113
+ - [Pause](https://www.narraleaf.com/docs/narraleaf-react/core/elements/character/pause)
114
+ - [Image](https://www.narraleaf.com/docs/narraleaf-react/core/elements/image)
115
+ - [Sound](https://www.narraleaf.com/docs/narraleaf-react/core/elements/sound)
116
+ - [Menu](https://www.narraleaf.com/docs/narraleaf-react/core/elements/menu)
117
+ - [Script](https://www.narraleaf.com/docs/narraleaf-react/core/elements/script)
118
+ - [Condition](https://www.narraleaf.com/docs/narraleaf-react/core/elements/condition)
119
+ - [Control](https://www.narraleaf.com/docs/narraleaf-react/core/elements/control)
120
+ - [Text](https://www.narraleaf.com/docs/narraleaf-react/core/elements/text)
121
+ - [Persistent](https://www.narraleaf.com/docs/narraleaf-react/core/elements/persistent)
122
+ - [Story](https://www.narraleaf.com/docs/narraleaf-react/core/elements/story)
123
+ - [Displayable](https://www.narraleaf.com/docs/narraleaf-react/core/elements/displayable)
124
+ - [Layer](https://www.narraleaf.com/docs/narraleaf-react/core/elements/layer)
125
+ - [Service](https://www.narraleaf.com/docs/narraleaf-react/core/elements/service)
126
+ - [Video](https://www.narraleaf.com/docs/narraleaf-react/core/elements/video)
127
+ - [Built-in Gallery](https://www.narraleaf.com/docs/narraleaf-react/core/elements/built-in/gallery)
128
+ - [Animation](https://www.narraleaf.com/docs/narraleaf-react/core/animation)
129
+ - [Transform](https://www.narraleaf.com/docs/narraleaf-react/core/animation/transform)
130
+ - [Transitions](https://www.narraleaf.com/docs/narraleaf-react/core/animation/transition)
131
+ - [Game](https://www.narraleaf.com/docs/narraleaf-react/core/game)
132
+ - [LiveGame](https://www.narraleaf.com/docs/narraleaf-react/core/game/live-game)
133
+ - [Storable](https://www.narraleaf.com/docs/narraleaf-react/core/game/storable)
134
+ - [Preference](https://www.narraleaf.com/docs/narraleaf-react/core/game/preference/preference)
135
+ - [Key Map](https://www.narraleaf.com/docs/narraleaf-react/core/game/key-map)
136
+ - [Hooks](https://www.narraleaf.com/docs/narraleaf-react/core/game/hooks)
137
+ - [Plugin](https://www.narraleaf.com/docs/narraleaf-react/core/plugin)
138
+ - [Utils](https://www.narraleaf.com/docs/narraleaf-react/core/utils)
139
+ - [Player](https://www.narraleaf.com/docs/narraleaf-react/player)
140
+ - [Player](https://www.narraleaf.com/docs/narraleaf-react/player/player)
141
+ - [FixedAspectRatioContainer](https://www.narraleaf.com/docs/narraleaf-react/player/fixed-aspect-ratio-container)
142
+ - [GameProviders](https://www.narraleaf.com/docs/narraleaf-react/player/game-providers)
143
+ - [Hooks](https://www.narraleaf.com/docs/narraleaf-react/player/hooks)
144
+ - [LayoutRouter](https://www.narraleaf.com/docs/narraleaf-react/player/page-router)
145
+ - [Dialog](https://www.narraleaf.com/docs/narraleaf-react/player/dialog)
146
+ - [Notification](https://www.narraleaf.com/docs/narraleaf-react/player/notification)
147
+ - [Menu](https://www.narraleaf.com/docs/narraleaf-react/player/menu)
148
+ - [NvlContainer](https://www.narraleaf.com/docs/narraleaf-react/player/nvl-container)
149
+ - About
150
+ - [License](https://www.narraleaf.com/docs/narraleaf-react/info/license)
151
+ - [Incompatible Changes](https://www.narraleaf.com/docs/narraleaf-react/info/incompatible-changes)
152
+
153
+ Read more in the [NarraLeaf-React documentation](https://www.narraleaf.com/docs/narraleaf-react).
154
+
155
+ ## License
156
+
157
+ > NarraLeaf-React is licensed under the MPL-2.0 License.
158
+ >
159
+ > We updated the license to MPL-2.0 on 2024-9-24.
160
+
161
+ ## Contributing
162
+
163
+ We welcome all contributions.
164
+ If you have any ideas, just open an issue or a pull request.
165
+
@@ -5,14 +5,16 @@ import { ImageTransition } from "../elements/transition/transitions/image/imageT
5
5
  import { TextTransition } from "../elements/transition/transitions/text/textTransition";
6
6
  import { BlurDissolve } from "../elements/transition/transitions/image/blurDissolve";
7
7
  import { Push } from "../elements/transition/transitions/image/push";
8
+ import { Darkness } from "../elements/transition/transitions/image/darkness";
8
9
  import { ThroughColor } from "../elements/transition/transitions/image/throughColor";
9
10
  import { Reveal } from "../elements/transition/transitions/image/reveal";
10
11
  import { Mask } from "../elements/transition/transitions/image/mask";
11
- export { Transition, ImageTransition, TextTransition, Dissolve, FadeIn, BlurDissolve, Push, ThroughColor, Reveal, Mask, };
12
+ export { Transition, ImageTransition, TextTransition, Dissolve, FadeIn, BlurDissolve, Push, Darkness, ThroughColor, Reveal, Mask, };
12
13
  export type { DissolveOptions } from "../elements/transition/transitions/image/dissolve";
13
14
  export type { FadeInOptions } from "../elements/transition/transitions/image/fadeIn";
14
15
  export type { BlurDissolveOptions } from "../elements/transition/transitions/image/blurDissolve";
15
16
  export type { PushOptions } from "../elements/transition/transitions/image/push";
17
+ export type { DarknessOptions } from "../elements/transition/transitions/image/darkness";
16
18
  export type { ThroughColorOptions, ThroughColorUncover, } from "../elements/transition/transitions/image/throughColor";
17
19
  export type { RevealOptions } from "../elements/transition/transitions/image/reveal";
18
20
  export type { MaskPattern, WipePatternOptions, BarnDoorPatternOptions, IrisPatternOptions, ClockPatternOptions, FanPatternOptions, BlindsPatternOptions, DotsPatternOptions, } from "../elements/transition/transitions/image/mask";
@@ -34,6 +34,14 @@ export type TextEventConfig = {
34
34
  *
35
35
  * First version carries a closed set: a character-expression switch and/or a sound effect.
36
36
  *
37
+ * **Skip / instant reveal semantics.** Skipping the typewriter — or an instant, non-type-effect
38
+ * reveal that uncovers the whole sentence at once — does not drop the tokens it flies past. Every
39
+ * crossed token fires exactly once, in source order, so the outcome is identical to letting the
40
+ * typewriter reach each one in turn: the image ends in the appearance the *last* crossed token
41
+ * specifies, and each crossed sound effect plays once. Re-visiting an already-fired token within the
42
+ * same reveal is a no-op — it never double-plays a sound effect nor re-writes the expression — which
43
+ * is what keeps a re-mount of an already-revealed line from replaying its effects.
44
+ *
37
45
  * @example
38
46
  * ```ts
39
47
  * // switch Alice's portrait to "angry" the moment the typewriter reaches this point
@@ -12,8 +12,12 @@ export type DarknessOptions = {
12
12
  easing?: TransformDefinitions.EasingDefinition;
13
13
  };
14
14
  /**
15
- * Darken the original image and fade in the target image at the same time.
16
- * Internal: drives `image.darken(x, duration)`.
15
+ * A brightness-dim transition: swaps to the incoming image and animates its
16
+ * brightness from `1 - from` to `1 - to` (darkness `0` leaves it untouched, `1`
17
+ * drives it fully black), replacing the outgoing image at once.
18
+ *
19
+ * This is what backs `image.darken(amount, duration)` — darkening an image in
20
+ * place is expressed as a transition from its current darkness to the new one.
17
21
  */
18
22
  export declare class Darkness extends ImageTransition<AnimationType> {
19
23
  private from;
@@ -14,16 +14,25 @@ export type PushOptions = {
14
14
  * image slides out the opposite way, as if the camera panned.
15
15
  *
16
16
  * The offset is applied via the independent CSS `translate` property (not
17
- * `transform`) in viewport units. That composes additively with the layer's
18
- * base positioning instead of overriding it, and is the identity at offset `0`,
19
- * so neither image jumps at the start/end of the slide.
17
+ * `transform`) in **percentages of the layer's own size**. That composes
18
+ * additively with the layer's base positioning instead of overriding it, and is
19
+ * the identity at offset `0`, so neither image jumps at the start/end of the slide.
20
+ *
21
+ * Percentages — not viewport units — matter here: whichever element this drives
22
+ * lives inside the letterboxed stage box. On the layered render path that element
23
+ * is the transition stack wrapper (`inset: 0`, see `Image.tsx` `stackStyle`); on the
24
+ * non-layered path the style is applied to the `<img>` itself. A `100vw`/`100vh`
25
+ * travel is measured against the *window*, so whenever the window aspect differs
26
+ * from the design aspect the slide overshoots the stage and exposes the backdrop
27
+ * mid-transition. `100%` is measured against that element, so either way a full
28
+ * slide lands exactly one stage width/height away regardless of window shape.
20
29
  */
21
30
  export declare class Push extends ImageTransition<AnimationType> {
22
31
  private duration;
23
32
  private direction;
24
33
  private easing?;
25
34
  constructor(options: PushOptions);
26
- private axisUnit;
35
+ private axisSign;
27
36
  private translate;
28
37
  createTask(): TransitionTask<HTMLImageElement, AnimationType>;
29
38
  copy(): Push;
@@ -0,0 +1,34 @@
1
+ import type { Scene } from "../../../nlcore/elements/scene";
2
+ import type { Sound } from "../../../nlcore/elements/sound";
3
+ export type ScenePreloadPlan = {
4
+ /**
5
+ * Image urls the scene that is about to paint registers directly: its own backgrounds and
6
+ * images, plus the immediate background of any scene it jumps to. This tier is on the path to
7
+ * the first painted frame, so it is fetched unpaced and nothing is revealed until it is warm.
8
+ */
9
+ critical: string[];
10
+ /**
11
+ * Image urls belonging to the scenes reachable from here, minus anything already in
12
+ * {@link ScenePreloadPlan.critical}. Speculative: warmed after the critical tier, paced, and
13
+ * nothing waits for it.
14
+ */
15
+ lookAhead: string[];
16
+ /** Every url in the plan, in order — the set the cache should keep for this scene. */
17
+ all: string[];
18
+ /**
19
+ * Sounds this scene registers. Warmed alongside the critical tier but never gated on: the
20
+ * audio context can be locked until the player interacts with the page (see
21
+ * `AudioManager.preload`), so waiting for these could wait forever.
22
+ */
23
+ criticalAudio: Sound[];
24
+ };
25
+ /**
26
+ * Split a scene's registered image sources into what must be warm before the scene paints and what
27
+ * is merely likely to be needed soon.
28
+ *
29
+ * The distinction matters because a scene's `srcManager` reaches transitively: `getFutureSrc()`
30
+ * carries the whole asset set of every scene this one can jump to. Treating that as one preload
31
+ * pass meant the first frame of a large story waited on assets from scenes the player had not
32
+ * reached yet.
33
+ */
34
+ export declare function planScenePreload(scene: Scene): ScenePreloadPlan;
@@ -18,6 +18,13 @@ type DialogStateConfig = {
18
18
  evaluatedWords: Word<Pausing | string | TextEvent>[];
19
19
  gameState: GameState;
20
20
  suppressInitialAnimation?: boolean;
21
+ /**
22
+ * Persistent per-reveal text-event fire guard. When supplied (NVL entries pass the set stored on
23
+ * their long-lived {@link NvlDialogEntry}), a re-mount of the same line reuses it and so replays
24
+ * neither the sound effects nor the stale expression. Left undefined for ADV, whose dialog state
25
+ * is already memoized per action and falls back to a per-run set.
26
+ */
27
+ firedTextEvents?: Set<TextEvent>;
21
28
  };
22
29
  export declare class DialogState {
23
30
  static Events: {
@@ -1,5 +1,7 @@
1
1
  import type { GameState } from "../../gameState";
2
2
  import type { TextEvent } from "../../../nlcore/elements/character/textEvent";
3
+ import type { Word } from "../../../nlcore/elements/character/word";
4
+ import type { Pausing } from "../../../nlcore/elements/character/pause";
3
5
  /**
4
6
  * Apply a {@link TextEvent}'s effect directly to element state — no transition, no action history,
5
7
  * no stack model. This is the "reveal semantics" seam: the typewriter calls it the moment it
@@ -17,3 +19,15 @@ export declare function dispatchTextEvent(event: TextEvent, state: GameState): v
17
19
  * a replay of the same `say` — starts with an empty set and fires again (contract 4).
18
20
  */
19
21
  export declare function fireTextEventOnce(event: TextEvent, fired: Set<TextEvent>, state: GameState): void;
22
+ /**
23
+ * Land the final state of an instantly-revealed sentence: every {@link TextEvent} token in `words`
24
+ * fires once, in source order — the same "final state" a typewriter skip produces (contract 3).
25
+ *
26
+ * `fired` is the persistent per-reveal guard. Pass the SAME set across re-mounts of one dialog line
27
+ * (e.g. an NVL entry re-keyed on a phase/active change, or the whole container re-mounting) so the
28
+ * re-mount replays neither the sound effects nor the — now stale — expression writes. A genuinely
29
+ * fresh reveal (a new line, or a `say` re-evaluated on load) passes its own empty set and fires
30
+ * again, preserving replay safety (contract 4). Returns the tokens that actually fired this call
31
+ * (empty on a guarded re-mount), for tests.
32
+ */
33
+ export declare function fireInstantRevealEvents(words: readonly Word<string | Pausing | TextEvent>[], fired: Set<TextEvent>, state: GameState): TextEvent[];
@@ -10,6 +10,7 @@ import { Storable } from "../nlcore/elements/persistent/storable";
10
10
  import { Game } from "../nlcore/game";
11
11
  import { Clickable, MenuElement, TextElement } from "./gameState.type";
12
12
  import { Sentence } from "../nlcore/elements/character/sentence";
13
+ import type { TextEvent } from "../nlcore/elements/character/textEvent";
13
14
  import { type SceneSnapshot } from "../nlcore/action/actions/sceneAction";
14
15
  import { Logger } from "../../util/logger";
15
16
  import { Story } from "../nlcore/elements/story";
@@ -36,6 +37,13 @@ export type NvlDialogEntry = {
36
37
  character: Character | null;
37
38
  sentence: Sentence;
38
39
  text: string;
40
+ /**
41
+ * Runtime-only, per-line text-event fire guard. Lives on the long-lived entry (not the React
42
+ * dialog state, which is re-created on every re-mount) so the tokens a line reveals fire once and
43
+ * a re-mount replays nothing. Absent from {@link NvlDialogEntryData}, so a load starts a fresh
44
+ * reveal that fires again (replay safety). See {@link fireInstantRevealEvents}.
45
+ */
46
+ firedTextEvents?: Set<TextEvent>;
39
47
  };
40
48
  export type NvlDialogPhase = "idle" | "typing" | "awaitAdvance";
41
49
  /**
@@ -43,6 +43,18 @@ export declare class AudioManager {
43
43
  fromData(data: AudioManagerDataRaw, elementMap: Map<string, LogicAction.GameElement>): this;
44
44
  soundFromData(sound: SoundElement, data: AudioDataRaw): void;
45
45
  isManaged(sound: SoundElement): boolean;
46
+ /**
47
+ * Fetch and decode a sound into the audio cache without playing it, so the first `play()` of
48
+ * this source starts on the same frame it is asked to instead of after a fetch and a decode.
49
+ *
50
+ * Deliberately **not** something to gate a loading screen on: the audio context only becomes
51
+ * ready once the browser's autoplay policy is satisfied by a user gesture, so this can sit
52
+ * pending indefinitely on a page nobody has interacted with yet. Start it and let it land —
53
+ * in practice the gesture that opens a menu unlocks the context long before the scene it
54
+ * belongs to is entered. Failures resolve quietly; the sound then loads on first play, exactly
55
+ * as it did before.
56
+ */
57
+ preload(sound: SoundElement): Promise<void>;
46
58
  reset(): void;
47
59
  setGroupVolume(type: SoundType, volume: number): void;
48
60
  setGlobalVolume(volume: number): void;
@@ -14,19 +14,46 @@ export declare class ImageCacheManager {
14
14
  * the bytes are cached — the first reveal still pays the (async) decode cost and can paint
15
15
  * a blank frame. Decode failures are ignored: the image then simply decodes lazily on
16
16
  * first paint, exactly as before.
17
+ *
18
+ * Returns the element the decode ran on so callers can keep it alive (see
19
+ * {@link ImageCacheManager.preload}'s `retainDecoded`); `null` when the environment has no
20
+ * `Image` or no `decode()`.
17
21
  */
18
22
  private static decodeImage;
19
23
  private src;
20
24
  private preloadTasks;
25
+ /**
26
+ * Decoded images held on purpose. A decoded bitmap only stays in the browser's cache while
27
+ * something still references it, so dropping the element right after `decode()` lets the
28
+ * bitmap be evicted and the reveal decodes all over again. Retention is opt-in per preload
29
+ * (`retainDecoded`) because a full-resolution bitmap costs width × height × 4 bytes — worth
30
+ * it for the scene that is about to paint, far too expensive for a whole reachable graph.
31
+ */
32
+ private decoded;
21
33
  constructor(game: Game);
22
34
  has(name: string): boolean;
23
35
  add(name: string, src: string): this;
24
36
  remove(name: string): this;
25
37
  get(name: string): string | undefined;
38
+ /**
39
+ * Whether this source has been decoded and its decoded bitmap is still held, i.e. attaching
40
+ * it to an `<img>` can paint without an asynchronous decode first.
41
+ */
42
+ isDecoded(name: string): boolean;
26
43
  clear(): this;
27
44
  size(): number;
28
45
  isPreloading(src: string): boolean;
29
- preload(gameState: GameState, url: string): PreloadedToken;
46
+ /**
47
+ * Fetch `url`, cache it as a data URL and decode it, resolving the returned token's
48
+ * `onFinished` only once the decode has settled.
49
+ *
50
+ * @param options.retainDecoded keep the decoded bitmap alive until this source leaves the
51
+ * cache. Use it for the assets that are about to be revealed; leave it off for speculative
52
+ * look-ahead preloading, whose bitmaps would otherwise pile up in memory.
53
+ */
54
+ preload(gameState: GameState, url: string, options?: {
55
+ retainDecoded?: boolean;
56
+ }): PreloadedToken;
30
57
  abortAll(): void;
31
58
  abort(src: string): void;
32
59
  preloadedSrc(): string[];