koi-ascii 0.1.0 → 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/README.md +337 -8
- package/dist/index.d.ts +105 -1
- package/dist/index.js +1056 -79
- package/package.json +8 -2
package/README.md
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Three koi swimming in a pond, drawn with ASCII characters, as a background for
|
|
4
4
|
your page. WebGL2, a day/night cycle, ripples and wakes that follow the
|
|
5
|
-
pointer
|
|
5
|
+
pointer, palettes to match your site and an intensity to keep text on top
|
|
6
|
+
readable. Plain JavaScript, ESM only, TypeScript declarations included, no
|
|
6
7
|
runtime dependencies.
|
|
7
8
|
|
|
8
9
|
**Demo:** https://luckmorecat.github.io/koi-ascii/
|
|
@@ -48,6 +49,9 @@ const pond = createKoiPond(container, {
|
|
|
48
49
|
interactive: 'window', // 'window' | 'canvas' | 'none'
|
|
49
50
|
seed: 42, // same seed, same fish; omit for a random pond on every mount
|
|
50
51
|
fontFamily: '"JetBrains Mono", ui-monospace, Menlo, Consolas, monospace',
|
|
52
|
+
palette: 'pond', // a preset name or a palette object; see "Palettes"
|
|
53
|
+
intensity: 1, // 0 background only … 1 full pond; see "Intensity"
|
|
54
|
+
sky: { moon: true }, // night-sky features; see "Night sky"
|
|
51
55
|
background: '#06131a', // CSS colour before the first frame and on failure
|
|
52
56
|
onError: (error) => console.warn(error.code),
|
|
53
57
|
});
|
|
@@ -59,23 +63,37 @@ const pond = createKoiPond(container, {
|
|
|
59
63
|
| `interactive` | `'window'` | See "Interaction". |
|
|
60
64
|
| `seed` | random | Any number; the same seed gives the same fish. |
|
|
61
65
|
| `fontFamily` | `'"JetBrains Mono", ui-monospace, Menlo, Consolas, monospace'` | The glyph font. See "Fonts". |
|
|
62
|
-
| `
|
|
66
|
+
| `palette` | `'pond'` | Can change later with `setPalette`. See "Palettes". |
|
|
67
|
+
| `intensity` | `1` | Can change later with `setIntensity`. See "Intensity". |
|
|
68
|
+
| `sky` | stars, fireflies and `darkNight` on; moon and sun off | Can change later with `setSky`. See "Night sky". |
|
|
69
|
+
| `background` | the palette's background | Shown until the first frame and if the pond fails. |
|
|
63
70
|
| `onError` | logs to the console | See "Failure". |
|
|
64
71
|
|
|
65
|
-
|
|
66
|
-
pond and create a new one.
|
|
72
|
+
The time, palette, intensity and sky can change after creation. For anything else,
|
|
73
|
+
`destroy()` the pond and create a new one.
|
|
74
|
+
|
|
75
|
+
Without `background`, the container shows the palette's background (and
|
|
76
|
+
follows `setPalette`), so a light palette never flashes dark before the first
|
|
77
|
+
frame. An explicit `background` only sets that colour; the pond itself is
|
|
78
|
+
always drawn on the palette's background.
|
|
67
79
|
|
|
68
80
|
### Handle
|
|
69
81
|
|
|
70
82
|
| Method | What it does |
|
|
71
83
|
| --------------- | ------------------------------------------------------------------------------------------------ |
|
|
72
84
|
| `setTime(time)` | Switches the time of day live. `{ mode: 'loop' }` without a phase carries on from the current one. |
|
|
85
|
+
| `setPalette(palette)` | Recolours the pond from the next frame on; the fish carry on. |
|
|
86
|
+
| `setIntensity(intensity)` | Fades the pond towards its background from the next frame on. Animate it for a fade. |
|
|
87
|
+
| `setSky(sky)` | Turns night-sky features on or off from the next frame on, with the same values as the `sky` option; the fish carry on. |
|
|
73
88
|
| `pause()` | Stops simulating and drawing. The last frame stays on screen. |
|
|
74
89
|
| `resume()` | Carries on from where it paused, without jumping ahead. |
|
|
75
90
|
| `destroy()` | Stops the pond, removes its canvas, listeners and observers and frees its WebGL context. Safe to call twice, and safe before the pond has started. |
|
|
76
91
|
|
|
77
92
|
Exported types: `KoiPond`, `KoiPondOptions`, `KoiPondTime`,
|
|
78
|
-
`KoiPondInteractive`, `KoiPondError`, `KoiPondErrorCode
|
|
93
|
+
`KoiPondInteractive`, `KoiPondError`, `KoiPondErrorCode`, `KoiPondPalette`,
|
|
94
|
+
`KoiPondPaletteName`, `KoiPondRampPalette`, `KoiPondRolesPalette`,
|
|
95
|
+
`KoiPondFilterPalette`, `KoiPondColor`, `KoiPondSky`, `KoiPondSkyFeatures`.
|
|
96
|
+
Exported value besides `createKoiPond`: `koiPalettes`.
|
|
79
97
|
|
|
80
98
|
## Time of day
|
|
81
99
|
|
|
@@ -97,6 +115,184 @@ pond.setTime({ mode: 'loop', cycle: 120 }); // a 2-minute day from here
|
|
|
97
115
|
pond.setTime({ mode: 'clock' });
|
|
98
116
|
```
|
|
99
117
|
|
|
118
|
+
## Night sky
|
|
119
|
+
|
|
120
|
+
`sky` picks what the night (and the day's sky) adds to the pond. Stars,
|
|
121
|
+
fireflies and a darker night are on by default; the moon and the sun are
|
|
122
|
+
opt-in:
|
|
123
|
+
|
|
124
|
+
```js
|
|
125
|
+
createKoiPond(el, { sky: { moon: true } }); // the defaults plus the moon
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
| Feature | Default | What it does |
|
|
129
|
+
| ----------- | ------- | ------------ |
|
|
130
|
+
| `darkNight` | on | At night, darker water and background and about half the floor caustics, so the night reads as night rather than a tinted day. Fades in and out through the blue hours; by day nothing changes. Dark palettes only: a light background stays light at night. |
|
|
131
|
+
| `stars` | on | Reflected stars at night: a sparse field of small points (`.` `'` `` ` `` `+`, a rare `*`) that twinkle slowly. Fixed to the screen, so they don't drift with the water, but a passing ripple makes them jump. They arrive after dusk and leave before dawn, and never cover a fish. On a light palette they are dark ink, and the motes thin out at night so the stars read. |
|
|
132
|
+
| `fireflies` | on | Fireflies at dusk: 14 points (`.` `+` `*` by brightness) that drift slowly and blink in short pulses, each a single character with a soft glow, drawn over the fish. They come out after the golden hour and are gone by full night; never at dawn. The same `seed` gives the same fireflies. On a light palette there is no glow, so they are the glyph alone. |
|
|
133
|
+
| `moon` | off | The moon's reflection at night: a full disc with darker maria (`.` `:` `o`, a rare `O`) and a drawn rim, dimmer than the fish so it reads as a reflection. It rises up the left side after dusk, crosses the top through the night and is gone before dawn, always clear of the middle of the pond. A ripple through it tears it into `=` and `\|` shards that settle back. Fish swim over it. Opt-in; see "Placement" below. |
|
|
134
|
+
| `sun` | off | The sun's reflection by day: a patch of glitter (`.` `+` `*`) that flashes cell by cell, densest at its centre, gold at noon and amber with a low sun, never brighter than the Ogon's glints. It follows the moon's path: up the left side after dawn, over the top at noon, gone near the top-right corner before dusk, always clear of the middle of the pond. Ripples spread it; fish swim over it. Opt-in, like the moon; it is up all day. See "Placement" below. |
|
|
135
|
+
|
|
136
|
+
- Omitted: the defaults above.
|
|
137
|
+
- `true`: every feature on. `false`: every feature off, which is the pond
|
|
138
|
+
exactly as it looked in 0.1.
|
|
139
|
+
- An object changes only the features it names: `{ moon: true }` adds the
|
|
140
|
+
moon to the defaults, `{ darkNight: false }` keeps the 0.1 night water.
|
|
141
|
+
|
|
142
|
+
Invalid values never throw: a feature that is not `true` or `false` keeps its
|
|
143
|
+
default, an unknown feature is ignored, and anything that is neither a
|
|
144
|
+
boolean nor an object gives the defaults, each with a
|
|
145
|
+
`console.warn('koi-ascii: …')`.
|
|
146
|
+
|
|
147
|
+
```js
|
|
148
|
+
createKoiPond(el, { sky: false }); // the 0.1 pond
|
|
149
|
+
pond.setSky({ sun: true }); // defaults plus the sun, from the next frame
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**Placement.** The moon and the sun are the only large shapes the sky draws,
|
|
153
|
+
and you can't place them. Both keep clear of the middle 40 % of the pond (in
|
|
154
|
+
each axis), where a headline usually sits, and travel along the left side and
|
|
155
|
+
the top edge, ending near the top-right corner. If your content sits there,
|
|
156
|
+
leave them off. Stars and fireflies are single characters spread over the
|
|
157
|
+
whole pond.
|
|
158
|
+
|
|
159
|
+
The features follow the palette (on a light palette they are dark ink and
|
|
160
|
+
have no glow), the `intensity` (they fade with the rest of the pond) and
|
|
161
|
+
reduced motion (see "Good to know").
|
|
162
|
+
|
|
163
|
+
## Palettes
|
|
164
|
+
|
|
165
|
+
A palette is a preset name or an object whose `mode` picks how it recolours
|
|
166
|
+
the pond:
|
|
167
|
+
|
|
168
|
+
- **`ramp`**: a gradient map. Each glyph's brightness picks a colour along
|
|
169
|
+
`colors` (2 to 5 evenly spaced stops, background first). Suits single-hue
|
|
170
|
+
looks; fish of the same brightness come out the same colour.
|
|
171
|
+
- **`roles`**: named colour slots. Each glyph takes the colour of what it
|
|
172
|
+
draws: `background`, `water` (deep water and ripples are derived from it),
|
|
173
|
+
`highlight` (glints, crests, eyes, stars, the sun's glitter) and, under `fish`, `pale` (white
|
|
174
|
+
scales), `ink` (black patches), `accent` (red markings) and `metallic`
|
|
175
|
+
(Ogon gold). The most control.
|
|
176
|
+
- **`filter`**: today's pond with a filter on top: `background`, `tint`
|
|
177
|
+
(multiplies, default `'#ffffff'`), `saturation` (0 grey … 1 original,
|
|
178
|
+
default 1) and `invert` (dark glyphs on light, hues kept). Keeps the
|
|
179
|
+
pond's own hues, but can't add new ones.
|
|
180
|
+
|
|
181
|
+
### Presets
|
|
182
|
+
|
|
183
|
+
| Name | Mode | Look |
|
|
184
|
+
| ---------- | -------- | ---------------------------------------------------------- |
|
|
185
|
+
| `pond` | `filter` | The default: today's pond, `{ mode: 'filter' }` with nothing changed. |
|
|
186
|
+
| `mono` | `ramp` | Greys on near-black. |
|
|
187
|
+
| `phosphor` | `ramp` | Green phosphor terminal. |
|
|
188
|
+
| `amber` | `ramp` | Amber terminal. |
|
|
189
|
+
| `paper` | `roles` | Dark ink on off-white, vermilion accent, ochre metallic. Light. |
|
|
190
|
+
| `brand` | `roles` | Indigo, lavender and pink. |
|
|
191
|
+
|
|
192
|
+
```js
|
|
193
|
+
createKoiPond(el, { palette: 'paper' });
|
|
194
|
+
pond.setPalette('phosphor');
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
`koiPalettes` holds the preset definitions, frozen (nested objects too), for
|
|
198
|
+
copying and tweaking:
|
|
199
|
+
|
|
200
|
+
```js
|
|
201
|
+
import { createKoiPond, koiPalettes } from 'koi-ascii';
|
|
202
|
+
|
|
203
|
+
createKoiPond(el, {
|
|
204
|
+
palette: { ...koiPalettes.paper, fish: { ...koiPalettes.paper.fish, accent: '#0a7' } },
|
|
205
|
+
});
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Your own palette
|
|
209
|
+
|
|
210
|
+
For your own brand colours, write a `roles` palette. The `brand` preset is
|
|
211
|
+
one; swap in your colours:
|
|
212
|
+
|
|
213
|
+
```js
|
|
214
|
+
createKoiPond(el, {
|
|
215
|
+
palette: {
|
|
216
|
+
mode: 'roles',
|
|
217
|
+
background: '#14102a', // indigo
|
|
218
|
+
water: '#3b2f6e',
|
|
219
|
+
highlight: '#f1edff',
|
|
220
|
+
fish: { pale: '#c8c2ff', ink: '#5a4a9a', accent: '#ff5c8a', metallic: '#ffc2d6' },
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Only `background` is required. A missing `highlight` is white on a dark
|
|
226
|
+
background and near-black on a light one; missing `water`, `pale` and `ink`
|
|
227
|
+
are mixed from the background and the highlight; `accent` and `metallic`
|
|
228
|
+
default to the pond's red and gold.
|
|
229
|
+
|
|
230
|
+
### Colours
|
|
231
|
+
|
|
232
|
+
Every colour is a CSS colour string, parsed by the browser: `'#0a7'`,
|
|
233
|
+
`'rgb(20 16 42)'`, `'hsl(250 45% 11%)'`, `'oklch(0.7 0.15 30)'`, `'teal'`
|
|
234
|
+
and so on. Alpha is ignored. `var()` is not supported: read the custom
|
|
235
|
+
property yourself (`getComputedStyle(document.documentElement).getPropertyValue('--brand')`)
|
|
236
|
+
and pass the value.
|
|
237
|
+
|
|
238
|
+
### Invalid input
|
|
239
|
+
|
|
240
|
+
A palette never throws and never calls `onError`. Invalid input falls back
|
|
241
|
+
field by field, with a `console.warn` for each:
|
|
242
|
+
|
|
243
|
+
- An unknown preset name or `mode` uses `'pond'`.
|
|
244
|
+
- An invalid colour field is ignored: a `roles` slot is derived as above, a
|
|
245
|
+
filter `tint` is white, a filter `background` is the pond's own.
|
|
246
|
+
- A `roles` palette without a valid `background` uses `'#06131a'`.
|
|
247
|
+
- A `ramp` with fewer than 2 or more than 5 colours, or an invalid one, uses
|
|
248
|
+
`'pond'`.
|
|
249
|
+
- A filter `saturation` outside [0, 1] is clamped; a non-number is 1. A
|
|
250
|
+
non-boolean `invert` is false.
|
|
251
|
+
|
|
252
|
+
### Light backgrounds and time of day
|
|
253
|
+
|
|
254
|
+
A background whose relative luminance is above 0.4 counts as light. A light
|
|
255
|
+
background stays light at night: the night shows only in the glyphs. The
|
|
256
|
+
glow halo is skipped on a light background.
|
|
257
|
+
|
|
258
|
+
On a dark palette the night is darker by default (the `darkNight` sky
|
|
259
|
+
feature, see "Night sky"): the water and a time-of-day graded background (`pond`, or a
|
|
260
|
+
`filter` with a dark `background`) at about ×0.7, and fewer caustics. A
|
|
261
|
+
`roles` background and a `ramp`'s first stop are never graded, so they keep
|
|
262
|
+
their colour; the water drawn on them still darkens. Turn it
|
|
263
|
+
off with `sky: { darkNight: false }`, or `sky: false` for the 0.1 look.
|
|
264
|
+
|
|
265
|
+
In `ramp` and `roles`, the time of day changes only brightness, so the hues
|
|
266
|
+
stay on your palette. `filter` (and so `pond`) keeps the full sky colours:
|
|
267
|
+
dusk warmth and night blue. A filter without `background` keeps the pond's
|
|
268
|
+
dark, time-of-day background, so `invert` needs a light `background` to read
|
|
269
|
+
as dark glyphs on light:
|
|
270
|
+
|
|
271
|
+
```js
|
|
272
|
+
pond.setPalette({ mode: 'filter', background: '#f5f2ea', invert: true });
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## Intensity
|
|
276
|
+
|
|
277
|
+
`intensity` mixes the pond with its background, linearly: 0 leaves only the
|
|
278
|
+
background, 1 is the full pond. For text on top, 0.2 to 0.5 keeps body text
|
|
279
|
+
readable with every palette (0.35 is a good start). Values outside [0, 1] are
|
|
280
|
+
clamped with a warning; a non-number is ignored with a warning (the option
|
|
281
|
+
then uses 1, the setter keeps the current value).
|
|
282
|
+
|
|
283
|
+
```js
|
|
284
|
+
const pond = createKoiPond(el, { palette: 'paper', intensity: 0.35 });
|
|
285
|
+
|
|
286
|
+
// A one-second fade-out:
|
|
287
|
+
const start = performance.now();
|
|
288
|
+
requestAnimationFrame(function fade(now) {
|
|
289
|
+
const t = Math.min(1, (now - start) / 1000);
|
|
290
|
+
pond.setIntensity(0.35 * (1 - t));
|
|
291
|
+
if (t < 1) requestAnimationFrame(fade);
|
|
292
|
+
else pond.pause(); // a fully faded pond keeps rendering until paused
|
|
293
|
+
});
|
|
294
|
+
```
|
|
295
|
+
|
|
100
296
|
## Interaction
|
|
101
297
|
|
|
102
298
|
- `'window'` (default): passive pointer listeners on `window`, mapped into the
|
|
@@ -159,12 +355,145 @@ logged to the console. Show your own fallback from `onError` if you want one.
|
|
|
159
355
|
hidden, and continues without a jump when it comes back. `pause()` and the
|
|
160
356
|
automatic pause combine: either one keeps it paused.
|
|
161
357
|
- While a pond is paused (by `pause()` or because it is off-screen), it draws
|
|
162
|
-
nothing: a `setTime` or a resize of the
|
|
163
|
-
|
|
164
|
-
|
|
358
|
+
nothing: a `setTime`, `setPalette`, `setIntensity`, `setSky` or a resize of the
|
|
359
|
+
container shows when it resumes.
|
|
360
|
+
- With `prefers-reduced-motion: reduce`, the fish swim slower, the water
|
|
361
|
+
flickers less, the stars hold still, the moon's reflection sways slower, the
|
|
362
|
+
sun's glitter flashes slower and the fireflies drift slower and glow softly
|
|
363
|
+
up and down instead of blinking.
|
|
165
364
|
- On software WebGL (SwiftShader, llvmpipe and the like) the pond renders at
|
|
166
365
|
a lower resolution to stay smooth.
|
|
167
366
|
|
|
367
|
+
## Demo debug mode
|
|
368
|
+
|
|
369
|
+
The demo page (not the package) has debug tooling behind one query flag,
|
|
370
|
+
`?debug`, on every build including the hosted demo. Without it the page
|
|
371
|
+
loads none of the debug code: no global, no panel, no shortcuts.
|
|
372
|
+
|
|
373
|
+
```
|
|
374
|
+
https://luckmorecat.github.io/koi-ascii/?debug
|
|
375
|
+
http://localhost:5173/?debug&seed=42&time=fixed&phase=0.8&view=raw
|
|
376
|
+
http://localhost:5173/?palette=paper&intensity=0.35
|
|
377
|
+
http://localhost:5173/?time=fixed&phase=0.95&sky=none
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### URL params
|
|
381
|
+
|
|
382
|
+
The scene lives in the URL next to `?debug`. Changes made from the panel or
|
|
383
|
+
the API are written back (`history.replaceState`), so a reload or a shared
|
|
384
|
+
link shows the same scene. Defaults are left out of the URL.
|
|
385
|
+
|
|
386
|
+
| Param | Values | Notes |
|
|
387
|
+
| ------- | ------------------------- | ------------------------------------------------------------------------------ |
|
|
388
|
+
| `seed` | `0` … `4294967295` | Same seed, same fish. Omitted: random on every load. |
|
|
389
|
+
| `time` | `loop`, `fixed`, `clock` | Default `loop`. |
|
|
390
|
+
| `phase` | `0` … `1` | `fixed`: the phase shown. `loop`: where the day starts (default 0). A running loop never rewrites it; only an explicit set (slider, day-part tick, API) does. |
|
|
391
|
+
| `cycle` | `1` … `86400` | Loop length in seconds, default 600. |
|
|
392
|
+
| `view` | `final`, `raw` | `raw`: glyphs on the plain background, without lighting grade or glow. Needs `?debug`. |
|
|
393
|
+
| `glow` | `on`, `off` | The halo around bright cells. Needs `?debug`. |
|
|
394
|
+
| `palette` | `pond`, `mono`, `phosphor`, `amber`, `paper`, `brand` | A preset (custom palettes can't go in the URL). Default `pond`; anything else is `pond`. |
|
|
395
|
+
| `intensity` | `0` … `1` | Default 1. |
|
|
396
|
+
| `sky` | `all` (or `on`), `none` (or `off`), or the features that are on, comma-separated: `stars,fireflies,darkNight,moon,sun` | Default `stars,fireflies,darkNight` (left out of the URL); `none` is the 0.1 pond. Anything unreadable gives the defaults. |
|
|
397
|
+
|
|
398
|
+
`seed`, `time`, `phase`, `cycle`, `palette`, `intensity` and `sky` also work without
|
|
399
|
+
`?debug`. Pause and
|
|
400
|
+
viewport size are not kept in the URL. The older `?perf` param is gone, and
|
|
401
|
+
`sky` no longer takes `slider|loop`: an old `?sky=slider&phase=0.8` link now
|
|
402
|
+
loops from 0.8 with the default sky; use `?time=fixed&phase=0.8` to hold it.
|
|
403
|
+
|
|
404
|
+
### Panel and shortcuts
|
|
405
|
+
|
|
406
|
+
`?debug` opens a panel (Perf, Time, Render, Scene, Capture) whose controls are
|
|
407
|
+
generated from the API's tools, so everything it does an agent can do too,
|
|
408
|
+
and it follows changes made through the API. The Render section has a preset
|
|
409
|
+
dropdown, an intensity slider and a toggle per sky feature. Shortcuts work only with
|
|
410
|
+
`?debug`, without modifier keys, and not while typing in a text or number box:
|
|
411
|
+
|
|
412
|
+
| Key | Does |
|
|
413
|
+
| ------- | -------------------- |
|
|
414
|
+
| `P` | show / hide the panel |
|
|
415
|
+
| `R` | toggle the raw view |
|
|
416
|
+
| `Space` | pause / resume |
|
|
417
|
+
| `.` | step one frame |
|
|
418
|
+
|
|
419
|
+
### `window.koi` for agents and humans
|
|
420
|
+
|
|
421
|
+
With `?debug` the page defines `window.koi`, a self-describing tool registry
|
|
422
|
+
in the style of MCP, meant to be driven through browser JS evaluation (for
|
|
423
|
+
example Playwright's `page.evaluate`). The console prints a pointer to
|
|
424
|
+
`await koi.help()` on load.
|
|
425
|
+
|
|
426
|
+
```js
|
|
427
|
+
await page.waitForFunction(() => window.koi); // the debug chunk loads on demand
|
|
428
|
+
await page.evaluate(() => koi.ready); // the pond is up
|
|
429
|
+
console.log(await page.evaluate(() => koi.help()));
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
- `koi.tools()`: every tool as `{ name, description, inputSchema, annotations }`.
|
|
433
|
+
`inputSchema` is a JSON Schema subset: `type: 'object'` with `properties`,
|
|
434
|
+
an optional `required` list and `additionalProperties: false` (unknown
|
|
435
|
+
arguments are rejected). Each property has a `description` and is a
|
|
436
|
+
`number` / `integer` (optional `minimum`, `maximum`), a `boolean` or a
|
|
437
|
+
`string` with an `enum`, with an optional `default` (the value used when
|
|
438
|
+
the argument is left out). `annotations` holds:
|
|
439
|
+
`section` (the panel section), `readOnly` (the call changes nothing),
|
|
440
|
+
`live` (each argument applies on its own, so the panel applies controls
|
|
441
|
+
as they change), `reflects` (the `getState()` key that holds each
|
|
442
|
+
argument's current value) and `shortcut` (`{ key, does }`).
|
|
443
|
+
- `koi.call(name, args)`: always resolves, never rejects, with
|
|
444
|
+
`{ ok: true, result }` or `{ ok: false, error: { code, message } }`. Codes:
|
|
445
|
+
`unknown-tool`, `invalid-args` (the message names the path, e.g.
|
|
446
|
+
`args.phase: must be at most 1, got 2`), `not-ready`, `context-lost`,
|
|
447
|
+
`pond-failed`, `internal`. Results are plain JSON.
|
|
448
|
+
- `koi.<tool>(args)`: the same call, resolving to `result` and rejecting
|
|
449
|
+
with the error object.
|
|
450
|
+
- `koi.ready`: resolves once the current pond is up. It rejects with
|
|
451
|
+
`{ code, message }`: `pond-failed` if the pond can't start, `not-ready`
|
|
452
|
+
if a `reseed` replaced it before it was up. Each `reseed` gives a new
|
|
453
|
+
promise, so read `koi.ready` again after one.
|
|
454
|
+
- `koi.on('change', fn)` after any call that changes state, with
|
|
455
|
+
`{ tool, state }`; `koi.on('frame', fn)` per drawn frame, including frames
|
|
456
|
+
drawn by `step` (marked `stepped: true`, and left out of `getPerf`). Both return an
|
|
457
|
+
unsubscribe function; `koi.off(event, fn)` works too. The last write wins.
|
|
458
|
+
- `koi.help()`: every tool with its description, arguments and shortcut.
|
|
459
|
+
|
|
460
|
+
| Tool | Args | Does |
|
|
461
|
+
| ------------- | ----------------------------- | ---------------------------------------------------------------------- |
|
|
462
|
+
| `getState` | | The whole state: ready, paused, frame counter, time, render (view, glow, palette, intensity), sky (each feature), seed, viewport, canvas size. |
|
|
463
|
+
| `setTime` | `mode?`, `phase?`, `cycle?`, `hold?` | Time of day; `hold` keeps the phase until a call with `hold: false`. |
|
|
464
|
+
| `setDayPart` | `part` | Jumps to a lighting keyframe (`night`, `blue-hour-am`, … `noon` …). |
|
|
465
|
+
| `getPerf` | | fps, 1% low, frame / CPU / GPU ms (GPU `null` without the timer extension), canvas size, GPU string, software flag, seed. |
|
|
466
|
+
| `profile` | `frames?` (400) | GPU µs per pass (`scene`, `glow`, `ascii`, `fly`; `frame` is all of them), median and p90, from timer queries around each draw over `frames` redraws of the paused frame. `{ gpuTimer: false }` without the timer extension. |
|
|
467
|
+
| `setRender` | `view?`, `glow?` | `final` / `raw` view and the glow halo. The raw view ignores the palette and intensity. |
|
|
468
|
+
| `setPalette` | `palette` | Switches to a preset (`pond`, `mono`, `phosphor`, `amber`, `paper`, `brand`). |
|
|
469
|
+
| `setIntensity` | `intensity` | 0 background only … 1 full pond. |
|
|
470
|
+
| `setSky` | `stars?`, `fireflies?`, `darkNight?`, `moon?`, `sun?` | Turns sky features on or off; omitted ones keep their value. |
|
|
471
|
+
| `reseed` | `seed?` | Remounts the pond with a seed (random if omitted), as on a fresh load of the URL. |
|
|
472
|
+
| `getFish` | | Each fish's variety, position, heading, speed, depth, behaviour. |
|
|
473
|
+
| `pause`, `resume` | | Stops and restarts the frame loop (no catch-up after a pause). |
|
|
474
|
+
| `step` | `frames?` (1), `dt?` (1/60) | Pauses, then simulates and draws the frames synchronously, so it works in a background tab. |
|
|
475
|
+
| `capture` | | Redraws the current state and returns `{ dataUrl, width, height }` (PNG). |
|
|
476
|
+
| `setViewport` | `width?`, `height?`, `dpr?` | Pins the pond to a size in CSS px (and a pixel ratio); no args fills the window again. |
|
|
477
|
+
| `setPanel` | `visible?` | Shows or hides the panel; no args toggles. |
|
|
478
|
+
|
|
479
|
+
A reproducible frame, identical on every run on the same machine:
|
|
480
|
+
|
|
481
|
+
```js
|
|
482
|
+
await koi.setViewport({ width: 800, height: 600, dpr: 1 });
|
|
483
|
+
await koi.setTime({ mode: 'fixed', phase: 0.5 });
|
|
484
|
+
await koi.reseed({ seed: 42 });
|
|
485
|
+
await koi.step({ frames: 120 });
|
|
486
|
+
const { dataUrl } = await koi.capture();
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
Two tools measure speed. `getPerf` is throughput of the running loop:
|
|
490
|
+
requestAnimationFrame fps and frame ms, with GPU ms for the whole frame.
|
|
491
|
+
`profile` is GPU time per pass: it pauses, redraws the same frame with a
|
|
492
|
+
timer query around every draw and gives the median and p90 per pass, so it
|
|
493
|
+
shows which pass a change made slower. To compare a change with another
|
|
494
|
+
revision, `npm run perf -- '?debug' --base main` profiles both side by side
|
|
495
|
+
(options at the top of `scripts/perf.mjs`).
|
|
496
|
+
|
|
168
497
|
## License
|
|
169
498
|
|
|
170
499
|
[MIT](./LICENSE)
|
package/dist/index.d.ts
CHANGED
|
@@ -33,6 +33,73 @@ export interface KoiPondError extends Error {
|
|
|
33
33
|
readonly name: 'KoiPondError';
|
|
34
34
|
readonly code: KoiPondErrorCode;
|
|
35
35
|
}
|
|
36
|
+
/** Any CSS colour, parsed by the browser. Alpha is ignored; `var()` is not supported. */
|
|
37
|
+
export type KoiPondColor = string;
|
|
38
|
+
export type KoiPondPaletteName = 'pond' | 'mono' | 'phosphor' | 'amber' | 'paper' | 'brand';
|
|
39
|
+
/** Gradient map: brightness picks a colour along `colors`, background first. */
|
|
40
|
+
export interface KoiPondRampPalette {
|
|
41
|
+
mode: 'ramp';
|
|
42
|
+
/** 2 to 5 stops, evenly spaced. */
|
|
43
|
+
colors: KoiPondColor[];
|
|
44
|
+
}
|
|
45
|
+
/** Named slots: each glyph takes the colour of what it draws. */
|
|
46
|
+
export interface KoiPondRolesPalette {
|
|
47
|
+
mode: 'roles';
|
|
48
|
+
background: KoiPondColor;
|
|
49
|
+
/** Deep water and ripples are derived from it. */
|
|
50
|
+
water?: KoiPondColor;
|
|
51
|
+
/** Glints, crests, eyes. */
|
|
52
|
+
highlight?: KoiPondColor;
|
|
53
|
+
fish?: {
|
|
54
|
+
/** White scales. */
|
|
55
|
+
pale?: KoiPondColor;
|
|
56
|
+
/** Black patches (Showa sumi). */
|
|
57
|
+
ink?: KoiPondColor;
|
|
58
|
+
/** Red markings (Kohaku/Showa hi). */
|
|
59
|
+
accent?: KoiPondColor;
|
|
60
|
+
/** Ogon gold. */
|
|
61
|
+
metallic?: KoiPondColor;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/** Colour filter over today's pond; keeps its hues. */
|
|
65
|
+
export interface KoiPondFilterPalette {
|
|
66
|
+
mode: 'filter';
|
|
67
|
+
background?: KoiPondColor;
|
|
68
|
+
/** Default `'#ffffff'`. */
|
|
69
|
+
tint?: KoiPondColor;
|
|
70
|
+
/** 0 grey … 1 original (default). */
|
|
71
|
+
saturation?: number;
|
|
72
|
+
/** Dark glyphs on light, hues kept. */
|
|
73
|
+
invert?: boolean;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* A preset name or a palette object; `mode` picks how it recolours the pond.
|
|
77
|
+
* Invalid fields fall back one by one with a `console.warn` (never a throw);
|
|
78
|
+
* an unknown preset or mode falls back to `'pond'`.
|
|
79
|
+
*/
|
|
80
|
+
export type KoiPondPalette = KoiPondPaletteName | KoiPondRampPalette | KoiPondRolesPalette | KoiPondFilterPalette;
|
|
81
|
+
/**
|
|
82
|
+
* Night-sky features, each on or off. Omitted fields keep their defaults:
|
|
83
|
+
* `stars`, `fireflies` and `darkNight` on, `moon` and `sun` off.
|
|
84
|
+
*/
|
|
85
|
+
export interface KoiPondSkyFeatures {
|
|
86
|
+
/** Reflected stars at night. */
|
|
87
|
+
stars?: boolean;
|
|
88
|
+
/** Fireflies at dusk, over the fish. */
|
|
89
|
+
fireflies?: boolean;
|
|
90
|
+
/** Darker water, fewer caustics and a darker background at night, on dark palettes only. */
|
|
91
|
+
darkNight?: boolean;
|
|
92
|
+
/** The moon's reflection at night. */
|
|
93
|
+
moon?: boolean;
|
|
94
|
+
/** The sun's reflection on the water by day, as glitter. */
|
|
95
|
+
sun?: boolean;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* The night sky. `true` turns every feature on, `false` every one off (the
|
|
99
|
+
* pond as it was in 0.1); an object changes only the features it names.
|
|
100
|
+
* Invalid values fall back one by one with a `console.warn` (never a throw).
|
|
101
|
+
*/
|
|
102
|
+
export type KoiPondSky = boolean | KoiPondSkyFeatures;
|
|
36
103
|
export interface KoiPondOptions {
|
|
37
104
|
/** Default `{ mode: 'loop', cycle: 600, phase: 0 }`. */
|
|
38
105
|
time?: KoiPondTime;
|
|
@@ -45,7 +112,16 @@ export interface KoiPondOptions {
|
|
|
45
112
|
* face yourself. Default `'"JetBrains Mono", ui-monospace, Menlo, Consolas, monospace'`.
|
|
46
113
|
*/
|
|
47
114
|
fontFamily?: string;
|
|
48
|
-
/**
|
|
115
|
+
/** Default `'pond'`. */
|
|
116
|
+
palette?: KoiPondPalette;
|
|
117
|
+
/** 0 leaves only the background, 1 is the full pond (linear). Default 1. */
|
|
118
|
+
intensity?: number;
|
|
119
|
+
/** Default: stars, fireflies and the darker night on; moon and sun off. */
|
|
120
|
+
sky?: KoiPondSky;
|
|
121
|
+
/**
|
|
122
|
+
* CSS colour shown before the first frame and if the pond fails. Default:
|
|
123
|
+
* the palette's background (and it follows `setPalette`).
|
|
124
|
+
*/
|
|
49
125
|
background?: string;
|
|
50
126
|
/** Called (asynchronously) when WebGL2 is missing or the renderer fails. */
|
|
51
127
|
onError?: (error: KoiPondError) => void;
|
|
@@ -53,12 +129,39 @@ export interface KoiPondOptions {
|
|
|
53
129
|
export interface KoiPond {
|
|
54
130
|
/** Changes the time of day live. A loop without `phase` carries on from the current phase. */
|
|
55
131
|
setTime(time: KoiPondTime): void;
|
|
132
|
+
/** Changes the palette from the next frame on; the fish carry on. */
|
|
133
|
+
setPalette(palette: KoiPondPalette): void;
|
|
134
|
+
/** Changes the intensity from the next frame on; animate it for a fade. */
|
|
135
|
+
setIntensity(intensity: number): void;
|
|
136
|
+
/** Changes the night-sky features from the next frame on, as the `sky` option would; the fish carry on. */
|
|
137
|
+
setSky(sky: KoiPondSky): void;
|
|
56
138
|
/** Stops simulating and drawing until `resume()`; combines with the off-screen pause. */
|
|
57
139
|
pause(): void;
|
|
58
140
|
resume(): void;
|
|
59
141
|
/** Stops the pond, removes its canvas and listeners and frees the WebGL context. Safe to call twice. */
|
|
60
142
|
destroy(): void;
|
|
61
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* A roles preset as frozen in `koiPalettes`, `fish` included. Still assignable
|
|
146
|
+
* to `KoiPondRolesPalette`. (A ramp's `colors` stays typed as a plain array so
|
|
147
|
+
* a preset can be passed as a palette, but it is frozen too.)
|
|
148
|
+
*/
|
|
149
|
+
type FrozenRoles = Readonly<KoiPondRolesPalette> & {
|
|
150
|
+
readonly fish?: Readonly<NonNullable<KoiPondRolesPalette['fish']>>;
|
|
151
|
+
};
|
|
152
|
+
/**
|
|
153
|
+
* Frozen preset definitions, for copying and tweaking:
|
|
154
|
+
* `{ ...koiPalettes.paper, fish: { ...koiPalettes.paper.fish, accent: '#0a7' } }`.
|
|
155
|
+
* Each preset keeps its own palette type, so its fields can be spread.
|
|
156
|
+
*/
|
|
157
|
+
export declare const koiPalettes: {
|
|
158
|
+
readonly pond: Readonly<KoiPondFilterPalette>;
|
|
159
|
+
readonly mono: Readonly<KoiPondRampPalette>;
|
|
160
|
+
readonly phosphor: Readonly<KoiPondRampPalette>;
|
|
161
|
+
readonly amber: Readonly<KoiPondRampPalette>;
|
|
162
|
+
readonly paper: FrozenRoles;
|
|
163
|
+
readonly brand: FrozenRoles;
|
|
164
|
+
};
|
|
62
165
|
/**
|
|
63
166
|
* Mounts a koi pond into `container`: appends a canvas that fills it (100% ×
|
|
64
167
|
* 100%) and starts once the font is ready. Size and position the container
|
|
@@ -66,3 +169,4 @@ export interface KoiPond {
|
|
|
66
169
|
* anywhere.
|
|
67
170
|
*/
|
|
68
171
|
export declare function createKoiPond(container: HTMLElement, options?: KoiPondOptions | null): KoiPond;
|
|
172
|
+
export {};
|