koi-ascii 0.3.1 → 0.4.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
@@ -52,6 +52,8 @@ const pond = createKoiPond(container, {
52
52
  palette: 'pond', // a preset name or a palette object; see "Palettes"
53
53
  intensity: 1, // 0 background only … 1 full pond; see "Intensity"
54
54
  sky: { moon: true }, // night-sky features; see "Night sky"
55
+ avoid: [document.querySelector('h1')], // fade under page content; see "Readable content"
56
+ avoidFade: { strength: 1, feather: 56 },
55
57
  background: '#06131a', // CSS colour before the first frame and on failure
56
58
  onError: (error) => console.warn(error.code),
57
59
  });
@@ -66,10 +68,12 @@ const pond = createKoiPond(container, {
66
68
  | `palette` | `'pond'` | Can change later with `setPalette`. See "Palettes". |
67
69
  | `intensity` | `1` | Can change later with `setIntensity`. See "Intensity". |
68
70
  | `sky` | stars, fireflies and `darkNight` on; moon and sun off | Can change later with `setSky`. See "Night sky". |
71
+ | `avoid` | none | Can change later with `setAvoid`. See "Readable content". |
72
+ | `avoidFade` | `{ strength: 1, feather: 56 }` | Can change later with `setAvoidFade`. See "Readable content". |
69
73
  | `background` | the palette's background | Shown until the first frame and if the pond fails. |
70
74
  | `onError` | logs to the console | See "Failure". |
71
75
 
72
- The time, palette, intensity and sky can change after creation. For anything else,
76
+ The time, palette, intensity, sky and readability zones can change after creation. For anything else,
73
77
  `destroy()` the pond and create a new one.
74
78
 
75
79
  Without `background`, the container shows the palette's background (and
@@ -85,6 +89,8 @@ always drawn on the palette's background.
85
89
  | `setPalette(palette)` | Recolours the pond from the next frame on; the fish carry on. |
86
90
  | `setIntensity(intensity)` | Fades the pond towards its background from the next frame on. Animate it for a fade. |
87
91
  | `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. |
92
+ | `setAvoid(list)` | Replaces the readability zones from the next frame on. `[]` removes them. |
93
+ | `setAvoidFade(fade)` | Changes the fade under the zones from the next frame on. Fields left out keep their current values. Animate `strength` to fade it in or out. |
88
94
  | `pause()` | Stops simulating and drawing. The last frame stays on screen. |
89
95
  | `resume()` | Carries on from where it paused, without jumping ahead. |
90
96
  | `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. |
@@ -92,7 +98,8 @@ always drawn on the palette's background.
92
98
  Exported types: `KoiPond`, `KoiPondOptions`, `KoiPondTime`,
93
99
  `KoiPondInteractive`, `KoiPondError`, `KoiPondErrorCode`, `KoiPondPalette`,
94
100
  `KoiPondPaletteName`, `KoiPondRampPalette`, `KoiPondRolesPalette`,
95
- `KoiPondFilterPalette`, `KoiPondColor`, `KoiPondSky`, `KoiPondSkyFeatures`.
101
+ `KoiPondFilterPalette`, `KoiPondColor`, `KoiPondSky`, `KoiPondSkyFeatures`,
102
+ `KoiPondAvoid`, `KoiPondAvoidFade`.
96
103
  Exported value besides `createKoiPond`: `koiPalettes`.
97
104
 
98
105
  ## Time of day
@@ -293,6 +300,77 @@ requestAnimationFrame(function fade(now) {
293
300
  });
294
301
  ```
295
302
 
303
+ ## Readable content
304
+
305
+ A hero headline, cards or prose over the pond stay readable when you list
306
+ them in `avoid`: the pond fades everything it draws under each element (fish,
307
+ water, trails, fireflies, moon, sun and glow), with a soft edge, and stays
308
+ alive everywhere else. Fish swim under the text as if deeper; they don't steer
309
+ around it.
310
+
311
+ ```js
312
+ const pond = createKoiPond(el, {
313
+ avoid: [
314
+ document.querySelector('.hero h1'), // the element's box, grown by 12 px
315
+ { element: document.querySelector('.card'), pad: 24 }, // a wider margin
316
+ ],
317
+ avoidFade: { strength: 1, feather: 56 },
318
+ });
319
+
320
+ pond.setAvoid([...document.querySelectorAll('article p')]); // replaces the list
321
+ pond.setAvoidFade({ strength: 0.5 }); // the feather stays 56
322
+ ```
323
+
324
+ | Setting | Default | Notes |
325
+ | ---------- | ------- | ----- |
326
+ | `pad` | `12` | CSS px the zone extends past the element's box on every side. The corners are rounded by the element's `border-radius` plus `pad`. |
327
+ | `strength` | `1` | 0 turns the fade off (the zones stay listed) … 1 the tuned look. Clamped to [0, 1]. |
328
+ | `feather` | `56` | Width of the soft edge outside the padded box, in CSS px. 0 is a hard edge; 48 to 80 looks intentional. |
329
+
330
+ - **Invalid values** fall back with a `console.warn`: a negative or non-number
331
+ `pad` gives 12 (so does a non-finite one), a negative or non-finite `feather` 56, a non-finite
332
+ `strength` 1. An `avoid` that isn't an array is treated as `[]`, and an entry
333
+ that isn't an element or `{ element }` (a `DOMRect` included) is skipped.
334
+ - **`setAvoidFade` merges.** Fields you leave out keep their current values,
335
+ so animating `strength` keeps a custom `feather`.
336
+ - **Up to 16 zones** are drawn at once. Elements outside the visible part of
337
+ the canvas (with a margin of 50 px plus the feather) don't count. Past 16
338
+ visible zones, the first 16 in list order are used and the pond warns once
339
+ per list.
340
+ - **Hidden and detached elements:** an element that measures zero size
341
+ (`display: none`, removed from the page) is skipped, and counts again when
342
+ it has a size. The pond keeps its references until `setAvoid` replaces the
343
+ list or `destroy()` runs, so call `setAvoid` when you remove elements for
344
+ good.
345
+ - **Pointer:** with `interactive: 'window'`, fish that the pointer draws
346
+ under a hovered card still come, and fade as they arrive. Zones and the
347
+ pointer don't know about each other.
348
+ - **When to use a CSS scrim instead:** for opaque UI such as modals, menus
349
+ and nav bars, put a background on the element. The fade keeps the pond
350
+ visible under the zone as texture, which suits text, not controls that
351
+ should hide what is behind them.
352
+
353
+ **Cost.** On the GPU the fade adds about 1 µs to a frame (+3.6% at
354
+ 1280×800). Each frame the pond
355
+ draws, it calls `getBoundingClientRect()` on every listed element (about 5 µs
356
+ for 8 elements, 9 µs for 16, 30 µs for 64, 90 µs for 200), so scrolling,
357
+ resizing, sticky elements, transforms and CSS animations need no listeners.
358
+ `border-radius` is read only when the list is set, when the pond mounts or
359
+ resizes, and when an element comes back from zero size. It is cached in
360
+ between, so a `border-radius` change from CSS (a hover style, a toggled
361
+ class) keeps the old corners until then; call `setAvoid` again to pick it up.
362
+ Nothing is read while the pond is paused (manual draws such as `step` still
363
+ measure), off-screen or in a hidden tab, when the list is empty, or while
364
+ `strength` is 0, so a faded-out fade costs no layout reads.
365
+ Culling with `IntersectionObserver`, for long lists, may come later without
366
+ an API change.
367
+
368
+ **Known limit.** The read happens inside the pond's own animation frame. If
369
+ your page writes layout both before and after that read in the same frame,
370
+ the read forces one extra layout (about 4 ms on a heavy page), however many
371
+ elements are listed. If your page's own animation frame changes layout after
372
+ the read, the zones lag that change by one frame.
373
+
296
374
  ## Interaction
297
375
 
298
376
  - `'window'` (default): passive pointer listeners on `window`, mapped into the
@@ -355,8 +433,8 @@ logged to the console. Show your own fallback from `onError` if you want one.
355
433
  hidden, and continues without a jump when it comes back. `pause()` and the
356
434
  automatic pause combine: either one keeps it paused.
357
435
  - While a pond is paused (by `pause()` or because it is off-screen), it draws
358
- nothing: a `setTime`, `setPalette`, `setIntensity`, `setSky` or a resize of the
359
- container shows when it resumes.
436
+ nothing: a `setTime`, `setPalette`, `setIntensity`, `setSky`, `setAvoid`,
437
+ `setAvoidFade` or a resize of the container shows when it resumes.
360
438
  - With `prefers-reduced-motion: reduce`, the fish swim slower, the water
361
439
  flickers less, the stars hold still, the moon's reflection sways slower, the
362
440
  sun's glitter flashes slower and the fireflies drift slower and glow softly
@@ -394,8 +472,9 @@ link shows the same scene. Defaults are left out of the URL.
394
472
  | `palette` | `pond`, `mono`, `phosphor`, `amber`, `paper`, `brand` | A preset (custom palettes can't go in the URL). Default `pond`; anything else is `pond`. |
395
473
  | `intensity` | `0` … `1` | Default 1. |
396
474
  | `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. |
475
+ | `avoid` | flag | Adds a centred headline and a line of text over the pond, listed as one readability zone (see "Readable content"). |
397
476
 
398
- `seed`, `time`, `phase`, `cycle`, `palette`, `intensity` and `sky` also work without
477
+ `seed`, `time`, `phase`, `cycle`, `palette`, `intensity`, `sky` and `avoid` also work without
399
478
  `?debug`. Pause and
400
479
  viewport size are not kept in the URL. The older `?perf` param is gone, and
401
480
  `sky` no longer takes `slider|loop`: an old `?sky=slider&phase=0.8` link now
@@ -463,11 +542,12 @@ console.log(await page.evaluate(() => koi.help()));
463
542
  | `setTime` | `mode?`, `phase?`, `cycle?`, `hold?` | Time of day; `hold` keeps the phase until a call with `hold: false`. |
464
543
  | `setDayPart` | `part` | Jumps to a lighting keyframe (`night`, `blue-hour-am`, … `noon` …). |
465
544
  | `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. |
545
+ | `profile` | `frames?` (400) | GPU µs per pass (`scene`, `emission`, `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
546
  | `setRender` | `view?`, `glow?` | `final` / `raw` view and the glow halo. The raw view ignores the palette and intensity. |
468
547
  | `setPalette` | `palette` | Switches to a preset (`pond`, `mono`, `phosphor`, `amber`, `paper`, `brand`). |
469
548
  | `setIntensity` | `intensity` | 0 background only … 1 full pond. |
470
549
  | `setSky` | `stars?`, `fireflies?`, `darkNight?`, `moon?`, `sun?` | Turns sky features on or off; omitted ones keep their value. |
550
+ | `showAvoid` | `on?` | Outlines the readability zones (`?avoid`) at their padded edge in red; no args toggles. Returns `{ on }`. The raw view shows no zones. Not kept in the URL. |
471
551
  | `reseed` | `seed?` | Remounts the pond with a seed (random if omitted), as on a fresh load of the URL. |
472
552
  | `getFish` | | Each fish's variety, position, heading, speed, depth, behaviour. |
473
553
  | `pause`, `resume` | | Stops and restarts the frame loop (no catch-up after a pause). |
package/dist/index.d.ts CHANGED
@@ -100,6 +100,27 @@ export interface KoiPondSkyFeatures {
100
100
  * Invalid values fall back one by one with a `console.warn` (never a throw).
101
101
  */
102
102
  export type KoiPondSky = boolean | KoiPondSkyFeatures;
103
+ /**
104
+ * A page element the pond fades under, so content over it stays readable:
105
+ * the element itself, or `{ element, pad }` with `pad` the CSS px the zone
106
+ * extends past the element's box on every side (default 12). The zone's
107
+ * corners follow the element's `border-radius` plus `pad`. The pond measures
108
+ * every listed element on each frame it draws, so scrolling, resizing,
109
+ * transforms and animations need nothing more; an element that measures zero
110
+ * size (hidden, detached) is skipped until it has a size again. The pond
111
+ * keeps its references until `setAvoid` replaces the list or `destroy()`.
112
+ */
113
+ export type KoiPondAvoid = Element | {
114
+ element: Element;
115
+ pad?: number;
116
+ };
117
+ /** How the pond fades under the `avoid` zones. */
118
+ export interface KoiPondAvoidFade {
119
+ /** 0 off … 1 the tuned look (default). Scales every fade amount together. */
120
+ strength?: number;
121
+ /** Width of the soft edge in CSS px. Default 56. */
122
+ feather?: number;
123
+ }
103
124
  export interface KoiPondOptions {
104
125
  /** Default `{ mode: 'loop', cycle: 600, phase: 0 }`. */
105
126
  time?: KoiPondTime;
@@ -118,6 +139,14 @@ export interface KoiPondOptions {
118
139
  intensity?: number;
119
140
  /** Default: stars, fireflies and the darker night on; moon and sun off. */
120
141
  sky?: KoiPondSky;
142
+ /**
143
+ * Page elements to fade under (hero text, cards), up to 16 visible at once:
144
+ * past that the first 16 in list order are used. Default none.
145
+ * Invalid entries are skipped with a `console.warn`.
146
+ */
147
+ avoid?: KoiPondAvoid[];
148
+ /** Default `{ strength: 1, feather: 56 }`; invalid values fall back with a `console.warn`. */
149
+ avoidFade?: KoiPondAvoidFade;
121
150
  /**
122
151
  * CSS colour shown before the first frame and if the pond fails. Default:
123
152
  * the palette's background (and it follows `setPalette`).
@@ -135,6 +164,13 @@ export interface KoiPond {
135
164
  setIntensity(intensity: number): void;
136
165
  /** Changes the night-sky features from the next frame on, as the `sky` option would; the fish carry on. */
137
166
  setSky(sky: KoiPondSky): void;
167
+ /** Replaces the zone list from the next frame on. */
168
+ setAvoid(list: KoiPondAvoid[]): void;
169
+ /**
170
+ * Changes the fade from the next frame on; animate `strength` to fade it in
171
+ * or out. Fields left out keep their current values.
172
+ */
173
+ setAvoidFade(fade: KoiPondAvoidFade): void;
138
174
  /** Stops simulating and drawing until `resume()`; combines with the off-screen pause. */
139
175
  pause(): void;
140
176
  resume(): void;