@promptctl/rich-js 0.28.0 → 0.29.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 +7 -7
- package/dist/core/animation.d.ts +37 -37
- package/dist/core/animation.js +61 -61
- package/dist/core/ansi.d.ts +4 -4
- package/dist/core/ansi.js +9 -9
- package/dist/core/bidi.js +6 -6
- package/dist/core/cells.d.ts +1 -1
- package/dist/core/cells.js +5 -5
- package/dist/core/clock.d.ts +1 -1
- package/dist/core/color.d.ts +50 -50
- package/dist/core/color.js +69 -69
- package/dist/core/console.d.ts +8 -8
- package/dist/core/console.js +5 -5
- package/dist/core/control-codes.d.ts +3 -3
- package/dist/core/control-codes.js +7 -7
- package/dist/core/easing.d.ts +40 -40
- package/dist/core/easing.js +40 -40
- package/dist/core/env.d.ts +1 -1
- package/dist/core/env.js +1 -1
- package/dist/core/export-html.js +1 -1
- package/dist/core/export-lines.d.ts +11 -11
- package/dist/core/export-lines.js +12 -12
- package/dist/core/export-svg.d.ts +3 -3
- package/dist/core/export-svg.js +4 -4
- package/dist/core/markup.js +1 -1
- package/dist/core/memo.d.ts +1 -1
- package/dist/core/memo.js +1 -1
- package/dist/core/oklch.d.ts +14 -14
- package/dist/core/oklch.js +10 -10
- package/dist/core/orient.d.ts +3 -3
- package/dist/core/orient.js +3 -3
- package/dist/core/paint.d.ts +9 -9
- package/dist/core/paint.js +3 -3
- package/dist/core/pretty.d.ts +1 -1
- package/dist/core/pretty.js +3 -3
- package/dist/core/protocol.d.ts +26 -26
- package/dist/core/protocol.js +22 -22
- package/dist/core/render.d.ts +11 -11
- package/dist/core/render.js +12 -12
- package/dist/core/segment.d.ts +3 -3
- package/dist/core/segment.js +5 -5
- package/dist/core/strip.d.ts +83 -83
- package/dist/core/strip.js +70 -70
- package/dist/core/style.d.ts +20 -20
- package/dist/core/style.js +55 -55
- package/dist/core/text.d.ts +2 -2
- package/dist/core/text.js +5 -5
- package/dist/core/timeline.d.ts +5 -5
- package/dist/core/timeline.js +9 -9
- package/dist/core/tween.d.ts +6 -6
- package/dist/core/tween.js +10 -10
- package/dist/core/view.d.ts +4 -4
- package/dist/core/view.js +12 -12
- package/dist/host/app.d.ts +9 -9
- package/dist/host/app.js +12 -12
- package/dist/host/host-environment.d.ts +2 -2
- package/dist/host/host-environment.js +2 -2
- package/dist/host/index.d.ts +2 -2
- package/dist/host/input.d.ts +7 -7
- package/dist/host/input.js +4 -4
- package/dist/host/terminal-colors.d.ts +2 -2
- package/dist/host/terminal-colors.js +4 -4
- package/dist/host/terminal-host.d.ts +2 -2
- package/dist/host/terminal-host.js +1 -1
- package/dist/index.d.ts +14 -14
- package/dist/index.js +10 -10
- package/dist/node/prompt.d.ts +1 -1
- package/dist/node/prompt.js +1 -1
- package/dist/node/terminal-host.d.ts +1 -1
- package/dist/node/traceback.js +2 -2
- package/dist/renderables/change.js +2 -2
- package/dist/renderables/effect.d.ts +39 -39
- package/dist/renderables/effect.js +45 -45
- package/dist/renderables/effects.d.ts +81 -81
- package/dist/renderables/effects.js +157 -157
- package/dist/renderables/embed.d.ts +1 -1
- package/dist/renderables/embed.js +2 -2
- package/dist/renderables/flexStrip.d.ts +7 -7
- package/dist/renderables/flexStrip.js +15 -15
- package/dist/renderables/frames.d.ts +6 -6
- package/dist/renderables/frames.js +11 -11
- package/dist/renderables/glyph-effect.d.ts +15 -15
- package/dist/renderables/glyph-effect.js +17 -17
- package/dist/renderables/glyph-effects.d.ts +7 -7
- package/dist/renderables/glyph-effects.js +16 -16
- package/dist/renderables/html-entities.d.ts +1 -1
- package/dist/renderables/html-entities.js +1 -1
- package/dist/renderables/lifecycle.d.ts +4 -4
- package/dist/renderables/lifecycle.js +12 -12
- package/dist/renderables/live.d.ts +2 -2
- package/dist/renderables/live.js +7 -7
- package/dist/renderables/markdown-parse.js +3 -3
- package/dist/renderables/panel.js +3 -3
- package/dist/renderables/progress.d.ts +1 -1
- package/dist/renderables/progress.js +1 -1
- package/dist/renderables/progressBar.d.ts +1 -1
- package/dist/renderables/progressBar.js +6 -6
- package/dist/renderables/prompt.d.ts +1 -1
- package/dist/renderables/scroll.d.ts +1 -1
- package/dist/renderables/scroll.js +3 -3
- package/dist/renderables/spinner.d.ts +4 -4
- package/dist/renderables/spinner.js +10 -10
- package/dist/renderables/syntax.d.ts +2 -2
- package/dist/renderables/syntax.js +2 -2
- package/dist/renderables/table.js +3 -3
- package/dist/renderables/traceback.d.ts +1 -1
- package/dist/renderables/traceback.js +1 -1
- package/dist/renderables/tree.js +3 -3
- package/dist/template-bindings/animation-funcs.d.ts +14 -14
- package/dist/template-bindings/animation-funcs.js +27 -27
- package/dist/template-bindings/color-funcs.d.ts +14 -14
- package/dist/template-bindings/color-funcs.js +25 -25
- package/dist/template-bindings/index.d.ts +9 -9
- package/dist/template-bindings/index.js +9 -9
- package/dist/template-bindings/palette-funcs.d.ts +1 -1
- package/dist/template-bindings/palette-funcs.js +3 -3
- package/dist/template-bindings/style-funcs.d.ts +5 -5
- package/dist/template-bindings/style-funcs.js +16 -16
- package/dist/themes/{transpose.d.ts → adjust.d.ts} +23 -23
- package/dist/themes/{transpose.js → adjust.js} +28 -28
- package/dist/themes/buildPalette.d.ts +3 -3
- package/dist/themes/buildPalette.js +20 -20
- package/dist/themes/colorMath.d.ts +16 -16
- package/dist/themes/colorMath.js +32 -32
- package/dist/themes/data/ansi-tables.d.ts +2 -2
- package/dist/themes/data/ansi-tables.js +2 -2
- package/dist/themes/data/types.d.ts +4 -4
- package/dist/themes/data/types.js +2 -2
- package/dist/themes/palette.d.ts +12 -12
- package/dist/themes/palette.js +16 -16
- package/dist/themes/ramp.d.ts +17 -17
- package/dist/themes/ramp.js +12 -12
- package/dist/themes/registry.js +4 -4
- package/dist/themes/terminalThemes.d.ts +1 -1
- package/dist/themes/terminalThemes.js +1 -1
- package/dist/widgets/button.d.ts +2 -2
- package/dist/widgets/button.js +4 -4
- package/dist/widgets/checkbox.d.ts +2 -2
- package/dist/widgets/checkbox.js +2 -2
- package/dist/widgets/dropdown.d.ts +2 -2
- package/dist/widgets/dropdown.js +3 -3
- package/dist/widgets/event-router.d.ts +6 -6
- package/dist/widgets/event-router.js +2 -2
- package/dist/widgets/focus-manager.d.ts +3 -3
- package/dist/widgets/focus-manager.js +3 -3
- package/dist/widgets/index.d.ts +2 -2
- package/dist/widgets/index.js +1 -1
- package/dist/widgets/ink.d.ts +3 -3
- package/dist/widgets/ink.js +4 -4
- package/dist/widgets/slider.d.ts +2 -2
- package/dist/widgets/static-item.d.ts +2 -2
- package/dist/widgets/text-input.d.ts +2 -2
- package/dist/widgets/text-input.js +5 -5
- package/dist/widgets/themed-widget.js +1 -1
- package/dist/widgets/toggle.d.ts +3 -3
- package/dist/widgets/toggle.js +3 -3
- package/dist/widgets/types.d.ts +6 -6
- package/dist/widgets/types.js +2 -2
- package/dist/widgets/widget-app.d.ts +10 -10
- package/dist/widgets/widget-app.js +8 -8
- package/dist/widgets/widget-base.d.ts +2 -2
- package/package.json +1 -1
|
@@ -2,21 +2,21 @@
|
|
|
2
2
|
* The effect catalogue: shimmer, pulse, sparkle and wheel, which loop for as
|
|
3
3
|
* long as they are drawn, fade-in and dissolve-out, which run once and are
|
|
4
4
|
* done, a blink and a bold pulse, which move an attribute on a beat, and a
|
|
5
|
-
* gradient, which lays a
|
|
5
|
+
* gradient, which lays a color ramp across an element and may move it. Each is an `Effect` for `Effected` (`./effect.ts`); the defaults
|
|
6
6
|
* in `EFFECT_CURVES` are the ones signed off by eye in the effects-feel demo
|
|
7
7
|
* (`npm run effects-feel`), at 30 fps and at one frame a second, and
|
|
8
8
|
* `EFFECT_CATALOGUE` makes every one of them at those, by name, one way.
|
|
9
9
|
*
|
|
10
10
|
* Each effect is something a first-time observer already knows from life —
|
|
11
|
-
* a slow glow, sunlight moving on water, the
|
|
11
|
+
* a slow glow, sunlight moving on water, the colors of daylight, fireflies, ink
|
|
12
12
|
* blooming in water, mist lifting — because a motion the eye recognises
|
|
13
13
|
* reads as calm and alive, where a motion it has to learn reads as a widget.
|
|
14
14
|
* One effect here is not ambience but a signal, and keeps none of this on
|
|
15
|
-
* purpose: `steppedInk` steps its ink through
|
|
15
|
+
* purpose: `steppedInk` steps its ink through colors on a beat, the news
|
|
16
16
|
* that something is at its limit, and jumps from slot to slot where the
|
|
17
17
|
* loops hold still. Nor is `gradient` a light: it paints an ink rather than
|
|
18
18
|
* lighting it, so it may darken one, and a ramp scrolled across it jumps
|
|
19
|
-
* where the ramp wraps unless its ends are one
|
|
19
|
+
* where the ramp wraps unless its ends are one color. Of the list below it
|
|
20
20
|
* keeps the words. What makes the loops' motions familiar is what every
|
|
21
21
|
* curve of theirs keeps:
|
|
22
22
|
*
|
|
@@ -32,13 +32,13 @@
|
|
|
32
32
|
* filaments, fronts — never per-cell static.
|
|
33
33
|
* - Light only ever lights, and never takes the words away. Ink and the
|
|
34
34
|
* fill under it both move toward the light and never darken, each by its
|
|
35
|
-
* own share: the share a
|
|
35
|
+
* own share: the share a color can take while every cell it is drawn in
|
|
36
36
|
* still reads at its resting contrast or WCAG AA, whichever is lower. A
|
|
37
37
|
* cell with contrast to spare glows brightly; one with none barely moves.
|
|
38
38
|
* - Elements are apart. Each sits at its own `z` in the noise, so two
|
|
39
39
|
* elements under one effect do not move in lockstep.
|
|
40
40
|
*
|
|
41
|
-
*
|
|
41
|
+
* Color moves are in OKLCH, where equal steps look equal. A curve's `ease`
|
|
42
42
|
* maps its intensity in [0, 1] — how strongly the effect acts on a cell at a
|
|
43
43
|
* moment — onto its swing, for the loops and the transitions alike.
|
|
44
44
|
*
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
*/
|
|
53
53
|
import { Animation } from "../core/animation.js";
|
|
54
54
|
import { ColorDepth, ColorRgba, ColorSpec, contrastRatio, relativeLuminance } from "../core/color.js";
|
|
55
|
-
import { EASES,
|
|
55
|
+
import { EASES, WrapMode, stepSequence } from "../core/easing.js";
|
|
56
56
|
import { fbm, hash, noise, smoothstep } from "../core/noise.js";
|
|
57
57
|
import { Memo, WeakMemo } from "../core/memo.js";
|
|
58
58
|
import { Oklch } from "../core/oklch.js";
|
|
@@ -64,7 +64,7 @@ const LEGIBLE = 4.5;
|
|
|
64
64
|
/**
|
|
65
65
|
* The strengths a cell is drawn at: a field is snapped to one of these, and
|
|
66
66
|
* a share is checked at every one, so every strength drawn was checked. A
|
|
67
|
-
* 256-
|
|
67
|
+
* 256-color cube color changes in jumps between strengths, and no check of
|
|
68
68
|
* a continuous strength between two of them could see every jump. A step is
|
|
69
69
|
* a 48th of a swing, well under a just-noticeable difference.
|
|
70
70
|
*/
|
|
@@ -75,7 +75,7 @@ const ROUNDING = 0.01;
|
|
|
75
75
|
/**
|
|
76
76
|
* The contrast no loop draws `pair` below at `depth`: its resting contrast or
|
|
77
77
|
* `LEGIBLE`, whichever is lower, less what truecolor's rounding can cost.
|
|
78
|
-
* Below truecolor every
|
|
78
|
+
* Below truecolor every color written is one the depth holds exactly, so
|
|
79
79
|
* there is nothing to forgive. [LAW:one-source-of-truth] The floor `shares`
|
|
80
80
|
* settles the light against and `wheel` writes its turn above.
|
|
81
81
|
*/
|
|
@@ -87,33 +87,33 @@ const SIDES = ["fg", "bg"];
|
|
|
87
87
|
* How much of `touch` each of `colors` (hex) can take: the largest share in
|
|
88
88
|
* [0, 1] at which every pair `drawn` shows it in, touched at the same
|
|
89
89
|
* strength, still reads at its resting contrast or `LEGIBLE`, whichever is
|
|
90
|
-
* lower, in the
|
|
91
|
-
* A
|
|
90
|
+
* lower, in the colors `Effected` writes it in at `drawn`'s depth and terminal.
|
|
91
|
+
* A color in a pair but not in `colors` takes none, and so does one
|
|
92
92
|
* the terminal supplies, whatever its hex, as in `onColors`. One of `colors`
|
|
93
93
|
* no pair writes has nothing to be measured against, and takes it all.
|
|
94
94
|
*
|
|
95
|
-
*
|
|
95
|
+
* Colors are settled lightest first, each against every partner: one
|
|
96
96
|
* already settled at its share, one not yet settled as it is. So the lighter
|
|
97
|
-
*
|
|
97
|
+
* color of a cell takes the light first and its darker partner spends
|
|
98
98
|
* whatever contrast is left — pale lettering lifting off a dark fill gives
|
|
99
|
-
* the fill room to glow — and a darker
|
|
99
|
+
* the fill room to glow — and a darker color left as it is always holds,
|
|
100
100
|
* because its lighter partner was settled against exactly that.
|
|
101
101
|
*/
|
|
102
102
|
export function shares({ pairs, depth, terminal }, colors, touch) {
|
|
103
|
-
// [LAW:one-source-of-truth] A slot shows a subject
|
|
103
|
+
// [LAW:one-source-of-truth] A slot shows a subject color, by hex, only
|
|
104
104
|
// where the element writes it: the reading `onColors` lights by.
|
|
105
105
|
const own = (pair, side) => (pair.terminal[side] ? undefined : pair[side].hex);
|
|
106
106
|
const rgba = new Map(pairs.flatMap((pair) => SIDES.flatMap((side) => {
|
|
107
107
|
const hex = own(pair, side);
|
|
108
108
|
return hex === undefined ? [] : [[hex, pair[side]]];
|
|
109
109
|
})));
|
|
110
|
-
// A
|
|
111
|
-
// a frame drawing a lit
|
|
110
|
+
// A color no pair writes keeps no word legible, so nothing bounds its share:
|
|
111
|
+
// a frame drawing a lit color only under blanks and seams, or not at all,
|
|
112
112
|
// is a frame like any other.
|
|
113
113
|
const settled = new Map([...colors].filter((hex) => !rgba.has(hex)).map((hex) => [hex, 1]));
|
|
114
114
|
const lightness = (hex) => Oklch.fromRgba(rgba.get(hex)).l;
|
|
115
115
|
for (const hex of [...colors].filter((hex) => rgba.has(hex)).sort((a, b) => lightness(b) - lightness(a))) {
|
|
116
|
-
// Each pair showing the
|
|
116
|
+
// Each pair showing the color, with the side it shows it on and the share its partner takes.
|
|
117
117
|
const facing = pairs.flatMap((pair) => {
|
|
118
118
|
const mine = SIDES.find((side) => own(pair, side) === hex);
|
|
119
119
|
if (mine === undefined)
|
|
@@ -121,7 +121,7 @@ export function shares({ pairs, depth, terminal }, colors, touch) {
|
|
|
121
121
|
const partner = own(pair, mine === "fg" ? "bg" : "fg");
|
|
122
122
|
return [[pair, mine, partner === undefined ? 0 : (settled.get(partner) ?? 0)]];
|
|
123
123
|
});
|
|
124
|
-
// [LAW:one-source-of-truth] A pair reads in the
|
|
124
|
+
// [LAW:one-source-of-truth] A pair reads in the colors `Effected` writes it in, touched at each side's share.
|
|
125
125
|
const holds = (share) => facing.every(([pair, mine, theirs]) => {
|
|
126
126
|
const floor = floorOf(pair, depth);
|
|
127
127
|
const at = (side, w) => touchedAt(touch, pair[side], (side === mine ? share : theirs) * w);
|
|
@@ -140,19 +140,19 @@ export function shares({ pairs, depth, terminal }, colors, touch) {
|
|
|
140
140
|
return settled;
|
|
141
141
|
}
|
|
142
142
|
/**
|
|
143
|
-
* A loop as an `Effect` on the element's own
|
|
143
|
+
* A loop as an `Effect` on the element's own colors, by hex, wherever a cell
|
|
144
144
|
* shows them, each touched at its share of the cell's strength: the share
|
|
145
145
|
* `shares` settles for it against the pairs the element draws its words in
|
|
146
146
|
* (`Drawn`), touched as `Effected` writes them at the frame's depth — at 256
|
|
147
|
-
*
|
|
148
|
-
* contrast it spends is that
|
|
149
|
-
* moves (`
|
|
147
|
+
* colors a touched color lands on a color the slot can write, and the
|
|
148
|
+
* contrast it spends is that color's; on a sixteen-color terminal nothing
|
|
149
|
+
* moves (`staticWhereUnchanged`). `colors` are the ones lit, by default every color the element
|
|
150
150
|
* sets itself. A powerline strip's fill is the ground of its cells and the
|
|
151
151
|
* ink of the seam glyphs between them, so the arrow moves with the cell it
|
|
152
|
-
* points out of. A
|
|
152
|
+
* points out of. A color the terminal supplies (`Pair.terminal`) is
|
|
153
153
|
* not the element's, whatever its hex, and takes no share.
|
|
154
154
|
*
|
|
155
|
-
* Settling shares is costly, so it is done once for each set of
|
|
155
|
+
* Settling shares is costly, so it is done once for each set of colors an
|
|
156
156
|
* element draws: make the effect once and draw it in every frame, and a frame
|
|
157
157
|
* drawing what an earlier one drew costs no settling.
|
|
158
158
|
*/
|
|
@@ -164,7 +164,7 @@ export function onColors(loop, colors) {
|
|
|
164
164
|
const key = [drawnKey(drawn), "|", ...lit].join(" ");
|
|
165
165
|
return settled.get(drawn.terminal, () => new Memo()).get(key, () => shares(drawn, lit, loop.touch));
|
|
166
166
|
};
|
|
167
|
-
return
|
|
167
|
+
return staticWhereUnchanged(SIDES, litIn, atShares(loop, settle));
|
|
168
168
|
}
|
|
169
169
|
/** What a frame draws, as a key: its depth and its pairs, which its `colors` and `fills` are read off. */
|
|
170
170
|
function drawnKey({ depth, pairs }) {
|
|
@@ -182,10 +182,10 @@ function pairKey({ fg, bg, terminal, reverse }) {
|
|
|
182
182
|
return `${fg.hex}/${bg.hex}/${terminal.fg}/${terminal.bg}/${reverse}`;
|
|
183
183
|
}
|
|
184
184
|
/**
|
|
185
|
-
* A ramp laid across the element: each lit cell's ink is the
|
|
185
|
+
* A ramp laid across the element: each lit cell's ink is the color `ramp`
|
|
186
186
|
* shows at `t` at the cell's column, the element's leftmost cell reading
|
|
187
187
|
* the ramp's first stop and its rightmost its last, each at its cell's
|
|
188
|
-
* centre — or, where that
|
|
188
|
+
* centre — or, where that color would not read on the cell's ground, the
|
|
189
189
|
* nearest that does (`legibleInks`, as `steppedInk` writes its steps). A
|
|
190
190
|
* ramp that moves (`ColorRamp.moving`) scrolls along the element and turns
|
|
191
191
|
* its hues, and the frame changes when the ramp does — every frame for a
|
|
@@ -193,26 +193,26 @@ function pairKey({ fg, bg, terminal, reverse }) {
|
|
|
193
193
|
* `Animation.constant(ramp)`. Lit cells are those whose ink is one of
|
|
194
194
|
* `colors`, named as a style names them (`"#a6e3a1"`, `"bright_green"`),
|
|
195
195
|
* opaque, and matched as the screen shows them at the frame's depth — so
|
|
196
|
-
* below truecolor every ink that lands on the same palette
|
|
197
|
-
* by default every
|
|
198
|
-
* rule's line by its
|
|
196
|
+
* below truecolor every ink that lands on the same palette color is lit —
|
|
197
|
+
* by default every color the element sets itself — never an ink the terminal supplies: a
|
|
198
|
+
* rule's line by its color and not its title, a panel's border and not its
|
|
199
199
|
* body. The element is where its frame lists
|
|
200
200
|
* cells, as the fireflies read it, so a padded or confined element has the
|
|
201
|
-
* ramp across what it shows. On a sixteen-
|
|
202
|
-
* (`
|
|
201
|
+
* ramp across what it shows. On a sixteen-color terminal nothing moves
|
|
202
|
+
* (`staticWhereUnchanged`): a gradient there is jumps between slots, and the cells
|
|
203
203
|
* hold as drawn.
|
|
204
204
|
*/
|
|
205
205
|
export function gradient(ramp, colors) {
|
|
206
206
|
const legible = legibleInks();
|
|
207
|
-
// [LAW:parse-dont-validate] Parsed at the call, so a misspelt
|
|
207
|
+
// [LAW:parse-dont-validate] Parsed at the call, so a misspelt color fails
|
|
208
208
|
// where it was written and not on some later frame.
|
|
209
209
|
const named = colors?.map(opaqueInk);
|
|
210
|
-
// [LAW:one-source-of-truth] A
|
|
210
|
+
// [LAW:one-source-of-truth] A color named as a style spells it is lit
|
|
211
211
|
// wherever a cell shows it, so it is resolved as `Effected` resolves a
|
|
212
212
|
// cell's ink: drawn at the frame's depth, then read on its terminal.
|
|
213
213
|
const shown = (spec, { depth, terminal }) => (Style.fromColor(spec).drawnColors(depth).color ?? ColorSpec.default()).getTruecolor(terminal, true).hex;
|
|
214
214
|
const litIn = (drawn) => (named === undefined ? drawn.colors : new Set(named.map((spec) => shown(spec, drawn))));
|
|
215
|
-
return
|
|
215
|
+
return staticWhereUnchanged(["fg"], litIn, (drawn, lit) => {
|
|
216
216
|
const inkOn = legible(drawn);
|
|
217
217
|
const [left, right] = extent(drawn.cells().map((cell) => cell.col));
|
|
218
218
|
return Animation.map(ramp, (now) => {
|
|
@@ -226,18 +226,18 @@ export function gradient(ramp, colors) {
|
|
|
226
226
|
/**
|
|
227
227
|
* `spec` parsed as an ink `gradient` lights. Opaque only: a translucent ink
|
|
228
228
|
* is drawn over the ground of each cell it is in, so it shows as no one
|
|
229
|
-
*
|
|
229
|
+
* color to match, and naming one is refused here.
|
|
230
230
|
*/
|
|
231
231
|
function opaqueInk(spec) {
|
|
232
232
|
const parsed = ColorSpec.parse(spec);
|
|
233
233
|
if (parsed.value !== undefined && parsed.value.alpha < 1)
|
|
234
|
-
throw new RangeError(`gradient lights opaque
|
|
234
|
+
throw new RangeError(`gradient lights opaque colors, and ${spec} is translucent`);
|
|
235
235
|
return parsed;
|
|
236
236
|
}
|
|
237
237
|
/**
|
|
238
|
-
* A loop as an `Effect` on
|
|
238
|
+
* A loop as an `Effect` on colors by hex, each touched at the share
|
|
239
239
|
* `share` gives it of the cell's strength in a frame drawing what `Drawn`
|
|
240
|
-
* says; a
|
|
240
|
+
* says; a color it gives none, and one the terminal supplies, stays as it is.
|
|
241
241
|
*/
|
|
242
242
|
export function atShares(loop, share) {
|
|
243
243
|
return (drawn) => {
|
|
@@ -246,7 +246,7 @@ export function atShares(loop, share) {
|
|
|
246
246
|
const field = loop.field(t, drawn);
|
|
247
247
|
return (look, cell) => {
|
|
248
248
|
const w = Math.round(clamp01(field(cell)) * LEVELS) / LEVELS;
|
|
249
|
-
// [LAW:one-source-of-truth] A share belongs to the
|
|
249
|
+
// [LAW:one-source-of-truth] A share belongs to the color the element drew; the touch falls on what is there now.
|
|
250
250
|
const moved = (side) => touchedAt(loop.touch, look[side], (cell.pair.terminal[side] ? 0 : (shared.get(cell.pair[side].hex) ?? 0)) * w);
|
|
251
251
|
return { ...look, fg: moved("fg"), bg: moved("bg") };
|
|
252
252
|
};
|
|
@@ -254,27 +254,27 @@ export function atShares(loop, share) {
|
|
|
254
254
|
};
|
|
255
255
|
}
|
|
256
256
|
/**
|
|
257
|
-
* `loop` as drawn at the frame's depth and on the
|
|
257
|
+
* `loop` as drawn at the frame's depth and on the colors it moves on `sides`
|
|
258
258
|
* (`litIn`): where the depth writes only the
|
|
259
|
-
* sixteen ANSI slots, or no
|
|
259
|
+
* sixteen ANSI slots, or no color, a move that shows at all is a jump to
|
|
260
260
|
* another slot, and a light on an element never jumps — so there it
|
|
261
261
|
* holds every cell still, as it does where no cell that shows anything has
|
|
262
|
-
* one of those
|
|
262
|
+
* one of those colors of its own there, and anywhere else it moves. [LAW:single-enforcer]
|
|
263
263
|
* `onColors`, `wheel` and `gradient` draw through this, so every loop in the catalogue
|
|
264
|
-
* meets the sixteen-
|
|
264
|
+
* meets the sixteen-color terminal here. `atShares` does not: a progress
|
|
265
265
|
* bar's pulse is the news that work is going on, and flashes a slot as
|
|
266
266
|
* Rich's does; nor does `steppedInk`, a signal, which steps slot to slot.
|
|
267
267
|
* A frame held still never changes, and says so, so a host drawing the
|
|
268
268
|
* loop on such a terminal draws it once.
|
|
269
269
|
*/
|
|
270
|
-
function
|
|
270
|
+
function staticWhereUnchanged(sides, litIn, loop) {
|
|
271
271
|
return (drawn) => {
|
|
272
272
|
const lit = litIn(drawn);
|
|
273
273
|
const shows = ({ pair }) => sides.some((side) => !pair.terminal[side] && lit.has(pair[side].hex));
|
|
274
274
|
return onlySlots(drawn.depth) || !drawn.cells().some(shows) ? Animation.constant((look) => look) : loop(drawn, lit);
|
|
275
275
|
};
|
|
276
276
|
}
|
|
277
|
-
/** Whether `depth` writes nothing between the slots it has: the sixteen, at either sixteen-
|
|
277
|
+
/** Whether `depth` writes nothing between the slots it has: the sixteen, at either sixteen-color depth, or none. */
|
|
278
278
|
function onlySlots(depth) {
|
|
279
279
|
switch (depth) {
|
|
280
280
|
case ColorDepth.DEFAULT:
|
|
@@ -287,8 +287,8 @@ function onlySlots(depth) {
|
|
|
287
287
|
}
|
|
288
288
|
}
|
|
289
289
|
/**
|
|
290
|
-
* `color` under `touch` at strength `w`, and at strength 0 the
|
|
291
|
-
* exactly: an OKLCH round trip can land a step off, and at 16
|
|
290
|
+
* `color` under `touch` at strength `w`, and at strength 0 the color itself,
|
|
291
|
+
* exactly: an OKLCH round trip can land a step off, and at 16 colors a step
|
|
292
292
|
* off is another slot.
|
|
293
293
|
*/
|
|
294
294
|
function touchedAt(touch, color, w) {
|
|
@@ -313,7 +313,7 @@ export function requireZ(what, z) {
|
|
|
313
313
|
/**
|
|
314
314
|
* Each effect's curve as it was signed off. Every loop's period is a prime
|
|
315
315
|
* number of seconds, so no two loops come back into step within a sitting.
|
|
316
|
-
* The periods are long on purpose: at one frame a second a loop's
|
|
316
|
+
* The periods are long on purpose: at one frame a second a loop's color
|
|
317
317
|
* moves by no more than a just-noticeable step from one frame to the next.
|
|
318
318
|
*/
|
|
319
319
|
export const EFFECT_CURVES = {
|
|
@@ -334,8 +334,8 @@ export const SHIMMER_WIDTH = 24;
|
|
|
334
334
|
const clamp01 = (x) => Math.min(1, Math.max(0, x));
|
|
335
335
|
/**
|
|
336
336
|
* `w` of the way from `from` to `to` along a straight line in OKLab. Light
|
|
337
|
-
* falling on a
|
|
338
|
-
* toward the other
|
|
337
|
+
* falling on a color, or a color thinning into its ground, moves straight
|
|
338
|
+
* toward the other color; `Oklch.mix` goes round the hue circle instead, so
|
|
339
339
|
* blue lit by gold would pass through cyan on the way.
|
|
340
340
|
*/
|
|
341
341
|
function blend(from, to, w) {
|
|
@@ -347,9 +347,9 @@ function blend(from, to, w) {
|
|
|
347
347
|
return new Oklch((1 - w) * from.l + w * to.l, Math.hypot(a, b), Math.atan2(b, a) / rad, from.alpha);
|
|
348
348
|
}
|
|
349
349
|
/**
|
|
350
|
-
* A
|
|
351
|
-
* the way toward the light's
|
|
352
|
-
* light can warm a
|
|
350
|
+
* A color under `glow`: the one way every light here falls. It moves `w` of
|
|
351
|
+
* the way toward the light's color, and never below its own lightness —
|
|
352
|
+
* light can warm a color and lift it, never darken it. A color already
|
|
353
353
|
* lighter than the light only warms, and white, which has no warmer shade at
|
|
354
354
|
* its lightness, stays white.
|
|
355
355
|
*/
|
|
@@ -399,11 +399,11 @@ function given(shape, s, k) {
|
|
|
399
399
|
/** A firefly's brightest flash, in turns: it kindles quicker than it fades, and the rest of its turn is dark. */
|
|
400
400
|
const FLASH = [[0.24, 1], [0.6, 0]];
|
|
401
401
|
/**
|
|
402
|
-
* Pulse, as a gentle glow on one element of a screen: the
|
|
402
|
+
* Pulse, as a gentle glow on one element of a screen: the colors handed to
|
|
403
403
|
* it, an element's own, warm into `light` and settle back, over and over, the
|
|
404
404
|
* way an indicator breathes while something is waiting. Swing: how far into
|
|
405
405
|
* the light it goes at the top of a beat, 0–1. The element glows as one —
|
|
406
|
-
* every cell it is drawn in at once — and the
|
|
406
|
+
* every cell it is drawn in at once — and the colors not handed to it, the
|
|
407
407
|
* rest of the screen, stay as they are, so what pulses is what was chosen.
|
|
408
408
|
* Each element keeps a time of its own, a little quicker or slower than
|
|
409
409
|
* another's by `z`, and no beat is quite the last: the rhythm wanders ahead
|
|
@@ -428,8 +428,8 @@ export function pulse(curve, glow, z) {
|
|
|
428
428
|
// Each beat's own depth, 70% to the whole of the swing; it changes where
|
|
429
429
|
// the beat is at rest, so nothing jumps.
|
|
430
430
|
const depth = 0.7 + 0.3 * smoothstep(-0.5, 0.5, noise(n * 0.37, 2.1, z));
|
|
431
|
-
const
|
|
432
|
-
const level = curve.swing * curve.ease(depth *
|
|
431
|
+
const stepSequence = 0.5 - 0.5 * Math.cos(2 * Math.PI * (beats - n));
|
|
432
|
+
const level = curve.swing * curve.ease(depth * stepSequence);
|
|
433
433
|
return () => level;
|
|
434
434
|
};
|
|
435
435
|
return { touch: light(glow), field };
|
|
@@ -437,7 +437,7 @@ export function pulse(curve, glow, z) {
|
|
|
437
437
|
/**
|
|
438
438
|
* How many columns a passing light crosses in its period: its pace.
|
|
439
439
|
* Sunlight moves at a speed, not in a time set by how long the row is,
|
|
440
|
-
* so a wider element is crossed for longer, never faster, and no cell's
|
|
440
|
+
* so a wider element is crossed for longer, never faster, and no cell's color
|
|
441
441
|
* changes quicker on it from one frame to the next.
|
|
442
442
|
*/
|
|
443
443
|
const STRIDE = 80;
|
|
@@ -628,11 +628,11 @@ export function sparkle(curve, glow, z) {
|
|
|
628
628
|
* A cell mixed toward the ground of the terminal the frame is drawn on
|
|
629
629
|
* (`Drawn.terminal`) by how hidden it is: its fill toward the ground, and its ink
|
|
630
630
|
* toward that fill, so an element with fills of its own — a powerline strip —
|
|
631
|
-
* arrives and leaves whole, fill and words together, never as bare
|
|
631
|
+
* arrives and leaves whole, fill and words together, never as bare colored
|
|
632
632
|
* blocks. Invisible is ink and fill equal to the ground. `swing` scales the
|
|
633
633
|
* hiding — 1 reaches fully invisible. A cell wholly shown is returned as it was.
|
|
634
634
|
*/
|
|
635
|
-
export function
|
|
635
|
+
export function mask(visibility, swing) {
|
|
636
636
|
return (drawn) => {
|
|
637
637
|
// [LAW:one-source-of-truth] The ground is the terminal `Effected` draws on, never a second one handed here.
|
|
638
638
|
const ground = Oklch.fromRgba(drawn.terminal.backgroundColor);
|
|
@@ -669,8 +669,8 @@ function order(z) {
|
|
|
669
669
|
* `schedule` is when its frame next changes on that clock, whatever the
|
|
670
670
|
* element draws: what a caller drawing the transition inside an animation —
|
|
671
671
|
* a lifecycle — says its frame does, since the frame it hands over carries
|
|
672
|
-
* no word of its own (`
|
|
673
|
-
* every frame, or on each hold of a beat (`
|
|
672
|
+
* no word of its own (`stampValidityFrame`, `../core/animation.ts`). Until done it changes
|
|
673
|
+
* every frame, or on each hold of a beat (`sampleAndHold`), and from then never.
|
|
674
674
|
* [LAW:one-source-of-truth] `done` is that schedule read as a fact — done
|
|
675
675
|
* from the first moment it never changes again — so no transition carries
|
|
676
676
|
* two answers to when it is over.
|
|
@@ -689,7 +689,7 @@ export class Transition {
|
|
|
689
689
|
/**
|
|
690
690
|
* `effect` run once over `seconds` as a `Transition`: what it draws until it
|
|
691
691
|
* is done, then held. [LAW:single-enforcer] The one rule for when a
|
|
692
|
-
* transition is done and what it answers after, for a
|
|
692
|
+
* transition is done and what it answers after, for a color effect and a
|
|
693
693
|
* glyph effect (`./glyph-effects.ts`) alike.
|
|
694
694
|
*/
|
|
695
695
|
export function transition(seconds, effect) {
|
|
@@ -704,21 +704,21 @@ export function transition(seconds, effect) {
|
|
|
704
704
|
const moving = effect(drawn);
|
|
705
705
|
return Animation.of({
|
|
706
706
|
at: (t) => moving.at(t),
|
|
707
|
-
|
|
707
|
+
validUntil: (t) => (ran.done(t) ? Number.POSITIVE_INFINITY : moving.validUntil(t)),
|
|
708
708
|
// Done, the frame settled when the transition did; until then it is the effect's.
|
|
709
|
-
|
|
709
|
+
validFrom: (t) => (ran.done(t) ? schedule.validFrom(t) : moving.validFrom(t)),
|
|
710
710
|
// A one-shot, reduced, cuts from its start to its end, whatever the effect it runs would hold as a loop.
|
|
711
711
|
reduced: () => Animation.cut(moving.at(0), moving.at(seconds)),
|
|
712
712
|
});
|
|
713
713
|
}, schedule);
|
|
714
714
|
return ran;
|
|
715
715
|
}
|
|
716
|
-
export function
|
|
716
|
+
export function sampleAndHold(stepped, stepSequence) {
|
|
717
717
|
// [LAW:types-are-the-program] An effect is a function and a transition an object: the representation is the discriminator.
|
|
718
718
|
if (typeof stepped === "function")
|
|
719
|
-
return (drawn) => Animation.
|
|
719
|
+
return (drawn) => Animation.sampleAndHold(stepped(drawn), stepSequence);
|
|
720
720
|
const { effect, schedule } = stepped;
|
|
721
|
-
return new Transition((drawn) => Animation.
|
|
721
|
+
return new Transition((drawn) => Animation.sampleAndHold(effect(drawn), stepSequence), Animation.sampleAndHold(schedule, stepSequence));
|
|
722
722
|
}
|
|
723
723
|
/**
|
|
724
724
|
* Of a transition's `seconds`, how long each cell's own change takes. Under
|
|
@@ -749,7 +749,7 @@ function ownChange(curve, place) {
|
|
|
749
749
|
const [first, last] = extent(cells.map(place));
|
|
750
750
|
const stagger = (last > first ? 1 - OWN : 0) * curve.seconds;
|
|
751
751
|
// One phase a frame, shifted per cell, so no cell builds its own.
|
|
752
|
-
const change =
|
|
752
|
+
const change = WrapMode.once(0, curve.seconds - stagger);
|
|
753
753
|
// A share of the way from the first place to the last, held to the order for a cell the frame does not list.
|
|
754
754
|
return (cell) => {
|
|
755
755
|
const along = last > first ? clamp01((place(cell) - first) / (last - first)) : 0;
|
|
@@ -776,7 +776,7 @@ export function fadeIn(curve, z) {
|
|
|
776
776
|
requireCurve(curve);
|
|
777
777
|
requireZ("fadeIn", z);
|
|
778
778
|
const risen = ownChange(curve, order(1.7 + z));
|
|
779
|
-
return transition(curve.seconds,
|
|
779
|
+
return transition(curve.seconds, mask((t, drawn) => {
|
|
780
780
|
const of = risen(t, drawn.cells());
|
|
781
781
|
return (cell) => curve.ease(of(cell).progress);
|
|
782
782
|
}, curve.swing));
|
|
@@ -805,7 +805,7 @@ export function dissolveOut(curve, z) {
|
|
|
805
805
|
const eddy = order(9.3 + z);
|
|
806
806
|
const faded = ownChange(curve, order(4.2 + z));
|
|
807
807
|
const { depth, from, to, maskFrom, maskTo, late } = DISSOLVE_SHAPE;
|
|
808
|
-
return transition(curve.seconds,
|
|
808
|
+
return transition(curve.seconds, mask((t, drawn) => {
|
|
809
809
|
const of = faded(t, drawn.cells());
|
|
810
810
|
return (cell) => {
|
|
811
811
|
const { along, progress } = of(cell);
|
|
@@ -837,22 +837,22 @@ const STRAY_SPREAD = 24;
|
|
|
837
837
|
/** `cell` as a place of its own, as wide as its glyph: where no region covers it. */
|
|
838
838
|
const ownPlace = (cell, width) => ({ row: cell.row, centre: cell.col + width / 2 });
|
|
839
839
|
/**
|
|
840
|
-
* The segments `drawn` draws, as where each
|
|
840
|
+
* The segments `drawn` draws, as where each color of each cell turns: a
|
|
841
841
|
* segment is a region of adjacent cells — beside or above one another — of
|
|
842
|
-
* one
|
|
842
|
+
* one color of the element's own, each cell carrying it as its ground or,
|
|
843
843
|
* on the terminal's ground, as its ink, read off `Drawn.all` once a frame.
|
|
844
844
|
* So a panel's ground is one segment whatever words lie on it, a blank
|
|
845
845
|
* carrying the ink keeps a styled paragraph one segment across its spaces
|
|
846
|
-
* and lines, a caption in a bar's
|
|
846
|
+
* and lines, a caption in a bar's color written under the bar turns with
|
|
847
847
|
* it, and a blank in the terminal's ink splits a segment. A seam a joiner
|
|
848
|
-
* drew is in none, since its
|
|
849
|
-
* of a seam cell turns with the region of that
|
|
848
|
+
* drew is in none, since its colors are its neighbours' edges: each color
|
|
849
|
+
* of a seam cell turns with the region of that color past the seam on
|
|
850
850
|
* either side — a two-cell rail reads past its other cell — so a powerline
|
|
851
|
-
* cap keeps the
|
|
851
|
+
* cap keeps the color of the cell it caps, a custom joiner's handed cap
|
|
852
852
|
* included, and with the cell's own place where none is beside it — a
|
|
853
853
|
* neighbour's edge the frame does not show, handed at the seam's cell. Any
|
|
854
|
-
* other cell's
|
|
855
|
-
* neighbour's fill
|
|
854
|
+
* other cell's colors turn with the region covering it: a letter in a
|
|
855
|
+
* neighbour's fill color is a word, not a cap. A cell the frame lists
|
|
856
856
|
* nowhere, which a hand-built `Drawn` hands, is one column wide. The place
|
|
857
857
|
* is the region's centre column from the element's left edge, with its
|
|
858
858
|
* centre row: a region that grows by a column moves its centre by half of
|
|
@@ -864,10 +864,10 @@ function segmentsOf(drawn) {
|
|
|
864
864
|
const { colors } = drawn;
|
|
865
865
|
const at = (row, col) => `${row}:${col}`;
|
|
866
866
|
const listed = new Map();
|
|
867
|
-
// Every column a seam cell covers: what a seam's
|
|
867
|
+
// Every column a seam cell covers: what a seam's colors look past to the region beside the seam.
|
|
868
868
|
const seams = new Set();
|
|
869
|
-
// The
|
|
870
|
-
const
|
|
869
|
+
// The color a cell is a region of: its own ground, else on the terminal's ground its own ink, else none.
|
|
870
|
+
const regionColor = (cell) => {
|
|
871
871
|
if (cell.seam)
|
|
872
872
|
return undefined;
|
|
873
873
|
const own = (terminal, hex) => (!terminal && colors.has(hex) ? hex : undefined);
|
|
@@ -878,7 +878,7 @@ function segmentsOf(drawn) {
|
|
|
878
878
|
const index = new Map();
|
|
879
879
|
for (const cell of drawn.all()) {
|
|
880
880
|
listed.set(at(cell.row, cell.col), cell);
|
|
881
|
-
const hex =
|
|
881
|
+
const hex = regionColor(cell);
|
|
882
882
|
for (let col = cell.col; col < cell.col + cell.width; col++) {
|
|
883
883
|
if (cell.seam)
|
|
884
884
|
seams.add(at(cell.row, col));
|
|
@@ -888,7 +888,7 @@ function segmentsOf(drawn) {
|
|
|
888
888
|
covered.push({ row: cell.row, col, hex });
|
|
889
889
|
}
|
|
890
890
|
}
|
|
891
|
-
// Union-find over the covered columns: two of one
|
|
891
|
+
// Union-find over the covered columns: two of one color beside or above one another are one region.
|
|
892
892
|
const parent = covered.map((_, i) => i);
|
|
893
893
|
const find = (i) => {
|
|
894
894
|
while (parent[i] !== i) {
|
|
@@ -920,7 +920,7 @@ function segmentsOf(drawn) {
|
|
|
920
920
|
return i === undefined ? undefined : regions.get(find(i));
|
|
921
921
|
};
|
|
922
922
|
const placeOf = (region) => ({ row: (region.top + region.bottom) / 2, centre: (region.left + region.right + 1) / 2 });
|
|
923
|
-
// The region past the seam from `col` in `step`'s direction: what a seam's
|
|
923
|
+
// The region past the seam from `col` in `step`'s direction: what a seam's color turns with.
|
|
924
924
|
const pastSeam = (row, col, step) => {
|
|
925
925
|
let past = col;
|
|
926
926
|
while (seams.has(at(row, past)))
|
|
@@ -936,17 +936,17 @@ function segmentsOf(drawn) {
|
|
|
936
936
|
};
|
|
937
937
|
}
|
|
938
938
|
/**
|
|
939
|
-
*
|
|
939
|
+
* WheelEvent, as the color of daylight going round: every hue turning the whole
|
|
940
940
|
* way round once a period — far too slowly to be seen moving, so a glance
|
|
941
941
|
* sees a steady palette and an hour's absence a different one — each
|
|
942
942
|
* segment of the element at its own pace. Swing: how far the segments stray
|
|
943
943
|
* from one another, 0–1, 1 being half a turn apart. A segment is a region
|
|
944
|
-
* of adjacent cells of one
|
|
944
|
+
* of adjacent cells of one color of the element's own, carried as their
|
|
945
945
|
* ground or, on the terminal's ground, as their ink (`segmentsOf`), and it
|
|
946
|
-
* paces by where it sits, not by its
|
|
947
|
-
* regions of one fill at two places turn apart, and each
|
|
948
|
-
* a joiner drew between them turns with the region of that
|
|
949
|
-
* it, so a powerline cap keeps the
|
|
946
|
+
* paces by where it sits, not by its color: two
|
|
947
|
+
* regions of one fill at two places turn apart, and each color of the seam
|
|
948
|
+
* a joiner drew between them turns with the region of that color beside
|
|
949
|
+
* it, so a powerline cap keeps the color of the cell it caps. An ink that
|
|
950
950
|
* is no fill turns with the ground it sits on, so a cell's words and fill
|
|
951
951
|
* turn together. A segment's stray is slow noise at its place, at
|
|
952
952
|
* `STRAY_PERIODS` in time and `STRAY_SPREAD` across the element, so
|
|
@@ -955,13 +955,13 @@ function segmentsOf(drawn) {
|
|
|
955
955
|
* column moves its hue a little, and those it pushes along as little. Only the
|
|
956
956
|
* stray reads the place: a place in the shared turn would jump a growing
|
|
957
957
|
* run by `360·t·Δ/P`, without bound in `t`.
|
|
958
|
-
* A
|
|
959
|
-
* words reads at the contrast it rested at; at 256
|
|
960
|
-
* lands on a
|
|
958
|
+
* A color keeps its luminance as it turns (`hueTurned`), so every pair of
|
|
959
|
+
* words reads at the contrast it rested at; at 256 colors a turned color
|
|
960
|
+
* lands on a color the depth writes, one that keeps every pair of words it
|
|
961
961
|
* is drawn in at its floor (`floorOf`) — its resting contrast or WCAG AA,
|
|
962
962
|
* whichever is lower — as `shares` keeps the light's (`turnedBounds`). The
|
|
963
|
-
* turn only happens to the element's own
|
|
964
|
-
* terminal's. On a sixteen-
|
|
963
|
+
* turn only happens to the element's own colors (`Drawn.colors`), never the
|
|
964
|
+
* terminal's. On a sixteen-color terminal nothing turns (`staticWhereUnchanged`).
|
|
965
965
|
*/
|
|
966
966
|
export function wheel(curve, z) {
|
|
967
967
|
requireCurve(curve);
|
|
@@ -978,35 +978,35 @@ export function wheel(curve, z) {
|
|
|
978
978
|
// Measured from where it stood at the start, as pulse's wander is, so the
|
|
979
979
|
// first frame turns nothing and the second has turned only a frame's worth.
|
|
980
980
|
const turned = (place, t) => (360 * t) / P + curve.swing * STRAY * ((stray(place, t) - stray(place, 0)) / 1.5);
|
|
981
|
-
// A turned
|
|
981
|
+
// A turned color's bounds rest on what a frame draws, never on `t`: settled once for each.
|
|
982
982
|
const settled = new WeakMemo();
|
|
983
|
-
return
|
|
983
|
+
return staticWhereUnchanged(SIDES, ({ colors }) => colors, (drawn) => {
|
|
984
984
|
const { colors, fills, depth, terminal } = drawn;
|
|
985
|
-
// Whether a slot shows one of the element's
|
|
985
|
+
// Whether a slot shows one of the element's colors: never the terminal's, whatever its hex.
|
|
986
986
|
const own = (supplied, color) => !supplied && colors.has(color.hex);
|
|
987
|
-
// The
|
|
988
|
-
// kept: itself if a fill or beside no own
|
|
987
|
+
// The color an own color is bounded with, under which its bounds are
|
|
988
|
+
// kept: itself if a fill or beside no own color, else the ground beside
|
|
989
989
|
// it. Where it turns is `segmentsOf`'s, not this.
|
|
990
990
|
const boundWith = (color, other, theirs) => (fills.has(color.hex) || !theirs ? color.hex : other.hex);
|
|
991
|
-
// The bounds a
|
|
991
|
+
// The bounds a color turns within, by the color it is bounded with: one bound for every place a frame writes it at.
|
|
992
992
|
const keyOf = (color, bound) => `${color.hex} ${bound}`;
|
|
993
993
|
const bounds = settled.get(terminal, () => new Memo()).get(drawnKey(drawn), () => turnedBounds(drawn.pairs, depth, (pair, side) => {
|
|
994
994
|
const other = side === "fg" ? "bg" : "fg";
|
|
995
995
|
return own(pair.terminal[side], pair[side]) ? keyOf(pair[side], boundWith(pair[side], pair[other], own(pair.terminal[other], pair[other]))) : undefined;
|
|
996
996
|
}));
|
|
997
|
-
// 256
|
|
997
|
+
// 256 colors writes a turned color as one it has an index for; truecolor
|
|
998
998
|
// as one of the two either side of its luminance.
|
|
999
999
|
const indexed = measuredIndexed(depth, terminal);
|
|
1000
|
-
// Where each cell's
|
|
1000
|
+
// Where each cell's colors turn, read off the frame's listing once.
|
|
1001
1001
|
const placeOf = segmentsOf(drawn);
|
|
1002
1002
|
return Animation.continuous((t) => {
|
|
1003
|
-
// [LAW:one-source-of-truth] A
|
|
1003
|
+
// [LAW:one-source-of-truth] A color turned at a place within a
|
|
1004
1004
|
// segment's bounds is written once a frame, wherever it falls: the
|
|
1005
1005
|
// key is the bounds' key with the place, so one entry never serves
|
|
1006
1006
|
// two bounds.
|
|
1007
1007
|
const written = new Map();
|
|
1008
|
-
// The bounds are measured from the
|
|
1009
|
-
// beneath may have lit it, so they travel with it, and a lit
|
|
1008
|
+
// The bounds are measured from the color the element drew; a layer
|
|
1009
|
+
// beneath may have lit it, so they travel with it, and a lit color
|
|
1010
1010
|
// turns round its own luminance rather than snapping back unturned.
|
|
1011
1011
|
const choose = (color, rested, bound, place) => {
|
|
1012
1012
|
const turn = hueTurned(color, turned(place, t));
|
|
@@ -1032,12 +1032,12 @@ export function wheel(curve, z) {
|
|
|
1032
1032
|
/**
|
|
1033
1033
|
* `color` turned `degrees` round the hue circle at its own relative
|
|
1034
1034
|
* luminance, and at its own chroma where the gamut has it. WCAG contrast is
|
|
1035
|
-
* a ratio of luminances, so a pair of
|
|
1035
|
+
* a ratio of luminances, so a pair of colors each turned this way reads at
|
|
1036
1036
|
* the contrast it did; one turned at its OKLCH lightness instead does not,
|
|
1037
1037
|
* since equal lightness is not equal luminance across hues. The lightness
|
|
1038
1038
|
* is found by bisection, as `ensureContrastToward` finds one, and 8-bit
|
|
1039
|
-
* rounding lands the luminance between two
|
|
1040
|
-
* degrees the
|
|
1039
|
+
* rounding lands the luminance between two colors, so both are kept. At 0
|
|
1040
|
+
* degrees the color itself, exactly: an OKLCH round trip can land a step
|
|
1041
1041
|
* off (`touchedAt`).
|
|
1042
1042
|
*/
|
|
1043
1043
|
function hueTurned(color, degrees) {
|
|
@@ -1048,7 +1048,7 @@ function greyOf(color) {
|
|
|
1048
1048
|
const { l, h, alpha } = Oklch.fromRgba(color);
|
|
1049
1049
|
return new Oklch(l, 0, h, alpha).toRgba();
|
|
1050
1050
|
}
|
|
1051
|
-
/** `color` turned `degrees` round the hue circle and brought to `luminance`: the two 8-bit
|
|
1051
|
+
/** `color` turned `degrees` round the hue circle and brought to `luminance`: the two 8-bit colors either side of it. */
|
|
1052
1052
|
function atLuminance(color, degrees, luminance) {
|
|
1053
1053
|
const from = Oklch.fromRgba(color);
|
|
1054
1054
|
const at = (l) => new Oklch(l, from.c, from.h + degrees, from.alpha).toRgba();
|
|
@@ -1060,34 +1060,34 @@ function atLuminance(color, degrees, luminance) {
|
|
|
1060
1060
|
return { looks: at(bright), either: [at(dim), at(bright)] };
|
|
1061
1061
|
}
|
|
1062
1062
|
/**
|
|
1063
|
-
* Where a
|
|
1064
|
-
* black. Two
|
|
1065
|
-
* stops, so a contrast to keep is a distance a
|
|
1063
|
+
* Where a color sits on WCAG's contrast scale: the log of its contrast with
|
|
1064
|
+
* black. Two colors' contrast ratio is `exp` of the gap between their
|
|
1065
|
+
* stops, so a contrast to keep is a distance a color may move.
|
|
1066
1066
|
*/
|
|
1067
1067
|
const stop = (color) => Math.log(contrastRatio(color, new ColorRgba(0, 0, 0)));
|
|
1068
1068
|
const UNBOUNDED = { lo: -Infinity, hi: Infinity };
|
|
1069
|
-
/** Bounds that admit no turn: the
|
|
1069
|
+
/** Bounds that admit no turn: the color is written as it rests. */
|
|
1070
1070
|
const STILL = { lo: Infinity, hi: -Infinity };
|
|
1071
1071
|
/**
|
|
1072
|
-
* The widest 8-bit rounding parts `hueTurned`'s two
|
|
1072
|
+
* The widest 8-bit rounding parts `hueTurned`'s two colors, in stops. They
|
|
1073
1073
|
* are one point of the hue rounded two ways, so they differ by at most a
|
|
1074
|
-
* step in each channel, and a channel of a
|
|
1074
|
+
* step in each channel, and a channel of a color that dark or light can
|
|
1075
1075
|
* take a step only so large: worst near a luminance of 0.05, at 3.03% of
|
|
1076
1076
|
* contrast. Bounds at least this wide admit one of the two whatever the
|
|
1077
|
-
* hue; narrower, a
|
|
1077
|
+
* hue; narrower, a color would turn at some hues and snap back to rest at
|
|
1078
1078
|
* the next, so it holds still.
|
|
1079
1079
|
*/
|
|
1080
1080
|
const ROUNDING_GAP = Math.log(1.031);
|
|
1081
1081
|
/**
|
|
1082
|
-
* How far each turned
|
|
1082
|
+
* How far each turned color's luminance may land from the one it rests at,
|
|
1083
1083
|
* keyed as `turnedAs` keys each side of a pair — none for a side that holds
|
|
1084
1084
|
* still — so that every pair of `pairs` reads at its floor (`floorOf`) at
|
|
1085
|
-
* `depth` however each of its
|
|
1085
|
+
* `depth` however each of its colors lands within its bounds. Bounds
|
|
1086
1086
|
* narrower than `ROUNDING_GAP` are `STILL`.
|
|
1087
1087
|
*
|
|
1088
1088
|
* A pair's contrast to spare above its floor, as a gap between stops, is
|
|
1089
1089
|
* shared out evenly between its sides that turn: the lighter may land that
|
|
1090
|
-
* much darker, the darker that much lighter, and never further. So a
|
|
1090
|
+
* much darker, the darker that much lighter, and never further. So a color
|
|
1091
1091
|
* bounded once for every pair it is drawn in is bounded by each of them at
|
|
1092
1092
|
* once, and every way a frame writes it — the wheel turns it at each place
|
|
1093
1093
|
* it falls — lands within them. A pair whose floor is 1:1 holds however it
|
|
@@ -1110,13 +1110,13 @@ function turnedBounds(pairs, depth, turnedAs) {
|
|
|
1110
1110
|
return new Map([...bounds].map(([key, { lo, hi }]) => [key, hi - lo < ROUNDING_GAP ? STILL : { lo, hi }]));
|
|
1111
1111
|
}
|
|
1112
1112
|
const measured = (color) => ({ color, looks: Oklch.fromRgba(color), stop: stop(color) });
|
|
1113
|
-
/** `ColorSpec.indexed`'s
|
|
1113
|
+
/** `ColorSpec.indexed`'s colors, measured once for each terminal and depth. */
|
|
1114
1114
|
const measuredMemo = new WeakMemo();
|
|
1115
1115
|
function measuredIndexed(depth, terminal) {
|
|
1116
1116
|
return measuredMemo.get(ColorSpec.indexed(depth, terminal), (indexed) => indexed.map(([, color]) => measured(color)));
|
|
1117
1117
|
}
|
|
1118
1118
|
/**
|
|
1119
|
-
* The
|
|
1119
|
+
* The color that looks nearest `color` (OKLab's ΔE): `rest`, or one of
|
|
1120
1120
|
* `candidates` whose luminance is within `bounds`. `rest` is what the bounds
|
|
1121
1121
|
* are measured from, so it is always offered; ties go to it, then to the
|
|
1122
1122
|
* earlier.
|
|
@@ -1138,9 +1138,9 @@ function nearestOf(color, offered) {
|
|
|
1138
1138
|
/** What a stop's float arithmetic can cost: a pair at its floor sits exactly on its bound. */
|
|
1139
1139
|
const MEASURE = 1e-9;
|
|
1140
1140
|
/**
|
|
1141
|
-
* The stops a
|
|
1141
|
+
* The stops a color written at `depth` can land on: everything between black
|
|
1142
1142
|
* and white in truecolor, and otherwise what the depth has an index for, so a
|
|
1143
|
-
* sixteen-
|
|
1143
|
+
* sixteen-color terminal's reach is its slots.
|
|
1144
1144
|
*/
|
|
1145
1145
|
function reachOf(depth, terminal) {
|
|
1146
1146
|
return depth === ColorDepth.TRUECOLOR ? [0, Math.log(21)] : extent(measuredIndexed(depth, terminal).map((candidate) => candidate.stop));
|
|
@@ -1150,7 +1150,7 @@ function reachOf(depth, terminal) {
|
|
|
1150
1150
|
* room to read on the side of it the ink rested on: a light ink caps its ground
|
|
1151
1151
|
* at the floor below the lightest the depth writes, a dark one bars it from the
|
|
1152
1152
|
* floor above the darkest. Where a ground rests is always within: a depth also
|
|
1153
|
-
* writes the terminal's own
|
|
1153
|
+
* writes the terminal's own color, which at sixteen colors lies past its
|
|
1154
1154
|
* terminal's reach, and a ground no layer moved comes back as it was. These are
|
|
1155
1155
|
* what the depth's reach leaves, so a ground any one effect moves is inside them.
|
|
1156
1156
|
*/
|
|
@@ -1168,15 +1168,15 @@ function groundBounds(pairs, depth, terminal) {
|
|
|
1168
1168
|
* Several effects on one element, as one effect: each layer is handed the look
|
|
1169
1169
|
* the one before it left and the same cell, so later layers are on top, and each
|
|
1170
1170
|
* recognises a cell by what the element drew (`EffectCell.pair`), never by the
|
|
1171
|
-
*
|
|
1172
|
-
*
|
|
1171
|
+
* color it is handed. A hue a layer turned onto another of the element's
|
|
1172
|
+
* colors is not mistaken for it, and turns once. It changes when the soonest
|
|
1173
1173
|
* layer does, and with no layers it leaves every cell as drawn.
|
|
1174
1174
|
*
|
|
1175
1175
|
* Every word reads at its floor (`floorOf`), whatever the layers did and in
|
|
1176
|
-
* whatever order. Each loop proves that for
|
|
1177
|
-
* and a
|
|
1176
|
+
* whatever order. Each loop proves that for colors as the element drew them,
|
|
1177
|
+
* and a color another layer moved voids the proof: light lands at another
|
|
1178
1178
|
* luminance on a turned hue. So once the layers have moved a cell, its ground
|
|
1179
|
-
* lands where its inks can still read (`groundBounds`), a function of the
|
|
1179
|
+
* lands where its inks can still read (`groundBounds`), a function of the color
|
|
1180
1180
|
* alone so one fill stays one fill, and an ink that does not read on it slides
|
|
1181
1181
|
* to the luminance that does, on the side of the ground it stands on while that
|
|
1182
1182
|
* side has room. A layer that takes an ink across its ground crosses it here too:
|
|
@@ -1186,7 +1186,7 @@ function groundBounds(pairs, depth, terminal) {
|
|
|
1186
1186
|
* it would be alone. A frame that takes a ground past its bounds is the one
|
|
1187
1187
|
* exception, whatever its ink reads: a swap of ink and ground lands at the floor,
|
|
1188
1188
|
* since a bound cannot ask what one cell's ink does without splitting a fill.
|
|
1189
|
-
* The floor outranks a
|
|
1189
|
+
* The floor outranks a color being the terminal's: a terminal
|
|
1190
1190
|
* ink is moved here where a ground under it left it unreadable. The rule is the
|
|
1191
1191
|
* same for one layer as for five: a veil (`veiled`, or a transition's `effect`)
|
|
1192
1192
|
* stops at the floor here, so a transition goes in an `Effected` around this
|
|
@@ -1200,12 +1200,12 @@ export function layered(...effects) {
|
|
|
1200
1200
|
const words = new Set(drawn.pairs.map(pairKey));
|
|
1201
1201
|
const offered = depth === ColorDepth.TRUECOLOR ? undefined : measuredIndexed(depth, terminal);
|
|
1202
1202
|
// `color` as itself if it is within the first of `bounds`, else the nearest
|
|
1203
|
-
//
|
|
1203
|
+
// color the depth writes there, else the same of the next; as itself where
|
|
1204
1204
|
// none has any. In truecolor that is its hue at the bound's luminance, or a
|
|
1205
|
-
// grey there: a
|
|
1205
|
+
// grey there: a color keeps its chroma only so near black or white, and a
|
|
1206
1206
|
// bound may be at either. `rested` is always offered, since a depth also
|
|
1207
|
-
// writes the terminal's own
|
|
1208
|
-
//
|
|
1207
|
+
// writes the terminal's own color, which it has no index for, and the
|
|
1208
|
+
// color a pair rested in is the one a bound was measured from.
|
|
1209
1209
|
const landing = (color, rested, ...bounds) => {
|
|
1210
1210
|
const at = stop(color);
|
|
1211
1211
|
for (const { lo, hi } of bounds) {
|
|
@@ -1220,9 +1220,9 @@ export function layered(...effects) {
|
|
|
1220
1220
|
return color;
|
|
1221
1221
|
};
|
|
1222
1222
|
const floored = (pair, moved) => {
|
|
1223
|
-
// [LAW:single-enforcer] Every measure below reads the
|
|
1223
|
+
// [LAW:single-enforcer] Every measure below reads the colors as `Effected`
|
|
1224
1224
|
// writes them: rounded to the depth, and a translucent one laid on what is
|
|
1225
|
-
// beneath it, so a
|
|
1225
|
+
// beneath it, so a color that passes here cannot draw as one that fails.
|
|
1226
1226
|
const ground = landing(Effected.shows(pair, moved, depth, terminal).bg, pair.bg, grounds.get(pair.bg.hex) ?? UNBOUNDED);
|
|
1227
1227
|
const { fg } = Effected.shows(pair, { fg: moved.fg, bg: ground }, depth, terminal);
|
|
1228
1228
|
const floor = floorOf(pair, depth);
|
|
@@ -1237,7 +1237,7 @@ export function layered(...effects) {
|
|
|
1237
1237
|
return { fg: landing(fg, pair.fg, near, far), bg: ground };
|
|
1238
1238
|
};
|
|
1239
1239
|
return Animation.map(Animation.all(effects.map((effect) => effect(drawn))), (frames) => {
|
|
1240
|
-
// A
|
|
1240
|
+
// A color pair lands once a frame, wherever it falls. [LAW:one-source-of-truth]
|
|
1241
1241
|
const landed = new Map();
|
|
1242
1242
|
return (look, cell) => {
|
|
1243
1243
|
const moved = frames.reduce((under, frame) => frame(under, cell), look);
|
|
@@ -1250,23 +1250,23 @@ export function layered(...effects) {
|
|
|
1250
1250
|
};
|
|
1251
1251
|
}
|
|
1252
1252
|
/**
|
|
1253
|
-
* A signal: every cell's ink stepping through `steps`'
|
|
1254
|
-
* one
|
|
1255
|
-
* chevrons in bright changing
|
|
1253
|
+
* A signal: every cell's ink stepping through `steps`' colors on a beat,
|
|
1254
|
+
* one color a hold, looping, for the time it is handed — a gauge's
|
|
1255
|
+
* chevrons in bright changing colors. The ground is left as the cell draws
|
|
1256
1256
|
* it, and the words stay legible on it: each step's ink is written as the
|
|
1257
|
-
*
|
|
1257
|
+
* color nearest the step's (OKLab's ΔE) that the depth draws and that
|
|
1258
1258
|
* reads on the cell's ground, as `Effected` writes it, at the cell's floor
|
|
1259
1259
|
* (`floorOf`) — its resting contrast or `LEGIBLE`, whichever is lower, the
|
|
1260
1260
|
* floor every loop here keeps. In truecolor that is the step itself, or its
|
|
1261
1261
|
* hue and chroma at the lightness nearest it that reaches the floor
|
|
1262
|
-
* (`ensureContrast`, the one slide a template's `
|
|
1263
|
-
* below truecolor, the nearest of the
|
|
1264
|
-
* sixteen-
|
|
1265
|
-
* that does (`
|
|
1262
|
+
* (`ensureContrast`, the one slide a template's `ensureContrast` makes too);
|
|
1263
|
+
* below truecolor, the nearest of the colors the depth has an index for, so on a
|
|
1264
|
+
* sixteen-color terminal it steps from slot to slot, the one effect here
|
|
1265
|
+
* that does (`staticWhereUnchanged`). A cell whose ground no offered color reads
|
|
1266
1266
|
* on keeps the ink it rested in.
|
|
1267
1267
|
*
|
|
1268
1268
|
* The search is settled once for each ground and step — below truecolor,
|
|
1269
|
-
* which
|
|
1269
|
+
* which colors read on a ground once for that ground — so a frame
|
|
1270
1270
|
* inside a step redoes none, and nor does a later pass of the beat: at sixty
|
|
1271
1271
|
* frames a second a one-second step is sixty identical frames, and the
|
|
1272
1272
|
* frame changes when the beat does: the signal is the beat mapped to inks.
|
|
@@ -1274,7 +1274,7 @@ export function layered(...effects) {
|
|
|
1274
1274
|
* nothing here reads a clock.
|
|
1275
1275
|
*/
|
|
1276
1276
|
export function steppedInk(steps) {
|
|
1277
|
-
const rhythm =
|
|
1277
|
+
const rhythm = stepSequence(steps.map((step) => step.seconds));
|
|
1278
1278
|
// A copy, as `beat` keeps of the holds: what the signal plays stays what
|
|
1279
1279
|
// `steps` said whatever the caller does to its array.
|
|
1280
1280
|
const inks = steps.map((step) => step.color);
|
|
@@ -1293,10 +1293,10 @@ export function steppedInk(steps) {
|
|
|
1293
1293
|
* what an earlier one drew redoes no search. [LAW:single-enforcer] Every
|
|
1294
1294
|
* effect that writes an ink of the caller's choosing writes it through
|
|
1295
1295
|
* here — `steppedInk`'s steps and `gradient`'s ramp alike — so none of them
|
|
1296
|
-
* leaves a word unreadable. Below truecolor which of the depth's
|
|
1296
|
+
* leaves a word unreadable. Below truecolor which of the depth's colors
|
|
1297
1297
|
* read on a pair does not depend on the ink, so it is settled once for each
|
|
1298
1298
|
* pair: a ramp, whose every frame offers inks no frame offered before,
|
|
1299
|
-
* then costs a nearest-
|
|
1299
|
+
* then costs a nearest-color scan a cell, not a contrast test of the whole
|
|
1300
1300
|
* palette.
|
|
1301
1301
|
*/
|
|
1302
1302
|
function legibleInks() {
|
|
@@ -1316,9 +1316,9 @@ function legibleInks() {
|
|
|
1316
1316
|
};
|
|
1317
1317
|
}
|
|
1318
1318
|
/**
|
|
1319
|
-
* Of `offered`, the
|
|
1319
|
+
* Of `offered`, the colors that read on `pair`'s ground at `pair`'s floor,
|
|
1320
1320
|
* as `Effected` writes them (`shows`). [LAW:one-source-of-truth] Measured the
|
|
1321
|
-
* way `shares` and `wheel` measure: the written
|
|
1321
|
+
* way `shares` and `wheel` measure: the written colors, at the catalogue's
|
|
1322
1322
|
* floor. An ink none of them is nearest stays `pair`'s own.
|
|
1323
1323
|
*/
|
|
1324
1324
|
function legibleAmong(pair, offered, depth, terminal) {
|
|
@@ -1336,7 +1336,7 @@ function legibleAmong(pair, offered, depth, terminal) {
|
|
|
1336
1336
|
* (`ATTRIBUTE_DEFAULTS`) [LAW:one-type-per-behavior].
|
|
1337
1337
|
*/
|
|
1338
1338
|
export function blink({ attribute, off, on }) {
|
|
1339
|
-
const rhythm =
|
|
1339
|
+
const rhythm = stepSequence([off, on]);
|
|
1340
1340
|
const frames = [(look) => look, (look) => ({ ...look, attributes: { ...look.attributes, [attribute]: true } })];
|
|
1341
1341
|
return () => Animation.map(rhythm, (i) => frames[i]);
|
|
1342
1342
|
}
|
|
@@ -1353,7 +1353,7 @@ export const ATTRIBUTE_DEFAULTS = {
|
|
|
1353
1353
|
* Every effect in the catalogue by its name in `EFFECT_CURVES`, each made the
|
|
1354
1354
|
* one way: for an element, at its signed-off curve unless handed another,
|
|
1355
1355
|
* with the light and the width it was signed off with. A loop comes lit on
|
|
1356
|
-
* the element's own
|
|
1356
|
+
* the element's own colors (`onColors`); a transition comes as itself, to
|
|
1357
1357
|
* be told when it is done. [LAW:types-are-the-program] Keyed by the two
|
|
1358
1358
|
* name types, which `EFFECT_CURVES` is held to as well: a curve signed off
|
|
1359
1359
|
* does not compile until it is named a loop or a transition, and then not
|